#!/usr/bin/env python3
"""Mechanical authority for the synthetic ProofRail release scenario."""

from __future__ import annotations

import argparse
import hashlib
import json
from pathlib import Path
from typing import Any


def load(path: Path) -> Any:
    return json.loads(path.read_text(encoding="utf-8"))


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for block in iter(lambda: handle.read(65536), b""):
            digest.update(block)
    return digest.hexdigest()


def registry_errors(fixture: Path, registry: dict[str, Any]) -> list[str]:
    errors: list[str] = []
    artifacts = registry.get("artifacts")
    if not isinstance(artifacts, list) or not artifacts:
        return ["registry.artifacts_missing"]
    for item in artifacts:
        if not isinstance(item, dict):
            errors.append("registry.artifact_not_object")
            continue
        rel = item.get("path")
        expected = item.get("sha256")
        if not isinstance(rel, str) or not isinstance(expected, str):
            errors.append("registry.artifact_fields_invalid")
            continue
        path = fixture / rel
        if not path.is_file():
            errors.append(f"artifact.missing:{rel}")
            continue
        actual = sha256(path)
        if actual != expected:
            errors.append(f"artifact.sha256_mismatch:{rel}")
    return errors


def plan_errors(
    plan: dict[str, Any],
    manifest: dict[str, Any],
    mode: str,
    correction: dict[str, Any] | None,
) -> list[str]:
    errors: list[str] = []
    if plan.get("schema_version") != "proofrail.release-plan.v1":
        errors.append("plan.schema_version")
    if plan.get("scenario_id") != manifest.get("scenario_id"):
        errors.append("plan.scenario_id")

    evidence = plan.get("evidence_used")
    if not isinstance(evidence, list):
        errors.append("plan.evidence_used")
        evidence = []
    claims = {item.get("id"): item for item in plan.get("claims", []) if isinstance(item, dict)}
    release_claim = claims.get("release-ready", {})

    if mode == "base":
        expected_services = [item["name"] for item in manifest["services"]]
        actual_services = [item.get("service") for item in plan.get("release_steps", []) if isinstance(item, dict)]
        if plan.get("decision") != "GO":
            errors.append("base.decision_not_go")
        if actual_services != expected_services:
            errors.append("base.release_order")
        if plan.get("blockers") != []:
            errors.append("base.blockers_not_empty")
        if plan.get("supersedes") != []:
            errors.append("base.supersedes_not_empty")
        if release_claim.get("state") != "SUPPORTED":
            errors.append("base.release_claim_not_supported")
        for required in ("release-manifest", "registry", "health-snapshot"):
            if required not in evidence:
                errors.append(f"base.missing_evidence:{required}")
    else:
        correction_id = correction.get("evidence_id") if correction else None
        blocker_codes = {
            item.get("code") for item in plan.get("blockers", []) if isinstance(item, dict)
        }
        if plan.get("decision") != "HOLD":
            errors.append("corrected.decision_not_hold")
        if plan.get("release_steps") != []:
            errors.append("corrected.release_steps_not_empty")
        if "plan-v1" not in plan.get("supersedes", []):
            errors.append("corrected.plan_v1_not_superseded")
        if "ARTIFACT_REVOKED" not in blocker_codes:
            errors.append("corrected.revocation_blocker_missing")
        if correction_id not in evidence:
            errors.append("corrected.correction_evidence_missing")
        if release_claim.get("state") != "REFUTED":
            errors.append("corrected.release_claim_not_refuted")
        affected = set(plan.get("affected_services", []))
        if affected != {"worker", "console-ui"}:
            errors.append("corrected.affected_services")

    return errors


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--fixture", type=Path, required=True)
    parser.add_argument("--registry", type=Path, required=True)
    parser.add_argument("--plan", type=Path, required=True)
    parser.add_argument("--mode", choices=("base", "corrected"), required=True)
    args = parser.parse_args()

    fixture = args.fixture.resolve()
    registry = load(args.registry)
    manifest = load(fixture / "release_manifest.json")
    plan = load(args.plan)
    correction = load(fixture / "authority_correction.json") if args.mode == "corrected" else None

    errors = registry_errors(fixture, registry)
    errors.extend(plan_errors(plan, manifest, args.mode, correction))
    result = {
        "ok": not errors,
        "mode": args.mode,
        "registry_evidence": registry.get("evidence_id"),
        "plan": str(args.plan),
        "errors": errors,
    }
    print(json.dumps(result, indent=2, sort_keys=True))
    return 1 if errors else 0


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