"""Phase 2: five-check verification gate over a fixer-produced worktree.

Order: luac -p, packager dry-run (skipped if absent), removed-symbol grep,
path allowlist, diff size cap. Each check produces one VerifyRow.
"""
import re
import shutil
import subprocess
from dataclasses import dataclass, field
from pathlib import Path

from bot.lib.grep import find_call_sites

MAX_DIFF_LINES = 200
DISALLOWED_PATH_RE = re.compile(r"^(Libs/|Localization/|\.release/|\.pkgmeta\.yaml$|bot/)")
PACKAGER_TIMEOUT_SECONDS = 120


@dataclass(frozen=True)
class VerifyRow:
    name: str
    passed: bool
    detail: str


@dataclass
class VerifyResult:
    passed_all: bool
    rows: list[VerifyRow] = field(default_factory=list)


def _git(cwd: Path, *args: str) -> subprocess.CompletedProcess:
    return subprocess.run(
        ["git", "-C", str(cwd), *args],
        capture_output=True, text=True, check=False,
    )


def _changed_files(worktree: Path) -> list[str]:
    proc = _git(worktree, "diff", "--name-only", "master..HEAD")
    return [line.strip() for line in proc.stdout.splitlines() if line.strip()]


def _luac_check(worktree: Path, files: list[str]) -> VerifyRow:
    if shutil.which("luac") is None:
        return VerifyRow("luac -p", False, "luac not installed")
    failures: list[str] = []
    for f in files:
        if not f.endswith(".lua"):
            continue
        full = worktree / f
        if not full.exists():
            continue
        proc = subprocess.run(
            ["luac", "-p", str(full)],
            capture_output=True, text=True, check=False,
        )
        if proc.returncode != 0:
            failures.append(f"{f}: {proc.stderr.strip()}")
    if failures:
        return VerifyRow("luac -p", False, "; ".join(failures))
    return VerifyRow("luac -p", True, f"{sum(1 for f in files if f.endswith('.lua'))} files clean")


def _packager_check(worktree: Path, repo_root: Path) -> VerifyRow:
    script = repo_root / ".release" / "release.sh"
    if not script.exists():
        return VerifyRow("packager dry-run", True, ".release/release.sh not present, skipped")
    proc = subprocess.run(
        ["bash", str(script), "-d", "-z", "-t", str(worktree)],
        capture_output=True, text=True,
        timeout=PACKAGER_TIMEOUT_SECONDS, check=False,
    )
    if proc.returncode != 0:
        combined = (proc.stderr or "") + "\n" + (proc.stdout or "")
        # BigWigs packager refuses to run inside a git worktree because it
        # looks for a `.git` directory (worktrees use a `.git` file). The
        # luac and grep checks already validate the proposed fix; treat this
        # as a skip rather than a failure for worktree-based fixes.
        if "No Git, SVN, or Hg checkout found" in combined:
            return VerifyRow("packager dry-run", True, "skipped: bigwigs packager incompatible with git worktrees")
        tail = (proc.stderr or proc.stdout).splitlines()[-3:]
        return VerifyRow("packager dry-run", False, " | ".join(tail))
    return VerifyRow("packager dry-run", True, "exit 0")


def _removed_symbol_check(worktree: Path, removed_symbols: set[str]) -> VerifyRow:
    if not removed_symbols:
        return VerifyRow("removed-symbol grep", True, "no symbols to check")
    sites = find_call_sites(removed_symbols, worktree)
    if sites:
        sample = ", ".join(f"{s.symbol}@{s.path}:{s.line}" for s in sites[:3])
        return VerifyRow("removed-symbol grep", False, f"{len(sites)} hits remaining ({sample})")
    return VerifyRow("removed-symbol grep", True, "0 remaining hits")


def _path_allowlist_check(files: list[str]) -> VerifyRow:
    bad = [f for f in files if DISALLOWED_PATH_RE.match(f)]
    if bad:
        return VerifyRow("path allowlist", False, f"disallowed: {', '.join(bad)}")
    return VerifyRow("path allowlist", True, f"{len(files)} files in scope")


_SHORTSTAT_RE = re.compile(r"(\d+) insertions?\(\+\).*?(\d+) deletions?\(-\)")
_INSERTIONS_ONLY_RE = re.compile(r"(\d+) insertions?\(\+\)")
_DELETIONS_ONLY_RE = re.compile(r"(\d+) deletions?\(-\)")


def _diff_size_check(worktree: Path) -> VerifyRow:
    proc = _git(worktree, "diff", "--shortstat", "master..HEAD")
    text = proc.stdout
    ins = del_ = 0
    m = _SHORTSTAT_RE.search(text)
    if m:
        ins = int(m.group(1))
        del_ = int(m.group(2))
    else:
        m_i = _INSERTIONS_ONLY_RE.search(text)
        m_d = _DELETIONS_ONLY_RE.search(text)
        if m_i:
            ins = int(m_i.group(1))
        if m_d:
            del_ = int(m_d.group(1))
    total = ins + del_
    if total > MAX_DIFF_LINES:
        return VerifyRow("diff size", False, f"{total} lines (cap {MAX_DIFF_LINES})")
    return VerifyRow("diff size", True, f"{total} lines")


def verify(
    *,
    worktree_path: Path,
    removed_symbols: set[str],
    repo_root: Path,
) -> VerifyResult:
    files = _changed_files(worktree_path)
    rows: list[VerifyRow] = [
        _luac_check(worktree_path, files),
        _packager_check(worktree_path, repo_root),
        _removed_symbol_check(worktree_path, removed_symbols),
        _path_allowlist_check(files),
        _diff_size_check(worktree_path),
    ]
    return VerifyResult(passed_all=all(r.passed for r in rows), rows=rows)
