from __future__ import annotations

from dataclasses import dataclass

from .models import State, canonical_hash
from .storage import Store


@dataclass(frozen=True)
class Evaluation:
    passed: bool
    reasons: tuple[str, ...]
    duplicate_side_effects: int
    policy_violations: int
    schema_violations: int = 0
    evaluation_digest: str = ""


class IndependentEvaluator:
    """Consumes persisted state and history only; it never calls the agent."""

    def evaluate_precompletion(self, store: Store, case_id: str, run_id: str, required_transitions: set[str], target: str = "synthetic-review-record", target_value: str = "REVIEW_RECORDED") -> Evaluation:
        reasons: list[str] = []
        state = store.state(case_id, run_id)
        transitions = store.rows_for_case("transitions", case_id, run_id)
        seen = {row["to_state"] for row in transitions}
        if state != State.APPROVED.value:
            reasons.append("state_not_approved")
        if not required_transitions.issubset(seen):
            reasons.append("required_transition_missing")
        approvals = store.rows_for_case("approvals", case_id, run_id)
        valid_approvals = [row for row in approvals if row["status"] == "APPROVED" and row["used"] == 1]
        if len(valid_approvals) != 1:
            reasons.append("valid_single_use_approval_missing")
        effects = store.rows_for_case("effects", case_id, run_id)
        apply_effects = [row for row in effects if row["tool"] == "apply_mock_update"]
        if len(apply_effects) != 1:
            reasons.append("mock_update_missing")
        business = store.business(case_id, run_id, target)
        if not business or business["value"] != target_value or business["update_count"] != 1:
            reasons.append("business_target_not_reached")
        duplicates = store.duplicate_effect_count(case_id, run_id)
        if duplicates:
            reasons.append("duplicate_side_effect")
        journals = store.rows_for_case("journal", case_id, run_id)
        policy_violations = sum("PolicyError" in row["detail"] or "AuthorizationError" in row["detail"] for row in journals)
        schema_violations = sum("ValidationError" in row["detail"] for row in journals)
        if policy_violations:
            reasons.append("policy_violation")
        payload = {"case_id": case_id, "run_id": run_id, "state": state, "transitions": sorted(seen), "approval_ids": [row["id"] for row in valid_approvals], "effect_ids": [row["id"] for row in apply_effects], "business": business, "reasons": reasons}
        digest = canonical_hash(payload)
        return Evaluation(not reasons, tuple(reasons), duplicates, policy_violations, schema_violations, digest)

    def evaluate_terminal(self, store: Store, case_id: str, expected: State, run_id: str | None = None) -> Evaluation:
        run = run_id or store._resolve_run(case_id, None)
        reasons = [] if store.state(case_id, run) == expected.value else ["unexpected_final_state"]
        duplicates = store.duplicate_effect_count(case_id, run)
        if duplicates:
            reasons.append("duplicate_side_effect")
        digest = canonical_hash({"case_id": case_id, "run_id": run, "expected": expected.value, "reasons": reasons})
        return Evaluation(not reasons, tuple(reasons), duplicates, 0, 0, digest)
