"""Tests for the historical flaky predictor."""

import pytest

from api.ai.flaky_predictor import HistoricalFlakyPredictor


class TestHistoricalFlakyPredictor:
    """Test the history-blended predictor."""

    @pytest.mark.asyncio
    async def test_no_history_returns_code_prediction(self):
        """Without history, should return code-only prediction."""
        async def get_history(test_name, project_id):
            return None

        predictor = HistoricalFlakyPredictor(db_get_test_history=get_history)
        result = await predictor.predict(
            test_code="def test_example():\n    time.sleep(5)\n    assert True",
            test_name="test_example",
        )
        # Should have a prediction based on code patterns only
        assert result.test_name == "test_example"
        assert result.risk_score >= 0

    @pytest.mark.asyncio
    async def test_insufficient_history_returns_code_prediction(self):
        """With < 10 runs, should return code-only prediction."""
        async def get_history(test_name, project_id):
            return {"total_runs": 5, "failed_runs": 2, "flaky_runs": 1}

        predictor = HistoricalFlakyPredictor(db_get_test_history=get_history)
        result = await predictor.predict(
            test_code="def test_example():\n    assert True",
            test_name="test_example",
        )
        assert result.test_name == "test_example"

    @pytest.mark.asyncio
    async def test_high_flakiness_history_increases_score(self):
        """High historical flakiness should increase risk score."""
        async def get_history(test_name, project_id):
            return {"total_runs": 100, "failed_runs": 30, "flaky_runs": 20}

        predictor = HistoricalFlakyPredictor(db_get_test_history=get_history)
        result = await predictor.predict(
            test_code="def test_example():\n    assert True",  # Clean code
            test_name="test_example",
        )
        # History should contribute significantly
        assert result.risk_score > 0.2

    @pytest.mark.asyncio
    async def test_clean_history_lowers_score(self):
        """Clean history should lower the risk score."""
        async def get_history(test_name, project_id):
            return {"total_runs": 100, "failed_runs": 0, "flaky_runs": 0}

        predictor = HistoricalFlakyPredictor(db_get_test_history=get_history)
        result = await predictor.predict(
            test_code="def test_example():\n    time.sleep(5)\n    assert True",
            test_name="test_example",
        )
        # Code says risky, but history says clean - should be lower than code-only
        assert result.risk_score < 0.5

    @pytest.mark.asyncio
    async def test_confidence_scales_with_data(self):
        """Confidence should increase with more historical data."""
        async def get_history_small(test_name, project_id):
            return {"total_runs": 20, "failed_runs": 5, "flaky_runs": 3}

        async def get_history_large(test_name, project_id):
            return {"total_runs": 200, "failed_runs": 50, "flaky_runs": 30}

        pred_small = HistoricalFlakyPredictor(db_get_test_history=get_history_small)
        pred_large = HistoricalFlakyPredictor(db_get_test_history=get_history_large)

        result_small = await pred_small.predict(
            test_code="def test_x():\n    assert True",
            test_name="test_x",
        )
        result_large = await pred_large.predict(
            test_code="def test_x():\n    assert True",
            test_name="test_x",
        )

        # More data -> higher confidence
        assert result_large.confidence > result_small.confidence


class TestHistoricalBlending:
    """Test the blending weights."""

    @pytest.mark.asyncio
    async def test_base_weights_sum_to_one(self):
        """BASE_CODE_WEIGHT + BASE_HISTORY_WEIGHT should equal 1.0."""
        pred = HistoricalFlakyPredictor()
        assert pred.BASE_CODE_WEIGHT + pred.BASE_HISTORY_WEIGHT == pytest.approx(1.0)

    @pytest.mark.asyncio
    async def test_adaptive_weights_sum_to_one(self):
        """Adaptive weights should always sum to 1.0 regardless of run count."""
        for runs in [10, 20, 50, 100, 500, 1000]:
            code_w, hist_w = HistoricalFlakyPredictor._adaptive_weights(runs)
            assert code_w + hist_w == pytest.approx(1.0), f"Failed at {runs} runs"

    @pytest.mark.asyncio
    async def test_history_dominates_with_data(self):
        """With sufficient data (>= 100 runs), history weight should exceed code weight."""
        code_w, hist_w = HistoricalFlakyPredictor._adaptive_weights(100)
        assert hist_w > code_w

    @pytest.mark.asyncio
    async def test_no_history_weight_below_threshold(self):
        """Below MIN_RUNS_FOR_HISTORY, history weight should be 0."""
        code_w, hist_w = HistoricalFlakyPredictor._adaptive_weights(5)
        assert hist_w == 0.0
        assert code_w == 1.0

    @pytest.mark.asyncio
    async def test_history_weight_increases_with_data(self):
        """History weight should grow as more data accumulates."""
        _, hist_w_small = HistoricalFlakyPredictor._adaptive_weights(20)
        _, hist_w_large = HistoricalFlakyPredictor._adaptive_weights(200)
        assert hist_w_large > hist_w_small
