from __future__ import annotations

import json
import os
import pathlib
import sys
import time
from dataclasses import asdict, dataclass
from typing import Any

from .evaluator import IndependentEvaluator
from .harness import resolve_runtime_artifact_root, run_process_restart, valid_documents
from .models import AgentAction, State, canonical_hash
from .policy import ExternalApprovalService, PolicyGateway
from .runtime import ScriptedAgent, WorkflowRuntime
from .storage import Store
from .tools import ToolExecutor


@dataclass(frozen=True)
class RegressionResult:
    test_id: str
    run_id: str
    seed: int
    passed: bool
    assertion: str
    observed: dict[str, Any]
    latency_ms: float


def _result(test_id: str, seed: int, started: float, passed: bool, assertion: str, observed: dict[str, Any]) -> RegressionResult:
    return RegressionResult(test_id, f"{test_id.lower()}-{seed}", seed, passed, assertion, observed, round((time.perf_counter() - started) * 1000, 3))


def run_rg(test_id: str, seed: int = 101) -> RegressionResult:
    started = time.perf_counter(); run_id = f"{test_id.lower()}-{seed}"; case_id = f"case-{test_id.lower()}-{seed}"
    try:
        if test_id == "RG-01":
            class ExplodingAgent:
                def choose(self, context): raise RuntimeError("agent explosion")
            runtime = WorkflowRuntime(run_id=run_id, agent=ExplodingAgent())
            error = None
            try: runtime.execute(case_id, valid_documents())
            except Exception as exc: error = type(exc).__name__
            state = runtime.store.state(case_id, run_id)
            passed = state != State.COMPLETED.value and error == "RuntimeError" and bool(runtime.store.rows_for_case("decisions", case_id, run_id))
            return _result(test_id, seed, started, passed, "exploding agent forbids completion and records classification", {"state": state, "error": error, "agent_calls": runtime.budget.agent_calls})
        if test_id == "RG-02":
            class NullAgent:
                def choose(self, context): return None
            runtime = WorkflowRuntime(run_id=run_id, agent=NullAgent())
            try: runtime.execute(case_id, valid_documents())
            except Exception: pass
            state = runtime.store.state(case_id, run_id)
            return _result(test_id, seed, started, state != State.COMPLETED.value and runtime.budget.agent_calls > 0, "null action is observed and completion blocked", {"state": state, "agent_calls": runtime.budget.agent_calls})
        if test_id == "RG-03":
            runtime = WorkflowRuntime(run_id=run_id); result = runtime.execute(case_id, valid_documents()); approvals = runtime.store.rows_for_case("approvals", case_id, run_id)
            approved = sum(row["status"] == "APPROVED" for row in approvals)
            return _result(test_id, seed, started, result["state"] == State.APPROVAL_PENDING.value and approved == 0, "runtime-only execution creates pending request but no approval", {"state": result["state"], "approved": approved, "pending": len(approvals)})
        if test_id == "RG-04":
            runtime = WorkflowRuntime(run_id=run_id); runtime.store.create_case(case_id, State.READY_FOR_REVIEW.value, run_id=run_id)
            blocked = False
            try: runtime.transition(case_id, State.APPROVED, "direct")
            except Exception: blocked = True
            return _result(test_id, seed, started, blocked, "unapproved direct transition blocked", {"blocked": blocked})
        if test_id == "RG-05":
            states = [State.NEEDS_INFORMATION, State.MANUAL_REVIEW, State.PERMANENT_FAILURE, State.CANCELLED, State.COMPLETED]
            blocked = 0
            for index, state in enumerate(states):
                local_run, local_case = f"{run_id}-{index}", f"{case_id}-{index}"
                store = Store(); store.create_case(local_case, state.value, run_id=local_run); executor = ToolExecutor(store, PolicyGateway(store), local_run)
                schemas = {
                    "save_review_draft": {"case_id": local_case, "draft_version": 1},
                    "request_approval": {"case_id": local_case, "approval_id": f"a-{index}", "requested_tool": "apply_mock_update", "requested_args": {"case_id": local_case, "target": "synthetic-review-record", "value": "REVIEW_RECORDED", "expected_version": 0}},
                    "apply_mock_update": {"case_id": local_case, "target": "synthetic-review-record", "value": "REVIEW_RECORDED", "expected_version": 0},
                }
                for tool, args in schemas.items():
                    try: executor.execute(local_case, local_case, tool, args, f"r-{tool}", f"i-{tool}")
                    except Exception: blocked += 1
            return _result(test_id, seed, started, blocked == 15, "all three writes blocked in five terminal or waiting states", {"blocked": blocked, "expected": 15})
        if test_id in {"RG-06", "RG-07"}:
            rejected = 0
            for tool, schema in ToolExecutor.SCHEMAS.items():
                local_run, local_case = f"{run_id}-{tool}", f"{case_id}-{tool}"
                store = Store(); store.create_case(local_case, State.READY_FOR_REVIEW.value, run_id=local_run); executor = ToolExecutor(store, PolicyGateway(store), local_run)
                args = {name: (local_case if name == "case_id" else expected()) for name, expected in schema.items()}
                if test_id == "RG-06": args["unknown"] = True
                else:
                    first = next(iter(args)); args[first] = None
                try: executor.execute(local_case, local_case, tool, args, "request", "idem")
                except Exception: rejected += 1
            return _result(test_id, seed, started, rejected == len(ToolExecutor.SCHEMAS), "every tool rejects extra or invalid required fields", {"rejected": rejected, "effects": 0})
        if test_id == "RG-08":
            runtime_root, source_tree_written = resolve_runtime_artifact_root(pathlib.Path(__file__).resolve().parents[2])
            root = runtime_root / "rg08" / f"{seed}_{time.time_ns()}"; root.mkdir(parents=True, exist_ok=False); db = root / "store.db"
            with Store(str(db)) as first: first.create_case(case_id, State.RECEIVED.value, run_id=run_id)
            with Store(str(db)) as second: state = second.state(case_id, run_id)
            return _result(test_id, seed, started, state == State.RECEIVED.value, "schema init is repeat-safe and data persists outside immutable source", {"state": state, "database_ref": f"{root.name}/{db.name}", "runtime_artifact_root": str(runtime_root), "source_tree_written": source_tree_written})
        if test_id == "RG-09":
            process = run_process_restart(seed)
            return _result(test_id, seed, started, process["passed"], "subprocess resume uses different PIDs", process)
        if test_id == "RG-10":
            store = Store(); store.create_case("case-a", State.APPROVED.value, run_id=run_id); store.create_case("case-b", State.APPROVED.value, run_id=run_id)
            token, digest = canonical_hash("token-a"), canonical_hash("evaluation-a"); store.issue_completion_token(token, run_id, "case-a", digest)
            blocked = False
            try: store.consume_completion_token(token, run_id, "case-b", digest)
            except Exception: blocked = True
            return _result(test_id, seed, started, blocked, "case A completion token cannot complete case B", {"blocked": blocked})
        if test_id in {"RG-11", "RG-12"}:
            store = Store(); store.create_case("case-a", State.APPROVED.value, run_id=run_id); store.init_business("case-a", run_id, "synthetic-review-record")
            if test_id == "RG-11":
                store.create_case("case-b", State.APPROVED.value, run_id=run_id); store.init_business("case-b", run_id, "synthetic-review-record")
                store.add_effect("case-b", "apply_mock_update", "b-effect", canonical_hash({}), {"type": "mock_update"}, run_id)
            else:
                store.add_effect("case-a", "apply_mock_update", "a-effect", canonical_hash({}), {"type": "mock_update"}, run_id)
            evaluation = IndependentEvaluator().evaluate_precompletion(store, "case-a", run_id, {State.APPROVED.value})
            return _result(test_id, seed, started, not evaluation.passed and "business_target_not_reached" in evaluation.reasons, "cross-case or effect-only evidence cannot pass evaluator", {"reasons": evaluation.reasons})
        if test_id in {"RG-13", "RG-14", "RG-15", "RG-16", "RG-17", "RG-18"}:
            from scripts.build_evidence import build_semantic_traceability, dynamic_safety_metrics, issues_from_gates, platform_matrix, raw_digest, run_canary_rollback_drill, verify_raw_digest
            if test_id == "RG-13":
                rows = [{"run_id": "raw-1", "test_id": "RG-13", "passed": True}]; digest = raw_digest(rows); rows[0]["passed"] = False; passed = not verify_raw_digest(rows, digest); observed = {"tamper_detected": passed}
            elif test_id == "RG-14":
                rows = [{"run_id": "raw-1", "policy_violations": 1}]; metrics = dynamic_safety_metrics(rows); passed = metrics["policy_violations"] == 1; observed = metrics
            elif test_id == "RG-15":
                rows = [{"run_id": "raw-1", "test_id": "semantic-test", "passed": False, "assertion": "intentional failure"}]
                mappings = {"REQ": {"code_locations": ["src/reliability_core/runtime.py:1-20"], "test_ids": ["semantic-test"]}}
                records = build_semantic_traceability(mappings, rows); passed = records[0]["status"] == "FAIL"; observed = records[0]
            elif test_id == "RG-16":
                if os.environ.get("ROLLBACK_DRILL_CHILD") == "1": passed, observed = True, {"nested_child": True}
                else:
                    proof = run_canary_rollback_drill(restore_command=[sys.executable,"run_all.py","--child-smoke"]); passed, observed = proof["passed"], proof
            elif test_id == "RG-17":
                if os.environ.get("PLATFORM_MATRIX_CHILD") == "1": passed, observed = True, {"nested_child": True}
                else:
                    matrix = platform_matrix(""); passed = matrix["powershell"]["status"] == "SKIPPED_NONBLOCKING" and matrix["passed"]; observed = matrix
            else:
                issues = issues_from_gates({"G-X": False}, [{"run_id": "failed-run", "passed": False}]); passed = issues["issue_count"] > 0 and issues["verdict"] == "FAIL"; observed = issues
            return _result(test_id, seed, started, passed, "evidence pipeline mutation resistance or dynamic routing", observed)
        raise ValueError(test_id)
    except Exception as error:
        return _result(test_id, seed, started, False, f"unexpected:{type(error).__name__}:{error}", {"error": type(error).__name__})


def run_all_regressions(seed: int = 101) -> list[dict[str, Any]]:
    return [asdict(run_rg(f"RG-{number:02d}", seed)) for number in range(1, 19)]
