"""
Unit tests for HaltingPolicyEngine.

Tests halting decisions based on quality results, iteration limits,
marginal improvement, and resource budgets.
"""

import pytest

from markitect.prompts.quality.models import (
    GateType,
    HaltDecision,
    QualityPolicy,
    ValidationResult,
    ValidationStatus,
)
from markitect.prompts.quality.policy import HaltingPolicyEngine


def _make_result(status=ValidationStatus.PASS, score=1.0, gate_id="gate-1"):
    """Helper to create a ValidationResult."""
    return ValidationResult.create(
        gate_id=gate_id,
        gate_type=GateType.PATTERN,
        artifact_id="art-1",
        status=status,
        score=score,
    )


class TestQualityMetDecision:
    """Tests for quality met halting."""

    def test_all_pass_halts_quality_met(self):
        """Test all gates passing triggers quality met halt."""
        policy = QualityPolicy(max_iterations=5)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.PASS, 1.0)]
        record = engine.evaluate(results, iteration=1)

        assert record.decision == HaltDecision.HALT_QUALITY_MET
        assert "quality gates passed" in record.reason.lower()

    def test_required_gates_all_pass(self):
        """Test required gates all passing triggers quality met."""
        policy = QualityPolicy(
            max_iterations=5,
            required_gate_ids=["required-gate"],
        )
        engine = HaltingPolicyEngine(policy)

        results = [
            _make_result(ValidationStatus.PASS, 1.0, gate_id="required-gate"),
            _make_result(ValidationStatus.FAIL, 0.5, gate_id="optional-gate"),
        ]
        record = engine.evaluate(results, iteration=1)

        assert record.decision == HaltDecision.HALT_QUALITY_MET

    def test_required_gate_fails_continues(self):
        """Test required gate failing allows continuation."""
        policy = QualityPolicy(
            max_iterations=5,
            required_gate_ids=["required-gate"],
        )
        engine = HaltingPolicyEngine(policy)

        results = [
            _make_result(ValidationStatus.FAIL, 0.5, gate_id="required-gate"),
        ]
        record = engine.evaluate(results, iteration=1)

        assert record.decision == HaltDecision.CONTINUE


class TestIterationLimitDecision:
    """Tests for iteration limit halting."""

    def test_at_iteration_limit(self):
        """Test halting at iteration limit."""
        policy = QualityPolicy(max_iterations=3)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.FAIL, 0.5)]
        record = engine.evaluate(results, iteration=3)

        assert record.decision == HaltDecision.HALT_ITERATION_LIMIT
        assert record.iteration == 3

    def test_before_iteration_limit(self):
        """Test not halting before iteration limit."""
        policy = QualityPolicy(max_iterations=5)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.FAIL, 0.5)]
        record = engine.evaluate(results, iteration=2)

        assert record.decision == HaltDecision.CONTINUE


class TestBudgetExhaustedDecision:
    """Tests for resource budget exhaustion."""

    def test_budget_exhausted(self):
        """Test halting when budget is exhausted."""
        policy = QualityPolicy(max_iterations=10, resource_budget=5)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.FAIL, 0.5)]
        record = engine.evaluate(results, iteration=1, total_runs=5)

        assert record.decision == HaltDecision.HALT_BUDGET_EXHAUSTED

    def test_budget_not_exhausted(self):
        """Test not halting when budget remains."""
        policy = QualityPolicy(max_iterations=10, resource_budget=10)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.FAIL, 0.5)]
        record = engine.evaluate(results, iteration=1, total_runs=3)

        assert record.decision == HaltDecision.CONTINUE


class TestMarginalImprovementDecision:
    """Tests for marginal improvement halting."""

    def test_no_improvement_halts(self):
        """Test halting when improvement is below threshold."""
        policy = QualityPolicy(max_iterations=10, min_improvement=0.05)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.FAIL, 0.52)]
        record = engine.evaluate(
            results,
            iteration=2,
            score_history=[0.50],  # improvement: 0.02 < 0.05
        )

        assert record.decision == HaltDecision.HALT_NO_IMPROVEMENT

    def test_sufficient_improvement_continues(self):
        """Test continuing when improvement meets threshold."""
        policy = QualityPolicy(max_iterations=10, min_improvement=0.05)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.FAIL, 0.60)]
        record = engine.evaluate(
            results,
            iteration=2,
            score_history=[0.50],  # improvement: 0.10 >= 0.05
        )

        assert record.decision == HaltDecision.CONTINUE

    def test_first_iteration_no_history(self):
        """Test first iteration with no history continues."""
        policy = QualityPolicy(max_iterations=10, min_improvement=0.05)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.FAIL, 0.50)]
        record = engine.evaluate(results, iteration=1)

        assert record.decision == HaltDecision.CONTINUE


class TestPriorityOrder:
    """Tests for the priority order of halting checks."""

    def test_budget_checked_before_iteration(self):
        """Test budget exhaustion takes priority over iteration limit."""
        policy = QualityPolicy(max_iterations=3, resource_budget=2)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.FAIL, 0.5)]
        record = engine.evaluate(results, iteration=3, total_runs=2)

        assert record.decision == HaltDecision.HALT_BUDGET_EXHAUSTED

    def test_iteration_checked_before_quality(self):
        """Test iteration limit checked before quality met."""
        policy = QualityPolicy(max_iterations=2)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.PASS, 1.0)]
        # At iteration limit AND quality met — iteration limit wins
        # Actually quality is checked after iteration, so quality would be checked
        # But iteration 2 >= max 2 triggers first
        record = engine.evaluate(results, iteration=2)

        assert record.decision == HaltDecision.HALT_ITERATION_LIMIT


class TestHaltingRecord:
    """Tests for HaltingRecord attributes."""

    def test_record_has_scores(self):
        """Test halting record includes score history."""
        policy = QualityPolicy(max_iterations=5)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.PASS, 0.9)]
        record = engine.evaluate(
            results, iteration=2, score_history=[0.5]
        )

        assert record.scores == [0.5, 0.9]

    def test_record_to_dict(self):
        """Test halting record serialization."""
        policy = QualityPolicy(max_iterations=3)
        engine = HaltingPolicyEngine(policy)

        results = [_make_result(ValidationStatus.PASS, 1.0)]
        record = engine.evaluate(results, iteration=1)

        d = record.to_dict()
        assert d["decision"] == "halted_quality_met"
        assert d["iteration"] == 1
        assert d["max_iterations"] == 3
