"""Tests for architect gate evaluation."""

from __future__ import annotations

from pathlib import Path

import pytest

from cleave.architect.config import ArchitectConfig
from cleave.architect.dag import DAGStore, Phase
from cleave.architect.gates import evaluate_gate


def _make_config(tmp_path: Path, **kwargs) -> ArchitectConfig:
    (tmp_path / ".git").mkdir(exist_ok=True)
    defaults = {"directive": "test", "repo_path": tmp_path}
    defaults.update(kwargs)
    return ArchitectConfig(**defaults)


def _make_dag(tmp_path: Path) -> DAGStore:
    store = DAGStore(tmp_path / "test.db")
    store.open()
    return store


class TestBudgetGate:
    def test_budget_exceeded_fails(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path, max_budget_usd=100.0)
        dag = _make_dag(tmp_path)
        dag.add_phase(Phase(id="p1", label="P1", status="completed", cost_usd=101.0))
        phase = dag.get_phase("p1")

        decision = evaluate_gate(phase, {}, config, dag)
        assert decision.decision == "fail"
        assert decision.gate_type == "budget"
        dag.close()

    def test_budget_at_limit_fails(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path, max_budget_usd=50.0)
        dag = _make_dag(tmp_path)
        dag.add_phase(Phase(id="p1", label="P1", status="completed", cost_usd=50.0))
        phase = dag.get_phase("p1")

        decision = evaluate_gate(phase, {}, config, dag)
        assert decision.decision == "fail"
        assert decision.gate_type == "budget"
        dag.close()

    def test_budget_under_limit_continues(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path, max_budget_usd=100.0)
        dag = _make_dag(tmp_path)
        dag.add_phase(Phase(id="p1", label="P1", status="completed", cost_usd=50.0))
        phase = dag.get_phase("p1")
        observation = {"git_diff_summary": {"files_changed": 5}, "test_results": {}}

        decision = evaluate_gate(phase, observation, config, dag)
        assert decision.decision == "continue"
        dag.close()


class TestFailureGate:
    def test_failed_phase_pauses(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path, circuit_breaker_threshold=3)
        dag = _make_dag(tmp_path)
        dag.add_phase(Phase(
            id="p1", label="P1", status="failed",
            error_message="Something broke", completed_at="2024-01-01T00:00:00",
        ))
        phase = dag.get_phase("p1")

        decision = evaluate_gate(phase, {}, config, dag)
        assert decision.decision == "pause"
        assert decision.gate_type == "failure"
        dag.close()

    def test_circuit_breaker_trips(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path, circuit_breaker_threshold=2)
        dag = _make_dag(tmp_path)
        dag.add_phase(Phase(
            id="p1", label="P1", status="failed", completed_at="2024-01-01T00:00:00",
        ))
        dag.add_phase(Phase(
            id="p2", label="P2", status="failed", completed_at="2024-01-01T01:00:00",
        ))
        phase = dag.get_phase("p2")

        decision = evaluate_gate(phase, {}, config, dag)
        assert decision.decision == "fail"
        assert decision.gate_type == "failure"
        assert "Circuit breaker" in decision.reason
        dag.close()


class TestStructuralGate:
    def test_tests_failed_pauses(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path)
        dag = _make_dag(tmp_path)
        dag.add_phase(Phase(id="p1", label="P1", status="completed"))
        phase = dag.get_phase("p1")
        observation = {
            "git_diff_summary": {"files_changed": 5},
            "test_results": {"ran": True, "passed": False, "summary": "3 failed"},
        }

        decision = evaluate_gate(phase, observation, config, dag)
        assert decision.decision == "pause"
        assert decision.gate_type == "structural"
        dag.close()

    def test_no_changes_pauses(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path)
        dag = _make_dag(tmp_path)
        dag.add_phase(Phase(id="p1", label="P1", status="completed"))
        phase = dag.get_phase("p1")
        observation = {
            "git_diff_summary": {"files_changed": 0},
            "test_results": {},
            "orchestrator_result": {"success": True},
        }

        decision = evaluate_gate(phase, observation, config, dag)
        assert decision.decision == "pause"
        assert decision.gate_type == "structural"
        dag.close()

    def test_success_continues(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path)
        dag = _make_dag(tmp_path)
        dag.add_phase(Phase(id="p1", label="P1", status="completed"))
        phase = dag.get_phase("p1")
        observation = {
            "git_diff_summary": {"files_changed": 10, "insertions": 200},
            "test_results": {"ran": True, "passed": True, "summary": "42 passed"},
        }

        decision = evaluate_gate(phase, observation, config, dag)
        assert decision.decision == "continue"
        assert decision.gate_type == "structural"
        dag.close()

    def test_no_tests_no_problem(self, tmp_path: Path) -> None:
        config = _make_config(tmp_path)
        dag = _make_dag(tmp_path)
        dag.add_phase(Phase(id="p1", label="P1", status="completed"))
        phase = dag.get_phase("p1")
        observation = {
            "git_diff_summary": {"files_changed": 3},
            "test_results": {"ran": False},
        }

        decision = evaluate_gate(phase, observation, config, dag)
        assert decision.decision == "continue"
        dag.close()


class TestGatePriority:
    def test_budget_takes_priority_over_failure(self, tmp_path: Path) -> None:
        """Budget gate should fire before failure gate."""
        config = _make_config(tmp_path, max_budget_usd=10.0)
        dag = _make_dag(tmp_path)
        dag.add_phase(Phase(
            id="p1", label="P1", status="failed",
            cost_usd=15.0, completed_at="2024-01-01T00:00:00",
        ))
        phase = dag.get_phase("p1")

        decision = evaluate_gate(phase, {}, config, dag)
        assert decision.gate_type == "budget"  # Budget fires first
        dag.close()
