"""Integration tests for end-to-end workflows."""

from datetime import date
from unittest.mock import Mock, patch

import pytest

from packages.core.common.data_quality import DataQualityValidator
from packages.core.ratings.engine import RatingEngine
from packages.core.ratings.predictions import PredictionEngine
from packages.core.storage.models import EntityType, Meeting, Race, Starter


class TestDataQualityIntegration:
    """Integration tests for data quality validation."""

    @pytest.fixture
    def mock_session(self):
        """Create mock database session."""
        return Mock()

    def test_data_quality_validator_creation(self, mock_session):
        """Test that DataQualityValidator can be created."""
        validator = DataQualityValidator(mock_session)
        assert validator is not None
        assert validator.session == mock_session

    def test_validate_race_with_valid_data(self, mock_session):
        """Test validation of a valid race."""
        validator = DataQualityValidator(mock_session)

        mock_race = Mock(spec=Race)
        mock_race.id = 1

        starters = [
            Mock(
                spec=Starter,
                id=1,
                horse_id=100,
                driver_id=200,
                trainer_id=300,
                barrier=1,
                handicap_m=0,
                placing=1,
                did_not_finish=False,
            ),
            Mock(
                spec=Starter,
                id=2,
                horse_id=101,
                driver_id=201,
                trainer_id=301,
                barrier=2,
                handicap_m=0,
                placing=2,
                did_not_finish=False,
            ),
        ]

        issues = validator.validate_race(mock_race, starters)

        # Should have no errors for valid data
        errors = [i for i in issues if i.severity == "error"]
        assert len(errors) == 0

    def test_validate_race_with_duplicate_placings(self, mock_session):
        """Test detection of duplicate placing values."""
        validator = DataQualityValidator(mock_session)

        mock_race = Mock(spec=Race)
        mock_race.id = 1

        starters = [
            Mock(
                spec=Starter,
                id=1,
                horse_id=100,
                driver_id=200,
                trainer_id=300,
                barrier=1,
                handicap_m=0,
                placing=1,
                did_not_finish=False,
            ),
            Mock(
                spec=Starter,
                id=2,
                horse_id=101,
                driver_id=201,
                trainer_id=301,
                barrier=2,
                handicap_m=0,
                placing=1,
                did_not_finish=False,
            ),  # Duplicate!
        ]

        issues = validator.validate_race(mock_race, starters)

        # Should detect duplicate placing
        errors = [
            i for i in issues if i.severity == "error" and i.category == "placing"
        ]
        assert len(errors) > 0
        assert "Duplicate" in errors[0].message

    def test_validate_race_with_missing_data(self, mock_session):
        """Test detection of missing data."""
        validator = DataQualityValidator(mock_session)

        mock_race = Mock(spec=Race)
        mock_race.id = 1

        starters = [
            Mock(
                spec=Starter,
                id=1,
                horse_id=100,
                driver_id=None,
                trainer_id=None,
                barrier=1,
                handicap_m=0,
                placing=1,
                did_not_finish=False,
            ),
        ]

        issues = validator.validate_race(mock_race, starters)

        # Should detect missing driver and trainer
        warnings = [
            i
            for i in issues
            if i.severity == "warning" and i.category == "missing_data"
        ]
        assert len(warnings) >= 2  # At least driver and trainer


class TestPredictionIntegration:
    """Integration tests for prediction engine."""

    @pytest.fixture
    def mock_session(self):
        """Create mock database session."""
        return Mock()

    @pytest.fixture
    def prediction_engine(self, mock_session):
        """Create prediction engine."""
        with patch("packages.core.ratings.predictions.RatingEngine"):
            engine = PredictionEngine(mock_session)
            return engine

    def test_prediction_engine_creation(self, mock_session):
        """Test that PredictionEngine can be created."""
        with patch("packages.core.ratings.predictions.RatingEngine"):
            engine = PredictionEngine(mock_session)
            assert engine is not None

    def test_compute_win_probabilities(self, prediction_engine):
        """Test win probability computation."""
        effective_ratings = {
            1: 1600.0,  # High rated
            2: 1500.0,  # Medium rated
            3: 1400.0,  # Low rated
        }

        probs = prediction_engine._compute_win_probabilities(effective_ratings)

        # Probabilities should sum to 1
        assert abs(sum(probs.values()) - 1.0) < 0.001

        # Higher rated should have higher probability
        assert probs[1] > probs[2]
        assert probs[2] > probs[3]

        # All probabilities between 0 and 1
        for prob in probs.values():
            assert 0.0 <= prob <= 1.0

    def test_compute_place_probabilities(self, prediction_engine):
        """Test place probability computation."""
        effective_ratings = {
            1: 1600.0,
            2: 1500.0,
            3: 1400.0,
        }

        probs = prediction_engine._compute_place_probabilities(
            effective_ratings, top_n=2
        )

        # Higher rated should have higher place probability
        assert probs[1] > probs[2]
        assert probs[2] > probs[3]

        # All probabilities between 0 and 1
        for prob in probs.values():
            assert 0.0 <= prob <= 1.0

    def test_prediction_uses_latest_ratings(self, mock_session):
        """Test predictions reflect latest rating snapshots."""
        from unittest.mock import Mock, patch

        mock_meeting = Mock(spec=Meeting)
        mock_meeting.venue = "Test Venue"
        mock_meeting.meeting_date = date(2025, 1, 1)

        mock_race = Mock(spec=Race)
        mock_race.id = 10
        mock_race.race_number = 1
        mock_race.distance_m = 2000
        mock_race.meeting = mock_meeting

        starter1 = Mock(spec=Starter)
        starter1.id = 1
        starter1.horse_id = 100
        starter1.driver_id = None
        starter1.trainer_id = None
        starter1.barrier = None
        starter1.handicap_m = None
        starter1.horse = None
        starter1.driver = None
        starter1.trainer = None

        starter2 = Mock(spec=Starter)
        starter2.id = 2
        starter2.horse_id = 200
        starter2.driver_id = None
        starter2.trainer_id = None
        starter2.barrier = None
        starter2.handicap_m = None
        starter2.horse = None
        starter2.driver = None
        starter2.trainer = None

        def snapshot_for(entity_id):
            snapshot = Mock()
            snapshot.rating = 1700.0 if entity_id == 100 else 1300.0
            snapshot.rd = 100.0
            snapshot.race = None
            return snapshot

        with patch(
            "packages.core.ratings.predictions.RatingSnapshotRepository.get_latest_rating",
            side_effect=lambda session, entity_type, entity_id, before_race_id=None: snapshot_for(
                entity_id
            ),
        ):
            with patch(
                "packages.core.ratings.engine.BarrierAdjustmentRepository.get_all",
                return_value=[],
            ):
                with patch(
                    "packages.core.ratings.engine.HandicapAdjustmentRepository.get_all",
                    return_value=[],
                ):
                    engine = PredictionEngine(mock_session)
                    with patch.object(
                        engine,
                        "_get_recent_finish_stats",
                        return_value={"top3_rate": 0.33, "consistency": 0.5},
                    ):
                        prediction = engine.predict_race(
                            mock_race, [starter1, starter2]
                        )

        win_probs = {p.starter_id: p.win_probability for p in prediction.predictions}
        assert win_probs[starter1.id] > win_probs[starter2.id]


class TestRatingEngineIntegration:
    """Integration tests for rating engine."""

    @pytest.fixture
    def engine(self, monkeypatch):
        """Create rating engine with test settings."""
        monkeypatch.setenv("HRNZ_USERNAME", "test")
        monkeypatch.setenv("HRNZ_PASSWORD", "test")
        monkeypatch.setenv("DATABASE_URL", "postgresql://test")
        monkeypatch.setenv("ENABLE_RD", "false")

        from packages.core.common.settings import reload_settings

        reload_settings()

        return RatingEngine()

    def test_rating_engine_with_rd_k_factor(self, monkeypatch):
        """Test that RD-based K-factor works correctly."""
        monkeypatch.setenv("ENABLE_RD", "true")
        monkeypatch.setenv("INITIAL_RD", "350.0")

        from packages.core.common.settings import reload_settings

        reload_settings()

        engine = RatingEngine()

        # New entity should have K-factor = base K
        k1 = engine.get_effective_k_factor(EntityType.HORSE, 1)
        assert k1 == pytest.approx(24.0)

        # Simulate races to reduce RD
        state = engine.get_or_init_rating(EntityType.HORSE, 1)
        initial_rd = state.rd

        updates = []
        for i in range(10):
            engine._apply_update(
                EntityType.HORSE,
                1,
                delta=5.0,
                race_id=i,
                race_date=date(2025, 1, 26),
                updates=updates,
            )

        # RD should have decreased
        assert state.rd < initial_rd

        # K-factor should have decreased proportionally
        k2 = engine.get_effective_k_factor(EntityType.HORSE, 1)
        assert k2 < k1

    def test_two_horse_race_zero_sum(self, engine):
        """Test that rating changes sum to zero in a two-horse race."""
        from packages.core.storage.models import Meeting, Race, Starter

        # Create mock race
        mock_meeting = Mock(spec=Meeting)
        mock_meeting.venue = "Test"
        mock_meeting.meeting_date = date(2025, 1, 26)

        mock_race = Mock(spec=Race)
        mock_race.id = 1
        mock_race.distance_m = 2000
        mock_race.start_type = "mobile"
        mock_race.meeting = mock_meeting

        # Create two starters with equal ratings
        starter1 = Mock(spec=Starter)
        starter1.id = 1
        starter1.horse_id = 100
        starter1.driver_id = None
        starter1.trainer_id = None
        starter1.barrier = 1
        starter1.handicap_m = 0
        starter1.placing = 1
        starter1.did_not_finish = False

        starter2 = Mock(spec=Starter)
        starter2.id = 2
        starter2.horse_id = 200
        starter2.driver_id = None
        starter2.trainer_id = None
        starter2.barrier = 2
        starter2.handicap_m = 0
        starter2.placing = 2
        starter2.did_not_finish = False

        starters = [starter1, starter2]

        # Initialize equal ratings
        engine.get_or_init_rating(EntityType.HORSE, 100)
        engine.get_or_init_rating(EntityType.HORSE, 200)

        # Process race
        updates = engine.process_race(mock_race, starters)

        # Check zero-sum property
        total_delta = sum(u.delta for u in updates)
        assert abs(total_delta) < 0.01  # Should be very close to zero

        # Winner should gain rating
        winner_update = [u for u in updates if u.entity_id == 100][0]
        assert winner_update.delta > 0

        # Loser should lose rating
        loser_update = [u for u in updates if u.entity_id == 200][0]
        assert loser_update.delta < 0
