"""execute_verify — drive post-generation gates.

The gates themselves live in ui_clone.gate; we shell out so the gate
module's argparse + exit codes stay the source of truth.
"""

from __future__ import annotations

import datetime
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import TYPE_CHECKING

from ui_clone.hooks._common import BOLD as _BOLD
from ui_clone.hooks._common import GREEN as _GREEN
from ui_clone.hooks._common import NC as _NC
from ui_clone.hooks._common import RED as _RED
from ui_clone.state import PipelineState
from ui_clone.verify_report import build_verify_report, write_verify_report

if TYPE_CHECKING:
    from ui_clone.pipeline import Pipeline


def _quick_tier_blocker(ref_dir: Path) -> str | None:
    """Return a final-verify blocker when the plan is quick-tier.

    `pipeline ... verify` is the canonical closeout path that writes
    verify-stamp.json. Quick plans are useful for inner iteration, but a quick
    stamp lets the Stop hook accept a clone that never ran browser scroll/live
    parity checks. Missing/unreadable plans are left to the normal spec gate.
    """
    plan_path = ref_dir / "verification-plan.json"
    if not plan_path.is_file():
        return None
    try:
        plan = json.loads(plan_path.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return None
    tier = str(plan.get("tier") or "").lower()
    if tier != "quick":
        return None
    return (
        "verification-plan.json is tier=quick. Canonical pipeline verify "
        "writes the final verify-stamp.json and must use tier=standard or "
        "tier=comprehensive so browser scroll/live parity checks can run. "
        "Regenerate the plan with: "
        "UI_CLONE_VERIFY_TIER=standard bash skills/visual-debug/scripts/"
        "verification-plan.sh <ref-dir>"
    )


def _resolve_verify_impl_dir(ref_dir: Path, cwd: Path) -> Path:
    """Resolve the implementation tree that verify should stamp.

    Phase execution persists the canonical per-run impl root in
    pipeline-state.json and mirrors it to `.impl-root`. Verify must prefer that
    run-owned path over the repository-level `impl/` symlink; otherwise a
    stale symlink can make a stamp/report describe one clone while browser
    checks or operators inspect another.
    """
    env_root = (os.environ.get("UI_CLONE_IMPL_ROOT") or "").strip()
    candidates: list[str] = []
    if env_root:
        candidates.append(env_root)
    state_root = PipelineState.load(ref_dir).impl_root.strip()
    if state_root:
        candidates.append(state_root)
    marker = ref_dir / ".impl-root"
    try:
        marker_root = marker.read_text(encoding="utf-8").strip()
    except OSError:
        marker_root = ""
    if marker_root:
        candidates.append(marker_root)
    candidates.append(str(cwd / "impl"))

    for candidate in candidates:
        path = Path(candidate).expanduser()
        try:
            resolved = path.resolve()
        except OSError:
            resolved = path
        if resolved.is_dir():
            return resolved
    return (cwd / "impl").resolve()


def _write_gate_log(ref_dir: Path, gate_name: str, result: subprocess.CompletedProcess[str]) -> Path:
    log_dir = ref_dir / "logs" / "verify"
    log_dir.mkdir(parents=True, exist_ok=True)
    log_path = log_dir / f"{gate_name}.log"
    log_path.write_text(
        (result.stdout or "")
        + (("\n--- stderr ---\n" + result.stderr) if result.stderr else ""),
        encoding="utf-8",
    )
    return log_path


def execute_verify(pipeline: Pipeline, json_output: bool = False) -> int:
    gates_post_impl = (
        "spec",
        "post-implement",
        "boundary",
        "font-parity",
        "section-compare",
    )
    impl_dir = _resolve_verify_impl_dir(pipeline.ref_dir, Path.cwd())
    if not impl_dir.is_dir():
        message = (
            f"verify: impl/ not found at {impl_dir}. "
            "Generate components first, then re-run verify."
        )
        if json_output:
            print(json.dumps({
                "schemaVersion": 1,
                "status": "fail",
                "reason": message,
                "next_action": "generate_impl",
                "verify_stamp": {"created": False, "success_only": True},
            }, ensure_ascii=False, indent=2))
        else:
            print(f"{_RED}{message}{_NC}")
        return 1

    tier_blocker = _quick_tier_blocker(pipeline.ref_dir)
    if tier_blocker is not None:
        if json_output:
            print(json.dumps({
                "schemaVersion": 1,
                "status": "fail",
                "reason": tier_blocker,
                "next_action": "regenerate_verification_plan",
                "verify_stamp": {"created": False, "success_only": True},
            }, ensure_ascii=False, indent=2))
        else:
            print(f"{_RED}verify: {tier_blocker}{_NC}")
        return 1

    failures: list[str] = []
    gate_exit_codes: dict[str, int] = {}
    gate_logs: dict[str, str] = {}
    for gate_name in gates_post_impl:
        if not json_output:
            print(f"\n{_BOLD}== verify: gate {gate_name}{_NC}")
        result = subprocess.run(
            [sys.executable, "-m", "ui_clone.gate", str(pipeline.ref_dir), gate_name],
            capture_output=json_output,
            text=json_output,
        )
        if json_output:
            gate_logs[gate_name] = str(_write_gate_log(pipeline.ref_dir, gate_name, result))
        gate_exit_codes[gate_name] = result.returncode
        if result.returncode != 0:
            failures.append(gate_name)
            if not json_output:
                print(
                    f"  {_RED}✗{_NC} {gate_name} exit {result.returncode} "
                    f"— continuing to surface every failure rather than short-circuit"
                )
    if failures:
        report = build_verify_report(
            pipeline.ref_dir,
            gates=gates_post_impl,
            impl_dir=impl_dir,
            gate_exit_codes=gate_exit_codes,
        )
        json_path, html_path = write_verify_report(pipeline.ref_dir, report)
        next_action = (
            f"Read {json_path} and patch only the listed failures, then rerun "
            f"`python -m ui_clone.pipeline {pipeline.url} {pipeline.component} "
            f"{pipeline.session} verify`."
        )
        state = PipelineState.load(pipeline.ref_dir)
        state.mark_terminal(
            pipeline.ref_dir,
            status="failed",
            category="canonical-verify-failed",
            gate=failures[0],
            reason=(
                f"canonical verify failed {len(failures)} gate(s): "
                f"{', '.join(failures)}"
            ),
            detail={
                "failed_gates": failures,
                "gate_exit_codes": gate_exit_codes,
                "verify_report": str(json_path),
                "verify_report_html": str(html_path),
                "gate_logs": gate_logs,
                "implDir": str(impl_dir),
            },
            next_action=next_action,
        )
        payload = {
            "schemaVersion": 1,
            "status": "failed",
            "failed_gates": failures,
            "gate_exit_codes": gate_exit_codes,
            "verify_stamp": {
                "path": str(pipeline.ref_dir / "verify-stamp.json"),
                "created": False,
                "success_only": True,
            },
            "terminalState": state.terminal_state,
            "reports": {
                "json": str(json_path),
                "html": str(html_path),
            },
            "logs": gate_logs,
            "next_action": next_action,
            "read_for_llm": [str(json_path), str(pipeline.ref_dir / "pipeline-state.json")],
            "do_not_read": [
                str(pipeline.ref_dir / "raw"),
                str(pipeline.ref_dir / "raw" / "dom.json"),
                str(pipeline.ref_dir / "raw" / "computed.json"),
                str(pipeline.ref_dir / "raw" / "styles.json"),
            ],
        }
        if json_output:
            print(json.dumps(payload, ensure_ascii=False, indent=2))
        else:
            print(
                f"\n{_RED}{_BOLD}verify: {len(failures)} gate(s) failed: "
                f"{', '.join(failures)}{_NC}"
            )
            print(f"  report: {json_path}")
            print(f"  html  : {html_path}")
            print(f"  terminalState: {pipeline.ref_dir / 'pipeline-state.json'}")
        return 1
    # Stop-hook stamp. On success, write a current-run-fresh marker that
    # the Stop hook checks before allowing the agent to claim completion.
    # Without this stamp, the Stop hook will block — closing the bypass
    # where an agent runs individual verification scripts directly and
    # never reaches the canonical entry.
    stamp = {
        "verifiedAt": datetime.datetime.now(datetime.UTC)
            .strftime("%Y-%m-%dT%H:%M:%SZ"),
        "gatesPassed": list(gates_post_impl),
        "stampedBy": "pipeline.execute_verify",
        "implDir": str(impl_dir),
        "refDir": str(pipeline.ref_dir),
    }
    stamp_path = pipeline.ref_dir / "verify-stamp.json"
    stamp_path.write_text(json.dumps(stamp, indent=2) + "\n")
    PipelineState.load(pipeline.ref_dir).clear_terminal(pipeline.ref_dir)
    report = build_verify_report(
        pipeline.ref_dir,
        gates=gates_post_impl,
        impl_dir=impl_dir,
        gate_exit_codes=gate_exit_codes,
    )
    json_path, html_path = write_verify_report(pipeline.ref_dir, report)
    if json_output:
        print(json.dumps({
            "schemaVersion": 1,
            "status": "passed",
            "gates_passed": list(gates_post_impl),
            "verify_stamp": {
                "path": str(stamp_path),
                "created": True,
                "success_only": True,
            },
            "reports": {
                "json": str(json_path),
                "html": str(html_path),
            },
            "logs": gate_logs,
            "next_action": None,
        }, ensure_ascii=False, indent=2))
    else:
        print(f"\n{_GREEN}{_BOLD}verify: all post-impl gates passed{_NC}")
        print(f"  stamp: {stamp_path}")
        print(f"  report: {json_path}")
        print(f"  html  : {html_path}")
    return 0
