"""Per-stage Git snapshots, from the v0.1 design, section 7.2."""

from __future__ import annotations

import hashlib
import subprocess
from pathlib import Path

from .domain import CheckpointRecord


class Checkpoint:
    def __init__(self, task_dir: str | Path) -> None:
        self.task_dir = Path(task_dir)

    @property
    def patch_dir(self) -> Path:
        return self.task_dir / "patches"

    def capture(self, stage: str, cycle: int, worktree: Path, baseline: str | None) -> CheckpointRecord | None:
        """Record HEAD, the porcelain status, and a binary diff against the baseline."""
        head = self._git(worktree, "rev-parse", "HEAD")
        if head is None:
            return None
        status = self._git(worktree, "status", "--porcelain") or ""
        self.patch_dir.mkdir(parents=True, exist_ok=True)
        path = self.patch_dir / f"checkpoint-{stage}-{cycle:02d}.diff"
        diff = self._git(worktree, "diff", "--binary", baseline) if baseline else self._git(worktree, "diff", "--binary")
        path.write_text(diff or "", encoding="utf-8", newline="\n")
        digest = self._workspace_digest(worktree)
        if digest is not None:
            Path(f"{path}.sha256").write_text(digest + "\n", encoding="ascii")
        return CheckpointRecord(
            stage=stage,
            cycle=cycle,
            head=head.strip(),
            status_porcelain=status,
            diff_path=str(path),
        )

    def verify(self, record: CheckpointRecord, worktree: Path) -> bool:
        """True only when the workspace still looks exactly as the snapshot recorded it."""
        head = self._git(worktree, "rev-parse", "HEAD")
        status = self._git(worktree, "status", "--porcelain")
        if head is None or status is None:
            return False
        if head.strip() != record.head or status != record.status_porcelain:
            return False
        digest_path = Path(f"{record.diff_path}.sha256")
        if not digest_path.is_file():
            return True
        digest = self._workspace_digest(worktree)
        return digest is not None and digest == digest_path.read_text(encoding="ascii").strip()

    @classmethod
    def _workspace_digest(cls, worktree: Path) -> str | None:
        status = cls._git(worktree, "status", "--porcelain", "-z")
        diff = cls._git(worktree, "diff", "--binary", "HEAD")
        untracked = cls._git(worktree, "ls-files", "--others", "--exclude-standard", "-z")
        if status is None or diff is None or untracked is None:
            return None
        digest = hashlib.sha256()
        digest.update(status.encode("utf-8", "surrogateescape"))
        digest.update(diff.encode("utf-8", "surrogateescape"))
        for relative in sorted(path for path in untracked.split("\0") if path):
            target = worktree / relative
            digest.update(relative.encode("utf-8", "surrogateescape"))
            if target.is_file():
                digest.update(target.read_bytes())
        return digest.hexdigest()

    @staticmethod
    def _git(worktree: Path, *args: str) -> str | None:
        result = subprocess.run(
            ["git", "-C", str(worktree), *args], capture_output=True, text=True, errors="replace"
        )
        return result.stdout if result.returncode == 0 else None


