"""Tests for the evaluation framework."""

from __future__ import annotations

from unittest.mock import AsyncMock

from koan.agents.types import AgentResult
from koan.graph.evaluation import (
    DecisionEvaluator,
    EvaluationPolicy,
    EvaluationResult,
    ReviewStatus,
    record_review_decision,
)
from koan.graph.feedback import CalibrationResult, ConfidenceCalibrator


def _mock_calibrator(
    factor: float = 1.0,
    sample_count: int = 10,
    positive_rate: float = 0.8,
    negative_rate: float = 0.2,
    fork_rate: float = 0.0,
) -> ConfidenceCalibrator:
    cal = AsyncMock(spec=ConfidenceCalibrator)
    cal.get_calibration = AsyncMock(return_value=CalibrationResult(
        factor=factor,
        sample_count=sample_count,
        positive_rate=positive_rate,
        negative_rate=negative_rate,
        fork_rate=fork_rate,
    ))
    return cal


class TestDecisionEvaluator:
    async def test_high_confidence_auto_approved(self) -> None:
        cal = _mock_calibrator(factor=1.0, negative_rate=0.1)
        evaluator = DecisionEvaluator(cal)
        result = await evaluator.evaluate(raw_confidence=0.95, agent_id="bot")
        assert result.review_status == ReviewStatus.APPROVED
        assert result.calibrated_confidence == 0.95
        assert len(result.reasons) == 0

    async def test_low_calibration_flags(self) -> None:
        cal = _mock_calibrator(factor=0.7, negative_rate=0.2)
        evaluator = DecisionEvaluator(cal)
        result = await evaluator.evaluate(raw_confidence=0.95, agent_id="bot")
        # 0.95 * 0.7 = 0.665 — below auto_approve (0.8) but above flag (0.5)
        assert result.review_status == ReviewStatus.FLAGGED
        assert result.calibrated_confidence == 0.665

    async def test_very_low_calibration_rejects(self) -> None:
        cal = _mock_calibrator(factor=0.4)
        evaluator = DecisionEvaluator(cal)
        result = await evaluator.evaluate(raw_confidence=0.9, agent_id="bot")
        # 0.9 * 0.4 = 0.36 — below flag threshold (0.5)
        assert result.review_status == ReviewStatus.REJECTED

    async def test_high_negative_rate_flags(self) -> None:
        cal = _mock_calibrator(factor=1.0, negative_rate=0.5)
        evaluator = DecisionEvaluator(cal)
        result = await evaluator.evaluate(raw_confidence=0.95, agent_id="bot")
        # Confidence is fine (0.95) but negative rate (50%) exceeds max (30%)
        assert result.review_status == ReviewStatus.FLAGGED
        assert any("Negative feedback rate" in r for r in result.reasons)

    async def test_high_fork_rate_flags(self) -> None:
        cal = _mock_calibrator(factor=1.0, negative_rate=0.1, fork_rate=0.4)
        evaluator = DecisionEvaluator(cal)
        result = await evaluator.evaluate(raw_confidence=0.95, agent_id="bot")
        assert result.review_status == ReviewStatus.FLAGGED
        assert any("Fork/rewind rate" in r for r in result.reasons)

    async def test_no_history_low_confidence_flags(self) -> None:
        cal = _mock_calibrator(factor=1.0, sample_count=0)
        evaluator = DecisionEvaluator(cal)
        result = await evaluator.evaluate(raw_confidence=0.7, agent_id="bot")
        assert result.review_status == ReviewStatus.FLAGGED
        assert any("No feedback history" in r for r in result.reasons)

    async def test_no_history_high_confidence_approved(self) -> None:
        cal = _mock_calibrator(factor=1.0, sample_count=0)
        evaluator = DecisionEvaluator(cal)
        result = await evaluator.evaluate(raw_confidence=0.95, agent_id="bot")
        assert result.review_status == ReviewStatus.APPROVED

    async def test_custom_policy_thresholds(self) -> None:
        cal = _mock_calibrator(factor=1.0, negative_rate=0.1)
        policy = EvaluationPolicy(auto_approve_threshold=0.95)
        evaluator = DecisionEvaluator(cal, policy=policy)
        result = await evaluator.evaluate(raw_confidence=0.9, agent_id="bot")
        # 0.9 < 0.95 threshold → flagged
        assert result.review_status == ReviewStatus.FLAGGED

    async def test_metadata_includes_calibration(self) -> None:
        cal = _mock_calibrator(factor=0.85, sample_count=15)
        evaluator = DecisionEvaluator(cal)
        result = await evaluator.evaluate(raw_confidence=0.95, agent_id="bot")
        assert result.metadata["calibration_factor"] == 0.85
        assert result.metadata["sample_count"] == 15

    async def test_evaluate_result_convenience(self) -> None:
        cal = _mock_calibrator(factor=1.0)
        evaluator = DecisionEvaluator(cal)
        agent_result = AgentResult(
            output="Test output",
            agent_name="bot",
            metadata={"confidence": 0.9},
        )
        result = await evaluator.evaluate_result(agent_result)
        assert result.calibrated_confidence == 0.9

    async def test_evaluate_result_default_confidence(self) -> None:
        cal = _mock_calibrator(factor=1.0)
        evaluator = DecisionEvaluator(cal)
        agent_result = AgentResult(output="Test", agent_name="bot")
        result = await evaluator.evaluate_result(agent_result)
        assert result.raw_confidence == 1.0

    async def test_multiple_reasons(self) -> None:
        cal = _mock_calibrator(factor=0.6, negative_rate=0.5, fork_rate=0.4)
        evaluator = DecisionEvaluator(cal)
        result = await evaluator.evaluate(raw_confidence=0.95, agent_id="bot")
        # 0.95 * 0.6 = 0.57 → flagged
        # negative_rate 50% > 30% → another reason
        # fork_rate 40% > 25% → another reason
        assert len(result.reasons) >= 2


class TestReviewStatus:
    def test_values(self) -> None:
        assert ReviewStatus.APPROVED.value == "approved"
        assert ReviewStatus.FLAGGED.value == "flagged"
        assert ReviewStatus.REJECTED.value == "rejected"


class TestEvaluationPolicy:
    def test_defaults(self) -> None:
        p = EvaluationPolicy()
        assert p.auto_approve_threshold == 0.8
        assert p.flag_threshold == 0.5
        assert p.max_negative_rate == 0.3
        assert p.max_fork_rate == 0.25


class TestRecordReviewDecision:
    async def test_records_review_node(self) -> None:
        client = AsyncMock()
        client.execute_write = AsyncMock()
        await record_review_decision(
            client,
            run_id="run-1",
            reviewer="alice@example.com",
            approved=True,
            comment="Looks good",
        )
        assert client.execute_write.call_count == 1
        query = client.execute_write.call_args[0][0]
        assert "Review" in query
        assert "REVIEWED_BY" in query
        params = client.execute_write.call_args[0][1]
        assert params["reviewer"] == "alice@example.com"
        assert params["approved"] is True
