"""Run a synthetic scenario against the unchanged ChangeFront service.

All actors and inputs are scripted fixtures. No LLM or real human participates.
The output is a recorded execution, not a live client or adoption demonstration.
"""
from __future__ import annotations

import argparse
import hashlib
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
from datetime import datetime, timezone

ROOT = Path(__file__).resolve().parents[1]
sys.dont_write_bytecode = True
sys.path.insert(0, str(ROOT / "src"))

from changefront_ref.errors import ApprovalBoundaryError, ConflictError  # noqa: E402
from changefront_ref.mcp import PROTOCOL_VERSION  # noqa: E402
from changefront_ref.service import Actor, AI_ACTOR, ChangefrontService  # noqa: E402
from changefront_ref.storage import Database  # noqa: E402


def canonical(value: object) -> bytes:
    return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("ascii")


def engine_hash() -> str:
    digest = hashlib.sha256()
    for path in sorted((ROOT / "src").rglob("*")):
        if path.is_file() and "__pycache__" not in path.parts:
            digest.update(path.relative_to(ROOT).as_posix().encode() + b"\0" + path.read_bytes() + b"\0")
    return digest.hexdigest()


def source_commit() -> str | None:
    result = subprocess.run(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True, capture_output=True)
    return result.stdout.strip() if result.returncode == 0 else None


def run() -> dict:
    checks = []
    steps = []
    counts: dict[str, int] = {}

    def check(name: str, condition: bool) -> None:
        checks.append({"name": name, "passed": condition})
        if not condition:
            raise AssertionError(name)

    def next_id(prefix: str) -> str:
        counts[prefix] = counts.get(prefix, 0) + 1
        return f"{prefix}-{counts[prefix]:04d}"

    with tempfile.TemporaryDirectory(prefix="changefront-walkthrough-") as temp:
        db = Path(temp) / "scenario.db"
        service = ChangefrontService(Database(db), lambda: datetime(2026, 9, 14, 12, tzinfo=timezone.utc), next_id)
        service.initialize()
        service.register_source({
            "id": "demo-policy", "name": "Synthetic review policy", "source_type": "official",
            "authority_tier": 1, "allowed_domain": "policy.example", "description": "Invented input for this test, not a real policy.", "public_safe": True,
        })
        payload = {
            "stable_key": "review-interval", "title": "Synthetic review interval",
            "claim": "Review this synthetic control every 90 days.", "source_id": "demo-policy",
            "source_uri": "https://policy.example/review", "observed_at": "2026-09-01T00:00:00Z",
            "valid_from": "2026-09-01T00:00:00Z", "expires_at": "2027-09-01T00:00:00Z",
            "applicability": "Invented portfolio test control", "verification_state": "verified",
            "confidence": 0.95, "excerpt": "Review every 90 days.", "metadata": {"synthetic": True},
        }
        v1 = service.ingest_evidence(payload)
        rec = service.create_recommendation({
            "title": "Schedule a 90-day review", "rationale": "The synthetic v1 policy specifies a 90-day interval.",
            "proposed_action": "Add a 90-day checkpoint to the example runbook.", "confidence": 0.9,
            "evidence_version_ids": [v1["id"]],
        })
        proposal = service.create_proposal({
            "document_key": "example-runbook", "base_revision": "r1", "title": "Add the review checkpoint",
            "rationale": "Follow the synthetic v1 policy.", "proposed_markdown": "Review the example control every 90 days.",
            "evidence_version_ids": [v1["id"]],
        })

        def snapshot(key: str, title: str, explanation: str, **extra: object) -> None:
            steps.append({"key": key, "title": title, "explanation": explanation,
                          "evidence": service.list_evidence(), "recommendation": service.get_recommendation(rec["id"]),
                          "proposal": service.get_proposal(proposal["id"]), **extra})

        service.submit_recommendation(rec["id"])
        service.submit_proposal(proposal["id"])
        check("Both items entered review citing evidence version 1", service.get_recommendation(rec["id"])["state"] == "in_review" and service.get_proposal(proposal["id"])["state"] == "in_review")
        snapshot("submitted", "The recommendation cites a specific source version", "A scripted draft and document proposal enter review with a direct link to version 1. The verified designation is supplied by the fixture, not established by fact-checking.")

        denied = None
        try:
            service.decide_recommendation(rec["id"], "approved", "Scripted AI-role attempt", AI_ACTOR, True)
        except ApprovalBoundaryError as error:
            denied = {"error": type(error).__name__, "message": str(error)}
        check("AI-role approval attempt was denied", denied is not None and service.get_recommendation(rec["id"])["state"] == "in_review")
        snapshot("denied", "The AI-role approval attempt is rejected", "The script deliberately calls the service's decision method as an AI actor. It receives an approval-boundary error; the recommendation stays in review.", denied_attempt=denied)

        reviewer = Actor("SCRIPTED REVIEWER FIXTURE", "human", "human_reviewer")
        service.decide_recommendation(rec["id"], "approved", "Synthetic reviewer-role test, not a real person's acceptance.", reviewer, True)
        service.decide_proposal(proposal["id"], "approved", "Synthetic reviewer-role test, not a real person's acceptance.", reviewer, True)
        check("Scripted reviewer approval records a decision but does not apply the document", service.get_proposal(proposal["id"])["state"] == "approved" and service.get_proposal(proposal["id"])["applied"] is False)
        snapshot("approved", "Approval records a decision, not a document change", "The script supplies the human-reviewer role and explicit confirmation. Both items become approved, but the proposal remains unapplied. This tests an authorization contract, not authenticated human identity.")

        v2 = service.ingest_evidence({**payload, "claim": "Review this synthetic control every 30 days.", "excerpt": "Review every 30 days."})
        check("New evidence invalidates previously approved dependent work", service.get_recommendation(rec["id"])["state"] == "invalidated" and service.get_proposal(proposal["id"])["state"] == "invalidated")
        check("The original evidence content is preserved", service.get_evidence(v1["id"])["claim"] == payload["claim"] and service.get_evidence(v1["id"])["effective_state"] == "superseded" and v2["version_no"] == 2)
        blocked = None
        try:
            service.submit_recommendation(rec["id"])
        except ConflictError as error:
            blocked = {"error": type(error).__name__, "message": str(error)}
        check("Old recommendation cannot be resubmitted with superseded evidence", blocked is not None)
        snapshot("invalidated", "A source change invalidates the approved work", "Version 2 changes the interval from 90 to 30 days. The original source and decision remain in history, while both dependent items become invalidated. Resubmitting the old recommendation is blocked.", blocked_resubmission=blocked)

        request = {"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {"_meta": {
            "io.modelcontextprotocol/protocolVersion": PROTOCOL_VERSION,
            "io.modelcontextprotocol/clientInfo": {"name": "scripted-portfolio-check", "version": "1.0"},
            "io.modelcontextprotocol/clientCapabilities": {},
        }}}
        environment = {**os.environ, "PYTHONPATH": str(ROOT / "src"), "PYTHONDONTWRITEBYTECODE": "1"}
        process = subprocess.run([sys.executable, "-m", "changefront_ref", "--db", str(db), "mcp"], input=json.dumps(request) + "\n", capture_output=True, text=True, env=environment, cwd=ROOT, timeout=20, check=True)
        response = json.loads(process.stdout.strip())
        names = [tool["name"] for tool in response["result"]["tools"]]
        check("Real MCP subprocess exposes tools but no approval or apply operation", len(names) > 0 and not any(any(word in name for word in ["approve", "decide", "apply"]) for name in names))
        audit = service.verify_audit_chain()
        check("Recorded audit chain validates", audit["valid"] is True)
        events = list(reversed(service.list_audit_events()))

    result = {"steps": steps, "checks": checks, "audit": audit, "audit_events": events,
              "mcp": {"transport": "real local stdio subprocess", "request": request, "response": response}}
    return {"schema_version": 1, "title": "ChangeFront recorded execution",
            "generated_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
            "scenario_clock": "2026-09-14T12:00:00Z",
            "scope": "Synthetic scripted test. No LLM call, no real human reviewer, no employer data, and no external document write.",
            "source": {"repository": "https://github.com/Robiswell/ChangeFront", "commit": source_commit(), "engine_sha256": engine_hash(), "runner_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest()},
            "python": sys.version.split()[0], "execution_sha256": hashlib.sha256(canonical(result)).hexdigest(), **result}


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--output", type=Path, default=ROOT / "runtime" / "portfolio-walkthrough.json")
    args = parser.parse_args()
    report = run()
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    print(f"PASS: {len(report['checks'])} scenario assertions; {report['audit']['events_checked']} audit events. Recorded {args.output.name}")
