from __future__ import annotations

import unittest

from reliability_core.documents import OfflineDocumentAdapter
from reliability_core.evaluator import IndependentEvaluator
from reliability_core.harness import aggregate_comparison, valid_documents
from reliability_core.models import AgentAction, Budget, CheckpointIncompatible, PolicyError, State, ValidationError
from reliability_core.policy import HumanApprovalService, PolicyGateway
from reliability_core.runtime import WorkflowRuntime, classify_error
from reliability_core.storage import Store
from reliability_core.tools import ToolExecutor


class StateAndCheckpointTests(unittest.TestCase):
    def test_wfl_01_illegal_transition(self) -> None:
        runtime = WorkflowRuntime(run_id="illegal")
        runtime.store.create_case("case-alpha", State.RECEIVED.value, run_id="illegal")
        with self.assertRaises(ValidationError):
            runtime.transition("case-alpha", State.COMPLETED, "natural language claim")

    def test_wfl_02_completion_requires_evaluator(self) -> None:
        runtime = WorkflowRuntime(run_id="guard")
        runtime.store.create_case("case-alpha", State.APPROVED.value, run_id="guard")
        with self.assertRaises(ValidationError):
            runtime.transition("case-alpha", State.COMPLETED, "unverified")

    def test_wfl_03_checkpoint_fields(self) -> None:
        runtime = WorkflowRuntime(run_id="checkpoint")
        runtime.execute("case-alpha", valid_documents(), stop_after_ready=True)
        checkpoint = runtime.store.checkpoint("checkpoint", "case-alpha")
        self.assertIsNotNone(checkpoint)
        self.assertTrue({"run_id", "case_id", "state", "completed_steps", "tool_call_ids", "external_effects", "versions", "budget", "deadline_at", "pending_approval_id", "input_hash", "journal_position"}.issubset(checkpoint))

    def test_wfl_04_version_mismatch(self) -> None:
        runtime = WorkflowRuntime(run_id="mismatch")
        runtime.execute("case-alpha", valid_documents(), stop_after_ready=True)
        checkpoint = runtime.store.checkpoint("mismatch", "case-alpha")
        assert checkpoint is not None
        checkpoint["versions"]["prompt"] = "changed"
        with self.assertRaises(CheckpointIncompatible):
            runtime.resume(checkpoint, valid_documents())


class DocumentAndAgentTests(unittest.TestCase):
    def test_doc_01_adapter_contract(self) -> None:
        result = OfflineDocumentAdapter().adapt(valid_documents())
        self.assertEqual(result.outcome, "VALID")

    def test_doc_02_provenance(self) -> None:
        result = OfflineDocumentAdapter().adapt(valid_documents())
        self.assertTrue(all(field.document_id and field.location for field in result.fields.values()))

    def test_doc_03_variants(self) -> None:
        stale = valid_documents()
        stale[1]["fields"]["submitted_day"]["value"] = 1
        wrong_unit = valid_documents()
        wrong_unit[1]["fields"]["currency_code"]["value"] = "UNKNOWN"
        malformed = [{"document_id": "doc-alpha", "fields": {"subject_ref": {"value": "x", "location": "p1", "extra": True}}}]
        kinds = {item["kind"] for docs in (stale, wrong_unit, malformed) for item in OfflineDocumentAdapter().adapt(docs).problems}
        self.assertTrue({"STALE", "UNIT_MISMATCH", "STRUCTURE"}.issubset(kinds))

    def test_agt_01_replaceable_adapter(self) -> None:
        action = AgentAction.from_payload({"tool": "get_case", "arguments": {}, "request_id": "r"}, {"get_case"})
        self.assertEqual(action.tool, "get_case")

    def test_agt_02_no_arbitrary_tool(self) -> None:
        with self.assertRaises(ValidationError):
            AgentAction.from_payload({"tool": "shell", "arguments": {}, "request_id": "r"}, {"get_case"})


class ToolPolicyAndEvaluationTests(unittest.TestCase):
    def setUp(self) -> None:
        self.store = Store()
        self.store.create_case("case-alpha", State.READY_FOR_REVIEW.value, run_id="tool-test")
        self.store.init_business("case-alpha", "tool-test", "synthetic-review-record")
        self.policy = PolicyGateway(self.store)
        self.tools = ToolExecutor(self.store, self.policy, "tool-test")

    def test_tool_01_six_tool_contracts(self) -> None:
        for tool in ("get_case", "get_policy", "get_result"):
            result = self.tools.execute("case-alpha", "case-alpha", tool, {"case_id": "case-alpha"}, f"r-{tool}")
            self.assertIn("type", result)
        result = self.tools.execute("case-alpha", "case-alpha", "save_review_draft", {"case_id": "case-alpha", "draft_version": 1}, "r-draft", "i-draft")
        self.assertIn("type", result)
        args = {"target": "synthetic-review-record", "case_id": "case-alpha", "value": "REVIEW_RECORDED", "expected_version": 0}
        request = {"case_id": "case-alpha", "approval_id": "request-approval", "requested_tool": "apply_mock_update", "requested_args": args}
        self.assertIn("type", self.tools.execute("case-alpha", "case-alpha", "request_approval", request, "r-request", "i-request"))
        HumanApprovalService(self.store, self.policy).grant("approval", "case-alpha", "apply_mock_update", args, run_id="tool-test")
        self.store.set_state("case-alpha", State.READY_FOR_REVIEW.value, State.APPROVED.value, "unit setup", "tool-test")
        self.assertTrue(self.tools.execute("case-alpha", "case-alpha", "apply_mock_update", args, "r-apply", "i-apply", "approval")["recorded"])

    def test_tool_02_idempotency_key_argument_binding(self) -> None:
        args = {"case_id": "case-alpha", "draft_version": 1}
        self.tools.execute("case-alpha", "case-alpha", "save_review_draft", args, "r1", "same")
        self.tools.execute("case-alpha", "case-alpha", "save_review_draft", args, "r2", "same")
        self.assertEqual(self.store.effect_count(), 1)
        with self.assertRaises(ValidationError):
            self.tools.execute("case-alpha", "case-alpha", "save_review_draft", {"case_id": "case-alpha", "draft_version": 2}, "r3", "same")

    def test_tool_03_journal_phases(self) -> None:
        self.tools.execute("case-alpha", "case-alpha", "save_review_draft", {"case_id": "case-alpha", "draft_version": 1}, "r", "i")
        phases = {row["phase"] for row in self.store.rows("journal")}
        self.assertTrue({"STARTED", "COMMITTED", "RESPONSE_RECEIVED", "COMPLETED"}.issubset(phases))

    def test_pol_hum_sec_binding(self) -> None:
        args = {"target": "synthetic-review-record", "case_id": "case-alpha", "value": "REVIEW_RECORDED", "expected_version": 0}
        HumanApprovalService(self.store, self.policy).grant("approval", "case-alpha", "apply_mock_update", args, run_id="tool-test")
        self.store.set_state("case-alpha", State.READY_FOR_REVIEW.value, State.APPROVED.value, "unit setup", "tool-test")
        with self.assertRaises(PolicyError):
            self.tools.execute("case-alpha", "case-alpha", "apply_mock_update", dict(args, target="changed"), "r", "i", "approval")

    def test_eval_01_02_independent_history_assertions(self) -> None:
        evaluation = IndependentEvaluator().evaluate_terminal(self.store, "case-alpha", State.READY_FOR_REVIEW, "tool-test")
        self.assertTrue(evaluation.passed)
        self.assertEqual(evaluation.policy_violations, 0)

    def test_obs_01_structured_decision_schema(self) -> None:
        payload = {"action": "hold", "evidence": [], "policy": "policy-v1", "verification": "safe", "next_state": "MANUAL_REVIEW", "failure_class": None}
        self.store.add_decision("run", payload)
        self.assertEqual(len(self.store.rows("decisions")), 1)
        with self.assertRaises(ValueError):
            self.store.add_decision("run", {"raw_reasoning": "not allowed"})


class LimitsIsolationAndReportsTests(unittest.TestCase):
    def test_env_03_store_isolation(self) -> None:
        first, second = Store(), Store()
        first.create_case("case-alpha", State.RECEIVED.value)
        with self.assertRaises(KeyError):
            second.state("case-alpha")

    def test_err_01_classifier(self) -> None:
        self.assertEqual(classify_error(ValidationError("x")), "INPUT_VALIDATION")
        self.assertEqual(classify_error(PolicyError("x")), "POLICY")

    def test_err_02_budget(self) -> None:
        budget = Budget(max_steps=1)
        budget.step()
        with self.assertRaises(PolicyError):
            budget.step()

    def test_eval_04_report_recomputed(self) -> None:
        rows = [{"version": "a", "passed": True, "latency_ms": 1}, {"version": "a", "passed": False, "latency_ms": 3}]
        report = aggregate_comparison(rows)
        self.assertEqual(report["a"]["pass_rate"], 0.5)
        self.assertEqual(report["a"]["mean_latency_ms"], 2)


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