"""Read the prepared final-verification target snapshot.

`run.write_verification_target_snapshot` writes it; two consumers read it —
report assembly, which records the run's `verificationScope`, and `validate-run`,
which re-checks the published report against it. The digest rule lived only in
the validator, so a second reader would have been a second implementation of it;
one wrong copy makes a well-formed target look tampered with. It lives here so
both readers ask the same question.
"""
from __future__ import annotations

import hashlib
import argparse
import json
import re
import subprocess
from pathlib import Path

from .execution_mutation_audit import source_content_snapshot
from .json_boundary import load_owned_object
from .path_hints import hydrate_active_run_context
from .qa_commands import verification_command_defects

TARGET_FIELD_RES = {
    "scope": re.compile(r"\*\*Verification scope:\*\*\s*`([^`]*)`"),
    "worktree": re.compile(r"\*\*Worktree:\*\*\s*`([^`]*)`"),
    "base": re.compile(r"\*\*Verification base ref:\*\*\s*`([^`]*)`"),
    "head": re.compile(r"\*\*Verification head ref:\*\*\s*`([^`]*)`"),
}
TARGET_STAGES_RE = re.compile(r"\*\*Stages under verification:\*\*\s*\[([^\]]*)\]")
# Anchored at the line start so the cut excludes the `- ` list marker; the
# digest covers the snapshot body only, so leaving the marker in shifts the
# hash and makes every well-formed target look tampered with.
TARGET_DIGEST_RE = re.compile(
    r"^- \*\*Verification target digest:\*\*\s*`([^`]*)`", re.M
)


def read_verification_target(project_root: Path, relative: str) -> dict | None:
    """The prepared target snapshot, but only when its digest still checks out.

    The digest covers the snapshot body (everything before the digest line, as
    `write_verification_target_snapshot` normalizes it). A file that no longer
    matches its own digest is not evidence of anything, so return ``None``
    rather than hand back text someone edited after prep.
    """
    path = Path(project_root) / relative
    if not path.is_file():
        return None
    try:
        content = path.read_text(encoding="utf-8")
    except OSError:
        return None
    digest_match = TARGET_DIGEST_RE.search(content)
    if digest_match is None:
        return None
    body = content[: digest_match.start()]
    body = body.replace("\r\n", "\n").replace("\r", "\n").rstrip() + "\n"
    recomputed = "sha256:" + hashlib.sha256(body.encode("utf-8")).hexdigest()
    if recomputed != digest_match.group(1).strip():
        return None
    parsed = {
        key: (match.group(1).strip() if (match := pattern.search(body)) else "")
        for key, pattern in TARGET_FIELD_RES.items()
    }
    stages_match = TARGET_STAGES_RE.search(body)
    parsed["stages"] = (
        {
            int(value.strip())
            for value in stages_match.group(1).split(",")
            if value.strip().isdigit()
        }
        if stages_match
        else set()
    )
    return parsed


def capture_verification_target(
    project_root: Path, manifest_path: Path, expected_head: str, command: str,
) -> dict:
    """실제 검사 명령은 실행하지 않고 대상과 소스 지문만 확인한다."""
    root = project_root.resolve()
    manifest_path = (root / manifest_path).resolve()
    manifest = load_owned_object(manifest_path, artifact="run manifest")
    context = hydrate_active_run_context(load_owned_object(
        root / manifest["activeRunContextPath"], artifact="active run context",
    ))
    if manifest.get("taskType") == "implementation":
        target = context["executorWorktree"]["path"]
    elif manifest.get("taskType") == "final-verification":
        target = context["verificationTarget"]["worktreePath"]
    else:
        raise ValueError("verification-target requires implementation or final-verification")
    if not target or not Path(target).is_absolute():
        raise ValueError("active run context has no absolute verification worktree")
    worktree = Path(target).resolve()
    defects = verification_command_defects(command)
    if defects:
        raise ValueError("; ".join(defects))
    git = ["git", "-C", str(worktree), "rev-parse"]
    head = subprocess.check_output([*git, "HEAD"], text=True).strip()
    actual_root = subprocess.check_output([*git, "--show-toplevel"], text=True).strip()
    if head != expected_head or Path(actual_root).resolve() != worktree:
        raise ValueError(f"verification target mismatch: expected HEAD {expected_head}, actual {head}, root {actual_root}")
    files = source_content_snapshot(worktree, frozenset({".okstra"}))
    digest = hashlib.sha256(json.dumps(files, sort_keys=True).encode()).hexdigest()
    return {
        "schemaVersion": "1.0", "runManifest": str(manifest_path),
        "taskKey": manifest["taskKey"], "cwd": str(worktree),
        "head": head, "sourceDigest": digest, "command": command,
    }


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(
        prog="okstra verification-target",
        description="Check the recorded verification target without executing the command",
    )
    parser.add_argument("--project-root", type=Path, required=True)
    parser.add_argument("--run-manifest", type=Path, required=True)
    parser.add_argument("--expected-head", required=True)
    parser.add_argument("--command", required=True, help="declared command to check, never executed")
    parser.add_argument("--baseline", type=Path, help="compare against a prior verification-target JSON result")
    args = parser.parse_args(argv)
    try:
        snapshot = capture_verification_target(
            args.project_root, args.run_manifest, args.expected_head, args.command,
        )
        if args.baseline:
            baseline = load_owned_object(args.baseline, artifact="verification target baseline")
            if baseline.get("ok") is not True or baseline.get("target") != snapshot:
                raise ValueError("verification target changed; do not reuse the previous verification result")
        result = {"ok": True, "target": snapshot}
    except (OSError, ValueError, KeyError, TypeError, subprocess.SubprocessError) as exc:
        result = {"ok": False, "reason": str(exc)}
    print(json.dumps(result, ensure_ascii=False, indent=2))
    return 0 if result["ok"] else 1


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