from __future__ import annotations

import pathlib
import sys
import tempfile
import unittest

from reliability_core.harness import valid_documents
from reliability_core.models import State
from reliability_core.policy import PolicyGateway
from reliability_core.runtime import WorkflowRuntime
from reliability_core.storage import Store
from reliability_core.tools import FaultInjector, ToolExecutor


class CorrectionDefectTests(unittest.TestCase):
    def test_d01_agent_is_on_execution_path(self) -> None:
        class ExplodingAgent:
            def choose(self, payload):
                raise RuntimeError("agent invocation probe")
        runtime = WorkflowRuntime(run_id="d01")
        runtime.agent = ExplodingAgent()
        with self.assertRaises(RuntimeError):
            runtime.execute("case-d01", valid_documents())

    def test_d02_runtime_does_not_auto_approve(self) -> None:
        runtime = WorkflowRuntime(run_id="d02")
        result = runtime.execute("case-d02", valid_documents())
        self.assertIn(result["state"], {"READY_FOR_REVIEW", "APPROVAL_PENDING"})
        self.assertEqual(sum(row["status"] == "APPROVED" for row in runtime.store.rows("approvals")), 0)

    def test_d03_file_store_reopens(self) -> None:
        with tempfile.TemporaryDirectory() as directory:
            path = str(pathlib.Path(directory) / "runtime.db")
            first = Store(path)
            first.create_case("case-d03", State.RECEIVED.value, run_id="d03")
            first.db.close()
            second = Store(path)
            self.assertEqual(second.state("case-d03", "d03"), State.RECEIVED.value)
            second.db.close()

    def test_d04_approval_transition_guard(self) -> None:
        runtime = WorkflowRuntime(run_id="d04")
        runtime.store.create_case("case-d04", State.READY_FOR_REVIEW.value, run_id="d04")
        with self.assertRaises(Exception):
            runtime.transition("case-d04", State.APPROVED, "direct")

    def test_d05_terminal_and_waiting_states_block_writes(self) -> None:
        blocked_states = [State.NEEDS_INFORMATION, State.MANUAL_REVIEW, State.PERMANENT_FAILURE, State.CANCELLED, State.COMPLETED]
        for state in blocked_states:
            store = Store()
            run_id = f"d05-{state.value}"; store.create_case(f"case-{state.value}", state.value, run_id=run_id)
            tools = ToolExecutor(store, PolicyGateway(store), run_id)
            with self.assertRaises(Exception, msg=state.value):
                tools.execute(f"case-{state.value}", f"case-{state.value}", "save_review_draft", {"case_id": f"case-{state.value}"}, "request", "idem")

    def test_d06_strict_tool_schema(self) -> None:
        store = Store(); store.create_case("case-d06", State.READY_FOR_REVIEW.value, run_id="d06")
        tools = ToolExecutor(store, PolicyGateway(store), "d06")
        with self.assertRaises(Exception):
            tools.execute("case-d06", "case-d06", "save_review_draft", {"case_id": "case-d06", "unknown": True}, "request", "idem")

    def test_d07_retry_pending_and_backoff(self) -> None:
        runtime = WorkflowRuntime(run_id="d07", faults=FaultInjector(7, {"apply_mock_update": "transient_before_commit"}))
        waiting = runtime.execute("case-d07", valid_documents())
        from reliability_core.policy import ExternalApprovalService
        ExternalApprovalService(runtime.store, runtime.policy).decide(waiting["pending_approval_id"], "external-reviewer")
        result = runtime.continue_ready("case-d07", valid_documents())
        transitions = [row["to_state"] for row in runtime.store.rows("transitions")]
        self.assertIn(State.RETRY_PENDING.value, transitions)
        self.assertGreater(result.get("backoff_total", 0), 0)

    def test_d08_all_calls_are_metered(self) -> None:
        runtime = WorkflowRuntime(run_id="d08")
        result = runtime.execute("case-d08", valid_documents())
        self.assertGreater(result.get("agent_call_count", 0), 0)
        self.assertEqual(result.get("tool_call_count"), len({row["request_id"] for row in runtime.store.rows("journal") if row["request_id"]}))

    def test_d09_business_state_is_persisted(self) -> None:
        runtime = WorkflowRuntime(run_id="d09"); waiting = runtime.execute("case-d09", valid_documents())
        from reliability_core.policy import ExternalApprovalService
        ExternalApprovalService(runtime.store, runtime.policy).decide(waiting["pending_approval_id"], "external")
        result = runtime.continue_ready("case-d09", valid_documents()); business = runtime.store.business("case-d09", "d09", "synthetic-review-record")
        self.assertEqual((result["state"], business["value"], business["update_count"]), (State.COMPLETED.value, "REVIEW_RECORDED", 1))

    def test_d10_evaluator_queries_are_case_scoped(self) -> None:
        from reliability_core.evaluator import IndependentEvaluator
        store = Store(); store.create_case("case-a", State.APPROVED.value, run_id="run-a"); store.init_business("case-a", "run-a", "synthetic-review-record")
        store.create_case("case-a", State.APPROVED.value, run_id="run-b"); store.init_business("case-a", "run-b", "synthetic-review-record"); store.db.execute("UPDATE business_state SET value='REVIEW_RECORDED',update_count=1 WHERE case_id='case-a' AND run_id='run-b'"); store.db.commit()
        evaluation = IndependentEvaluator().evaluate_precompletion(store, "case-a", "run-a", {State.APPROVED.value})
        self.assertIn("business_target_not_reached", evaluation.reasons)

    def test_d11_completion_token_is_case_run_bound(self) -> None:
        store = Store(); digest = "d" * 64; store.issue_completion_token("token", "run-b", "case", digest)
        with self.assertRaises(Exception): store.consume_completion_token("token", "run-a", "case", digest)
        self.assertEqual(store.rows_for_case("completion_tokens", "case", "run-b")[0]["used"], 0)

    def test_d12_a0_to_a4_are_executable_profiles(self) -> None:
        from reliability_core.harness import run_capability_ablation
        rows = run_capability_ablation((101,), ("normal",)); by_profile = {row["profile"]: row for row in rows}
        self.assertEqual(by_profile["A0"]["final_state"], State.READY_FOR_REVIEW.value)
        self.assertEqual(by_profile["A4"]["final_state"], State.COMPLETED.value)
        self.assertEqual(by_profile["A3"]["completion_token_count"], 0)

    def test_d13_reports_aggregate_raw_records(self) -> None:
        from reliability_core.harness import aggregate_raw_metrics
        rows = [{"profile":"A","passed":True,"agent_call_count":2},{"profile":"A","passed":False,"agent_call_count":3}]
        report = aggregate_raw_metrics(rows); self.assertEqual((report["A"]["runs"], report["A"]["passes"], report["A"]["agent_calls"]), (2,1,5))

    def test_d14_semantic_traceability(self) -> None:
        from scripts.build_evidence import build_semantic_traceability
        with tempfile.TemporaryDirectory() as directory:
            root=pathlib.Path(directory); (root/"m.py").write_text("def real():\n    return True\n",encoding="utf-8")
            mapping={"REQ":{"code_locations":["m.py:1-2"],"symbols":["real"],"test_ids":["REAL","MISSING"],"assertion_ids":["REAL:primary","MISSING:primary"]}}
            raw=[{"test_id":"REAL","run_id":"r","passed":True,"assertion_id":"REAL:primary"}]
            self.assertEqual(build_semantic_traceability(mapping,raw,root)[0]["status"], "FAIL")

    def test_d15_rollback_uses_canary_patch(self) -> None:
        from scripts.build_evidence import ROOT, run_canary_rollback_drill
        with tempfile.TemporaryDirectory() as directory:
            proof=run_canary_rollback_drill(ROOT,pathlib.Path(directory)/"drill",[sys.executable,"run_all.py","--child-smoke"])
        self.assertTrue(proof["passed"]); self.assertTrue(proof["canary_applied"]); self.assertEqual(proof["before_tree_sha256"],proof["restored_tree_sha256"])

    def test_d16_platform_check_is_nonblocking(self) -> None:
        import reliability_core.regressions_r2 as r2
        original=r2.shutil.which
        try: r2.shutil.which=lambda _:None; observed=r2._linux_probe()
        finally: r2.shutil.which=original
        self.assertTrue(observed["passed"]); self.assertEqual(observed["docker_status"], "SKIPPED_NONBLOCKING")

    def test_d17_issues_follow_gate_results(self) -> None:
        from scripts.build_evidence import issues_from_gates
        issues=issues_from_gates({"PASSING":True,"FAILING":False},[{"run_id":"raw-fail","passed":False}])
        self.assertEqual((issues["verdict"],issues["issue_count"],issues["issues"][0]["gate"]),("FAIL",1,"FAILING"))

    def test_d18_approval_decision_states_exist(self) -> None:
        runtime=WorkflowRuntime(run_id="d18"); waiting=runtime.execute("case-d18",valid_documents()); from reliability_core.policy import ExternalApprovalService
        ExternalApprovalService(runtime.store,runtime.policy).decide(waiting["pending_approval_id"],"external",decision="REJECTED")
        with self.assertRaises(Exception): runtime.continue_ready("case-d18",valid_documents())
        approval=runtime.store.rows_for_case("approvals","case-d18","d18")[0]; business=runtime.store.business("case-d18","d18","synthetic-review-record")
        self.assertEqual((approval["status"],approval["used"],business["update_count"]),("REJECTED",0,0))

    def test_d19_checkpoint_contains_resume_state(self) -> None:
        store=Store(); runtime = WorkflowRuntime(store=store,run_id="d19")
        runtime.execute("case-d19", valid_documents(), stop_after_ready=True)
        checkpoint = runtime.store.checkpoint("d19", "case-d19")
        assert checkpoint is not None
        self.assertTrue({"budget", "deadline_at", "pending_approval_id", "input_hash", "journal_position"}.issubset(checkpoint))
        foreign=WorkflowRuntime(store=store,run_id="foreign-d19"); foreign.execute("case-d19",valid_documents(),stop_after_ready=True)
        with self.assertRaises(Exception): foreign.resume(checkpoint,valid_documents(),"case-d19")

    def test_d20_resume_revalidates_input_hash(self) -> None:
        runtime = WorkflowRuntime(run_id="d20")
        runtime.execute("case-d20", valid_documents(), stop_after_ready=True)
        changed = valid_documents(); changed[0]["fields"]["amount_units"]["value"] = 999
        with self.assertRaises(Exception):
            runtime.continue_ready("case-d20", changed)


if __name__ == "__main__":
    unittest.main()
