#!/usr/bin/env python3
"""Create a disposable extension XML staging copy from a validated patch bundle."""

from __future__ import annotations

import argparse
import hashlib
import json
import shutil
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from check_1c_extension_staging import check_staging
from check_1c_patch_bundle import check_bundle


def load_json(path: Path) -> dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8-sig"))


def write_json(path: Path, data: dict[str, Any]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def sha256_file(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def safe_relative(relative_path: str) -> Path:
    path = Path(relative_path.replace("\\", "/"))
    if path.is_absolute() or ".." in path.parts or not str(path):
        raise SystemExit(f"Unsafe relative path: {relative_path}")
    return path


def slug_from_bundle(bundle_dir: Path) -> str:
    return bundle_dir.name or "onec-extension-staging"


def copy_extension_root(extension_root: Path, staging_dir: Path, *, force: bool) -> None:
    if not extension_root.exists() or not extension_root.is_dir():
        raise SystemExit(f"Extension root does not exist or is not a directory: {extension_root}")
    if staging_dir.exists() and not force:
        raise SystemExit(f"Staging directory already exists: {staging_dir}. Use --force to replace.")
    if staging_dir.exists():
        shutil.rmtree(staging_dir)
    shutil.copytree(extension_root, staging_dir)


def create_staging(bundle_dir: Path, output_root: Path, slug: str | None, *, force: bool) -> dict[str, Any]:
    bundle_check = check_bundle(bundle_dir)
    if not bundle_check.get("passed"):
        raise SystemExit(f"Bundle validation failed: {bundle_check.get('counts')}")
    bundle = load_json(bundle_dir / "manifest.json")
    extension_root_raw = bundle.get("extension_root")
    if not extension_root_raw:
        raise SystemExit("Bundle manifest has no extension_root.")
    extension_root = Path(str(extension_root_raw))
    staging_slug = slug or slug_from_bundle(bundle_dir)
    staging_dir = output_root / staging_slug
    copy_extension_root(extension_root, staging_dir, force=force)

    applied_files = []
    for record in bundle.get("files") or []:
        relative = safe_relative(str(record.get("relative_path") or ""))
        bundle_path = safe_relative(str(record.get("bundle_path") or ""))
        src = bundle_dir / bundle_path
        dst = staging_dir / relative
        if not src.exists():
            raise SystemExit(f"Bundle modified file is missing: {src}")
        dst.parent.mkdir(parents=True, exist_ok=True)
        original_hash = sha256_file(dst) if dst.exists() else None
        shutil.copy2(src, dst)
        staged_hash = sha256_file(dst)
        expected = ((record.get("sha256") or {}).get("working") if isinstance(record.get("sha256"), dict) else None)
        if expected and staged_hash != expected:
            raise SystemExit(f"Staged file hash mismatch for {relative}: {staged_hash} != {expected}")
        applied_files.append(
            {
                "relative_path": str(relative).replace("\\", "/"),
                "bundle_path": str(bundle_path).replace("\\", "/"),
                "staged_path": str(dst),
                "source_original_sha256": original_hash,
                "staged_sha256": staged_hash,
                "expected_working_sha256": expected,
                "kind": record.get("kind"),
                "name": record.get("name"),
            }
        )

    manifest = {
        "schema": "onec_extension_staging.v1",
        "created_at_utc": datetime.now(timezone.utc).isoformat(),
        "bundle_dir": str(bundle_dir),
        "bundle_schema": bundle.get("schema"),
        "extension_root": str(extension_root),
        "staging_dir": str(staging_dir),
        "preferred_extension": bundle.get("preferred_extension"),
        "task": bundle.get("task"),
        "bundle_check": {
            "schema": bundle_check.get("schema"),
            "passed": bundle_check.get("passed"),
            "counts": bundle_check.get("counts"),
        },
        "files": applied_files,
        "counts": {"files": len(applied_files)},
        "safety": {
            "source_extension_modified": False,
            "sql_modified": False,
            "requires_disposable_1c_validation": True,
        },
    }
    write_json(staging_dir / "_codex_staging_manifest.json", manifest)
    (staging_dir / "_codex_staging_README.md").write_text(render_readme(manifest), encoding="utf-8")
    staging_check = check_staging(staging_dir)
    if not staging_check.get("passed"):
        raise SystemExit(f"Staging validation failed: {staging_check.get('counts')}")
    return {
        "schema": "onec_extension_staging_creation.v1",
        "staging_dir": str(staging_dir),
        "manifest": str(staging_dir / "_codex_staging_manifest.json"),
        "bundle_dir": str(bundle_dir),
        "bundle_check": manifest["bundle_check"],
        "staging_check": {
            "schema": staging_check.get("schema"),
            "passed": staging_check.get("passed"),
            "counts": staging_check.get("counts"),
        },
        "counts": manifest["counts"],
    }


def render_readme(manifest: dict[str, Any]) -> str:
    lines = [
        "# 1C Extension Staging Copy",
        "",
        f"Source extension root: `{manifest.get('extension_root')}`",
        f"Bundle: `{manifest.get('bundle_dir')}`",
        f"Created UTC: `{manifest.get('created_at_utc')}`",
        "",
        "## Rules",
        "",
        "- This is a disposable staging copy.",
        "- Source extension files were not modified.",
        "- SQL, Config, ConfigSave, ConfigCAS, and production Designer state were not modified.",
        "- Validate this staging copy in a disposable 1C base before any production action.",
        "",
        "## Applied Files",
        "",
    ]
    for item in manifest.get("files") or []:
        lines.append(f"- `{item.get('relative_path')}` ({item.get('kind')})")
    return "\n".join(lines) + "\n"


def main() -> int:
    parser = argparse.ArgumentParser(description="Create a disposable extension XML staging copy from a patch bundle.")
    parser.add_argument("--bundle-dir", type=Path, required=True)
    parser.add_argument("--output-root", type=Path, default=Path("reports/1c-sql/upo/extension-staging"))
    parser.add_argument("--slug")
    parser.add_argument("--force", action="store_true")
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()

    result = create_staging(args.bundle_dir, args.output_root, args.slug, force=args.force)
    if args.output:
        write_json(args.output, result)
    print(json.dumps(result, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
