"""Progressive validation state machine — the core of Tier 4.

Implements the GATE_SEQUENCE state machine that progressively expands
validation scope from LINE_ITEM/MONTH to FULL_REPORT/YEAR, with
automatic shrink-on-failure and fix-loop integration.
"""
from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from .contracts import (
    Discrepancy,
    GateStatus,
    GATE_SEQUENCE,
    GATE_TRANSITIONS,
    ParityGate,
    ParityTestResult,
    ProgressiveValidationState,
    ReportSpec,
    ValidationPeriod,
    ValidationScope,
    ValidationSlice,
    _new_id,
)
from .discrepancy_classifier import DiscrepancyClassifier
from .parity_test_executor import ParityTestExecutor

logger = logging.getLogger(__name__)


class ProgressiveRunner:
    """Run the progressive validation state machine.

    Gate sequence:
      LINE_ITEM/MONTH → LINE_GROUP/MONTH → FULL_REPORT/MONTH
      → FULL_REPORT/QUARTER → FULL_REPORT/YEAR

    Key principle: Agents CANNOT skip gates. Each gate must pass
    before the next can be attempted.
    """

    def __init__(
        self,
        tolerance_pct: float = 0.01,
        schema_prefix: str = "",
        query_executor: Any = None,
    ):
        self._executor = ParityTestExecutor(
            tolerance_pct=tolerance_pct,
            schema_prefix=schema_prefix,
            query_executor=query_executor,
        )
        self._classifier = DiscrepancyClassifier()
        self._states: Dict[str, ProgressiveValidationState] = {}

    def initialize(self, spec: ReportSpec) -> ProgressiveValidationState:
        """Initialize the state machine for a report spec."""
        gates: List[ParityGate] = []
        prev_gate_id = ""

        for i, (scope, period) in enumerate(GATE_SEQUENCE):
            gate = ParityGate(
                scope=scope,
                period=period,
                status=GateStatus.PENDING if i == 0 else GateStatus.BLOCKED,
                predecessor_gate_id=prev_gate_id,
            )
            gates.append(gate)
            prev_gate_id = gate.gate_id

        state = ProgressiveValidationState(
            spec=spec,
            gates=gates,
            current_gate_index=0,
            overall_status="running",
        )
        self._states[state.state_id] = state
        return state

    def run_next_gate(
        self,
        state_id: str,
        actual_values: Optional[Dict[str, float]] = None,
    ) -> Dict[str, Any]:
        """Run the next pending/unblocked gate.

        Args:
            state_id: State machine ID
            actual_values: {line_item_id: actual_value} for tests
        """
        state = self._states.get(state_id)
        if not state:
            return {"error": f"State {state_id} not found"}

        if state.overall_status in ("certified", "failed"):
            return {"status": state.overall_status, "message": "Validation already complete"}

        idx = state.current_gate_index
        if idx >= len(state.gates):
            return {"error": "All gates exhausted"}

        gate = state.gates[idx]

        # Validate transition
        if gate.status not in (GateStatus.PENDING, GateStatus.FIXED):
            return {"error": f"Gate {gate.gate_id} is {gate.status.value}, cannot run"}

        # Transition to RUNNING
        self._transition_gate(gate, GateStatus.RUNNING)

        # Build validation slice
        validation_slice = ValidationSlice(
            scope=gate.scope,
            period=gate.period,
        )

        # Execute tests
        results = self._executor.execute_slice(state.spec, validation_slice, actual_values)
        gate.test_results = results

        # Check results
        failures = [r for r in results if r.status == "fail"]

        if not failures:
            self._transition_gate(gate, GateStatus.PASSED)
            gate.passed_at = datetime.now(timezone.utc).isoformat()

            # Advance to next gate
            state.current_gate_index = idx + 1
            if state.current_gate_index < len(state.gates):
                next_gate = state.gates[state.current_gate_index]
                self._transition_gate(next_gate, GateStatus.PENDING)
            else:
                state.overall_status = "certified"

            return {
                "status": "passed",
                "gate": gate.model_dump(),
                "next_gate_index": state.current_gate_index,
                "overall_status": state.overall_status,
            }
        else:
            self._transition_gate(gate, GateStatus.FAILED)
            gate.failed_at = datetime.now(timezone.utc).isoformat()

            # Classify discrepancies
            discrepancies = self._classifier.classify_batch(results)
            gate.discrepancies = discrepancies

            return {
                "status": "failed",
                "gate": gate.model_dump(),
                "failures": len(failures),
                "discrepancies": [d.model_dump() for d in discrepancies],
                "overall_status": state.overall_status,
            }

    def apply_fix(
        self,
        state_id: str,
        gate_index: int,
    ) -> Dict[str, Any]:
        """Mark a gate as fixed and ready for re-test."""
        state = self._states.get(state_id)
        if not state:
            return {"error": f"State {state_id} not found"}

        if gate_index >= len(state.gates):
            return {"error": f"Gate index {gate_index} out of range"}

        gate = state.gates[gate_index]
        if gate.status != GateStatus.FAILED:
            return {"error": f"Gate {gate.gate_id} is {gate.status.value}, not failed"}

        if gate.fix_attempts >= gate.max_fix_attempts:
            state.overall_status = "failed"
            return {
                "error": f"Max fix attempts ({gate.max_fix_attempts}) reached",
                "overall_status": "failed",
            }

        gate.fix_attempts += 1
        self._transition_gate(gate, GateStatus.FIXED)

        return {
            "status": "fixed",
            "gate_id": gate.gate_id,
            "fix_attempts": gate.fix_attempts,
            "max_fix_attempts": gate.max_fix_attempts,
        }

    def reset_gate(
        self,
        state_id: str,
        gate_index: int,
    ) -> Dict[str, Any]:
        """Reset a failed gate for re-testing (after manual fix)."""
        state = self._states.get(state_id)
        if not state:
            return {"error": f"State {state_id} not found"}

        if gate_index >= len(state.gates):
            return {"error": f"Gate index {gate_index} out of range"}

        gate = state.gates[gate_index]
        if gate.status not in (GateStatus.FAILED, GateStatus.FIXED):
            return {"error": f"Can only reset failed/fixed gates, got {gate.status.value}"}

        gate.status = GateStatus.PENDING
        gate.test_results = []
        gate.discrepancies = []
        gate.failed_at = ""
        state.current_gate_index = gate_index
        state.overall_status = "running"

        return {"status": "reset", "gate_id": gate.gate_id}

    def get_status(self, state_id: str) -> Dict[str, Any]:
        """Get current state machine status."""
        state = self._states.get(state_id)
        if not state:
            return {"error": f"State {state_id} not found"}

        gates_summary = []
        for i, gate in enumerate(state.gates):
            gates_summary.append({
                "index": i,
                "gate_id": gate.gate_id,
                "scope": gate.scope.value,
                "period": gate.period.value,
                "status": gate.status.value,
                "tests_run": len(gate.test_results),
                "failures": sum(1 for r in gate.test_results if r.status == "fail"),
                "discrepancies": len(gate.discrepancies),
                "fix_attempts": gate.fix_attempts,
            })

        return {
            "state_id": state.state_id,
            "report_name": state.spec.report_name,
            "overall_status": state.overall_status,
            "current_gate_index": state.current_gate_index,
            "total_gates": len(state.gates),
            "gates": gates_summary,
        }

    def list_runs(self) -> List[Dict[str, Any]]:
        """List all validation runs."""
        return [
            {
                "state_id": s.state_id,
                "report_name": s.spec.report_name,
                "overall_status": s.overall_status,
                "current_gate": s.current_gate_index,
                "total_gates": len(s.gates),
            }
            for s in self._states.values()
        ]

    def run_progressive(
        self,
        spec: ReportSpec,
        all_actual_values: Optional[Dict[str, float]] = None,
    ) -> ProgressiveValidationState:
        """Convenience: initialize and run all gates until failure or certification.

        Args:
            spec: Report spec to validate
            all_actual_values: Pre-computed actual values for all tests
        """
        state = self.initialize(spec)

        while state.overall_status == "running":
            result = self.run_next_gate(state.state_id, all_actual_values)
            if result.get("status") == "failed":
                break
            if state.current_gate_index >= len(state.gates):
                break

        return state

    @staticmethod
    def _transition_gate(gate: ParityGate, new_status: GateStatus) -> None:
        """Validate and apply a gate status transition."""
        valid = GATE_TRANSITIONS.get(gate.status, [])
        if new_status not in valid and gate.status != new_status:
            logger.warning(
                "Invalid gate transition: %s -> %s (valid: %s)",
                gate.status.value, new_status.value, [v.value for v in valid],
            )
        gate.status = new_status
