#!/usr/bin/env python3
"""Diff original/working files in a 1C patch workspace."""

from __future__ import annotations

import argparse
import difflib
import hashlib
import json
from pathlib import Path
from typing import Any

from check_1c_patch_workspace_integrity import check_workspace


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


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 read_text_lines(path: Path) -> list[str]:
    try:
        return path.read_text(encoding="utf-8-sig").splitlines(keepends=True)
    except UnicodeDecodeError:
        return path.read_text(encoding="cp1251", errors="replace").splitlines(keepends=True)


def count_diff_lines(diff_lines: list[str]) -> dict[str, int]:
    added = 0
    removed = 0
    hunks = 0
    for line in diff_lines:
        if line.startswith("@@"):
            hunks += 1
        elif line.startswith("+") and not line.startswith("+++"):
            added += 1
        elif line.startswith("-") and not line.startswith("---"):
            removed += 1
    return {"added_lines": added, "removed_lines": removed, "hunks": hunks}


def diff_file(record: dict[str, Any], *, workspace: Path, max_patch_chars: int) -> dict[str, Any]:
    rel = Path(str(record.get("relative_path") or ""))
    original = workspace / "original" / rel
    working = workspace / "working" / rel
    result = {
        "relative_path": str(rel).replace("\\", "/"),
        "kind": record.get("kind"),
        "name": record.get("name"),
        "origin": record.get("origin"),
        "original_path": str(original),
        "working_path": str(working),
        "exists": {"original": original.exists(), "working": working.exists()},
    }
    if not original.exists() or not working.exists():
        result["status"] = "missing_file"
        return result
    original_hash = sha256_file(original)
    working_hash = sha256_file(working)
    result["sha256"] = {"original": original_hash, "working": working_hash}
    if original_hash == working_hash:
        result["status"] = "unchanged"
        result["diff"] = {"added_lines": 0, "removed_lines": 0, "hunks": 0, "patch": ""}
        return result
    old_lines = read_text_lines(original)
    new_lines = read_text_lines(working)
    diff_lines = list(
        difflib.unified_diff(
            old_lines,
            new_lines,
            fromfile=f"original/{result['relative_path']}",
            tofile=f"working/{result['relative_path']}",
            lineterm="",
        )
    )
    patch = "\n".join(line.rstrip("\n") for line in diff_lines)
    counts = count_diff_lines(diff_lines)
    result["status"] = "modified"
    result["diff"] = {
        **counts,
        "patch": patch[:max_patch_chars],
        "patch_truncated": len(patch) > max_patch_chars,
        "patch_chars": len(patch),
    }
    return result


def build_diff(workspace: Path, *, max_patch_chars: int) -> dict[str, Any]:
    integrity = check_workspace(workspace)
    if not integrity.get("passed"):
        return {
            "schema": "onec_patch_workspace_diff.v1",
            "workspace": str(workspace),
            "passed": False,
            "integrity": integrity,
            "files": [],
            "counts": {
                "files": 0,
                "modified": 0,
                "unchanged": 0,
                "missing": 0,
                "added_lines": 0,
                "removed_lines": 0,
                "hunks": 0,
            },
        }
    manifest_path = workspace / "manifest.json"
    manifest = load_json(manifest_path)
    files = [diff_file(record, workspace=workspace, max_patch_chars=max_patch_chars) for record in manifest.get("files") or []]
    modified = [item for item in files if item.get("status") == "modified"]
    missing = [item for item in files if item.get("status") == "missing_file"]
    return {
        "schema": "onec_patch_workspace_diff.v1",
        "workspace": str(workspace),
        "manifest": str(manifest_path),
        "task": manifest.get("task"),
        "preferred_extension": manifest.get("preferred_extension"),
        "integrity": {
            "schema": integrity.get("schema"),
            "passed": integrity.get("passed"),
            "counts": integrity.get("counts"),
        },
        "files": files,
        "counts": {
            "files": len(files),
            "modified": len(modified),
            "unchanged": len([item for item in files if item.get("status") == "unchanged"]),
            "missing": len(missing),
            "added_lines": sum((item.get("diff") or {}).get("added_lines", 0) for item in modified),
            "removed_lines": sum((item.get("diff") or {}).get("removed_lines", 0) for item in modified),
            "hunks": sum((item.get("diff") or {}).get("hunks", 0) for item in modified),
        },
        "passed": not missing,
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Diff 1C patch workspace original/working files.")
    parser.add_argument("--workspace", type=Path, required=True)
    parser.add_argument("--max-patch-chars", type=int, default=200000)
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()

    result = build_diff(args.workspace, max_patch_chars=args.max_patch_chars)
    output = json.dumps(result, ensure_ascii=False, indent=2) + "\n"
    if args.output:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(output, encoding="utf-8")
    print(json.dumps({"output": str(args.output) if args.output else None, "passed": result["passed"], "counts": result["counts"]}, ensure_ascii=False))
    return 0 if result["passed"] else 2


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