#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import subprocess
import sys
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parents[1]
REQUIRED_CHECK = "Full gate"
REQUIRED_BASE = "main"

class MergeRefused(RuntimeError):
    pass


MergeGateError = MergeRefused


def run(args: list[str], *, check: bool = True) -> subprocess.CompletedProcess[str]:
    cp = subprocess.run(args, cwd=ROOT, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
    if check and cp.returncode:
        raise MergeRefused(f"command failed ({cp.returncode}): {' '.join(args)}\n{(cp.stdout + cp.stderr)[-4000:]}")
    return cp


def json_cmd(args: list[str]) -> Any:
    cp = run(args)
    try:
        return json.loads(cp.stdout)
    except json.JSONDecodeError as exc:
        raise MergeRefused(f"invalid JSON from {' '.join(args)}: {exc}") from exc


def repo_name() -> str:
    data = json_cmd(["gh", "repo", "view", "--json", "nameWithOwner"])
    value = str(data.get("nameWithOwner", "")).strip()
    if "/" not in value:
        raise MergeRefused(f"could not resolve repository identity: {value!r}")
    return value


def local_head() -> str:
    return run(["git", "rev-parse", "HEAD"]).stdout.strip()


def validate_pr_gate(data: dict[str, Any], local_head_sha: str) -> None:
    if data.get("state") != "OPEN":
        raise MergeGateError(f"PR is not open: {data.get('state')}")
    if data.get("isDraft"):
        raise MergeGateError("PR is still draft")
    if data.get("baseRefName") != REQUIRED_BASE:
        raise MergeGateError(f"PR targets {data.get('baseRefName')!r}, expected {REQUIRED_BASE!r}")
    sha = str(data.get("headRefOid", ""))
    if len(sha) != 40:
        raise MergeGateError(f"PR has invalid head SHA: {sha!r}")
    if local_head_sha != sha:
        raise MergeGateError(f"local HEAD does not match PR head: local={local_head_sha} pr={sha}")
    checks = data.get("statusCheckRollup", []) or []
    full = [c for c in checks if c.get("name") == REQUIRED_CHECK and c.get("workflowName") == "ThemeFactory Verify"]
    if not full or not any(c.get("status") == "COMPLETED" and c.get("conclusion") == "SUCCESS" for c in full):
        observed = [{"status": c.get("status"), "conclusion": c.get("conclusion")} for c in full]
        raise MergeGateError(f"Full gate is not successful for current PR head: {observed}")


def inspect_pr(repo: str, pr: int) -> dict[str, Any]:
    fields = "number,state,isDraft,baseRefName,headRefOid,mergeable,mergeStateStatus,statusCheckRollup,url"
    data = json_cmd(["gh", "pr", "view", str(pr), "--repo", repo, "--json", fields])
    validate_pr_gate(data, local_head())
    return data


def exact_sha_full_gate(repo: str, sha: str) -> dict[str, Any]:
    data = json_cmd([
        "gh", "api", f"repos/{repo}/commits/{sha}/check-runs",
        "--method", "GET", "-f", "filter=latest", "-f", "per_page=100",
    ])
    runs = data.get("check_runs", []) if isinstance(data, dict) else []
    matches = [
        r for r in runs
        if r.get("name") == REQUIRED_CHECK
        and r.get("head_sha") == sha
        and r.get("status") == "completed"
        and r.get("conclusion") == "success"
        and (r.get("app") or {}).get("slug") == "github-actions"
    ]
    if not matches:
        observed = [
            {
                "name": r.get("name"),
                "head_sha": r.get("head_sha"),
                "status": r.get("status"),
                "conclusion": r.get("conclusion"),
                "app": (r.get("app") or {}).get("slug"),
            }
            for r in runs if r.get("name") == REQUIRED_CHECK
        ]
        raise MergeRefused(f"exact-SHA GitHub Actions {REQUIRED_CHECK!r} success not found for {sha}; observed={observed}")
    matches.sort(key=lambda r: str(r.get("completed_at") or ""))
    return matches[-1]


def validate(repo: str, pr: int) -> dict[str, Any]:
    pr_data = inspect_pr(repo, pr)
    sha = str(pr_data["headRefOid"])
    check = exact_sha_full_gate(repo, sha)
    return {
        "repo": repo,
        "pr": pr,
        "head_sha": sha,
        "base": REQUIRED_BASE,
        "required_check": REQUIRED_CHECK,
        "check_run_id": check.get("id"),
        "check_details_url": check.get("details_url"),
        "result": "PASS",
    }


def merge(repo: str, pr: int) -> dict[str, Any]:
    evidence = validate(repo, pr)
    sha = evidence["head_sha"]
    # Never use --admin or --auto. --match-head-commit closes the race between
    # provider evidence validation and the actual merge mutation.
    run(["gh", "pr", "merge", str(pr), "--repo", repo, "--merge", "--match-head-commit", sha])
    evidence["merged"] = True
    return evidence


def main() -> int:
    ap = argparse.ArgumentParser(description="Fail-closed ThemeFactory merge authority")
    ap.add_argument("pr", type=int)
    ap.add_argument("--repo")
    ap.add_argument("--check-only", action="store_true")
    args = ap.parse_args()
    repo = args.repo or repo_name()
    result = validate(repo, args.pr) if args.check_only else merge(repo, args.pr)
    print(json.dumps(result, indent=2, sort_keys=True))
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except MergeRefused as exc:
        print(f"theme-safe-merge: {exc}", file=sys.stderr)
        raise SystemExit(1)
