"""
Deep behavioral tests for rating system enums, base classes, and rating distribution.
Tests RelevanceRating, QualityRating, BaseRatingSystem, QualityRatingSystem,
RelevanceRatingSystem, and _get_rating_distribution.
"""

from unittest.mock import Mock

import pytest

from local_deep_research.news.rating_system.base_rater import (
    BaseRatingSystem,
    QualityRating,
    QualityRatingSystem,
    RelevanceRating,
    RelevanceRatingSystem,
)


# --- RelevanceRating enum ---


class TestRelevanceRating:
    """Tests for RelevanceRating enum."""

    def test_up_value(self):
        assert RelevanceRating.UP.value == "up"

    def test_down_value(self):
        assert RelevanceRating.DOWN.value == "down"

    def test_total_count(self):
        assert len(RelevanceRating) == 2

    def test_from_string(self):
        assert RelevanceRating("up") == RelevanceRating.UP
        assert RelevanceRating("down") == RelevanceRating.DOWN

    def test_invalid_value_raises(self):
        with pytest.raises(ValueError):
            RelevanceRating("sideways")


# --- QualityRating enum ---


class TestQualityRating:
    """Tests for QualityRating enum."""

    def test_one_star(self):
        assert QualityRating.ONE_STAR.value == 1

    def test_two_stars(self):
        assert QualityRating.TWO_STARS.value == 2

    def test_three_stars(self):
        assert QualityRating.THREE_STARS.value == 3

    def test_four_stars(self):
        assert QualityRating.FOUR_STARS.value == 4

    def test_five_stars(self):
        assert QualityRating.FIVE_STARS.value == 5

    def test_total_count(self):
        assert len(QualityRating) == 5

    def test_from_int(self):
        assert QualityRating(3) == QualityRating.THREE_STARS

    def test_invalid_value_raises(self):
        with pytest.raises(ValueError):
            QualityRating(6)

    def test_values_are_sequential(self):
        values = [q.value for q in QualityRating]
        assert values == [1, 2, 3, 4, 5]


# --- BaseRatingSystem ---


class ConcreteRatingSystem(BaseRatingSystem):
    """Concrete implementation for testing abstract base class."""

    def rate(self, user_id, card_id, rating_value, metadata=None):
        return {"success": True}

    def get_rating(self, user_id, card_id):
        return None

    def get_rating_type(self):
        return "test"


class TestBaseRatingSystem:
    """Tests for BaseRatingSystem base class."""

    def test_cannot_instantiate_directly(self):
        with pytest.raises(TypeError):
            BaseRatingSystem()

    def test_stores_storage_backend(self):
        backend = Mock()
        r = ConcreteRatingSystem(storage_backend=backend)
        assert r.storage_backend is backend

    def test_storage_backend_none_by_default(self):
        r = ConcreteRatingSystem()
        assert r.storage_backend is None

    def test_rating_type_is_class_name(self):
        r = ConcreteRatingSystem()
        assert r.rating_type == "ConcreteRatingSystem"

    def test_get_recent_ratings_returns_empty(self):
        r = ConcreteRatingSystem()
        result = r.get_recent_ratings("user1")
        assert result == []

    def test_get_card_ratings_returns_default(self):
        r = ConcreteRatingSystem()
        result = r.get_card_ratings("card1")
        assert result == {"total": 0, "average": None}

    def test_remove_rating_returns_false(self):
        r = ConcreteRatingSystem()
        assert r.remove_rating("user1", "card1") is False


# --- _create_rating_record ---


class TestCreateRatingRecord:
    """Tests for the rating record creation helper."""

    def test_includes_user_id(self):
        r = ConcreteRatingSystem()
        record = r._create_rating_record("user1", "card1", "value")
        assert record["user_id"] == "user1"

    def test_includes_card_id(self):
        r = ConcreteRatingSystem()
        record = r._create_rating_record("user1", "card1", "value")
        assert record["card_id"] == "card1"

    def test_includes_rating_type(self):
        r = ConcreteRatingSystem()
        record = r._create_rating_record("user1", "card1", "value")
        assert record["rating_type"] == "test"

    def test_includes_rating_value(self):
        r = ConcreteRatingSystem()
        record = r._create_rating_record("user1", "card1", RelevanceRating.UP)
        assert record["rating_value"] == RelevanceRating.UP

    def test_includes_rated_at(self):
        r = ConcreteRatingSystem()
        record = r._create_rating_record("user1", "card1", "value")
        assert "rated_at" in record

    def test_empty_metadata_by_default(self):
        r = ConcreteRatingSystem()
        record = r._create_rating_record("user1", "card1", "value")
        assert record["metadata"] == {}

    def test_includes_provided_metadata(self):
        r = ConcreteRatingSystem()
        record = r._create_rating_record(
            "user1", "card1", "value", metadata={"context": "test"}
        )
        assert record["metadata"] == {"context": "test"}


# --- _validate_rating_value ---


class TestValidateRatingValue:
    """Tests for rating value validation."""

    def test_none_raises_value_error(self):
        r = ConcreteRatingSystem()
        with pytest.raises(ValueError, match="cannot be None"):
            r._validate_rating_value(None)

    def test_valid_value_no_error(self):
        r = ConcreteRatingSystem()
        r._validate_rating_value("anything")  # Should not raise

    def test_zero_is_valid(self):
        r = ConcreteRatingSystem()
        r._validate_rating_value(0)  # Should not raise

    def test_empty_string_is_valid(self):
        r = ConcreteRatingSystem()
        r._validate_rating_value("")  # Should not raise


# --- QualityRatingSystem ---


class TestQualityRatingSystem:
    """Tests for QualityRatingSystem."""

    def test_rating_type(self):
        qs = QualityRatingSystem()
        assert qs.get_rating_type() == "quality"

    def test_rate_valid_quality(self):
        qs = QualityRatingSystem()
        result = qs.rate("user1", "card1", QualityRating.FOUR_STARS)
        assert result["success"] is True
        assert "rating" in result
        assert "4 stars" in result["message"]

    def test_rate_one_star(self):
        qs = QualityRatingSystem()
        result = qs.rate("user1", "card1", QualityRating.ONE_STAR)
        assert result["success"] is True
        assert "1 stars" in result["message"]

    def test_rate_five_stars(self):
        qs = QualityRatingSystem()
        result = qs.rate("user1", "card1", QualityRating.FIVE_STARS)
        assert result["success"] is True

    def test_rate_with_metadata(self):
        qs = QualityRatingSystem()
        result = qs.rate(
            "user1",
            "card1",
            QualityRating.THREE_STARS,
            metadata={"note": "good"},
        )
        assert result["rating"]["metadata"] == {"note": "good"}

    def test_validate_rejects_non_quality_rating(self):
        qs = QualityRatingSystem()
        with pytest.raises(ValueError, match="QualityRating"):
            qs.rate("user1", "card1", "not_an_enum")

    def test_validate_rejects_relevance_rating(self):
        qs = QualityRatingSystem()
        with pytest.raises(ValueError, match="QualityRating"):
            qs.rate("user1", "card1", RelevanceRating.UP)

    def test_validate_rejects_none(self):
        qs = QualityRatingSystem()
        with pytest.raises(ValueError):
            qs.rate("user1", "card1", None)

    def test_validate_rejects_integer(self):
        qs = QualityRatingSystem()
        with pytest.raises(ValueError, match="QualityRating"):
            qs.rate("user1", "card1", 3)

    def test_get_rating_returns_none_without_backend(self):
        qs = QualityRatingSystem()
        assert qs.get_rating("user1", "card1") is None

    def test_rating_record_has_quality_type(self):
        qs = QualityRatingSystem()
        result = qs.rate("user1", "card1", QualityRating.THREE_STARS)
        assert result["rating"]["rating_type"] == "quality"


# --- RelevanceRatingSystem ---


class TestRelevanceRatingSystem:
    """Tests for RelevanceRatingSystem."""

    def test_rating_type(self):
        rs = RelevanceRatingSystem()
        assert rs.get_rating_type() == "relevance"

    def test_rate_up(self):
        rs = RelevanceRatingSystem()
        result = rs.rate("user1", "card1", RelevanceRating.UP)
        assert result["success"] is True
        assert "thumbs up" in result["message"]

    def test_rate_down(self):
        rs = RelevanceRatingSystem()
        result = rs.rate("user1", "card1", RelevanceRating.DOWN)
        assert result["success"] is True
        assert "thumbs down" in result["message"]

    def test_rate_with_metadata(self):
        rs = RelevanceRatingSystem()
        result = rs.rate(
            "user1", "card1", RelevanceRating.UP, metadata={"reason": "helpful"}
        )
        assert result["rating"]["metadata"] == {"reason": "helpful"}

    def test_validate_rejects_non_relevance_rating(self):
        rs = RelevanceRatingSystem()
        with pytest.raises(ValueError, match="RelevanceRating"):
            rs.rate("user1", "card1", "up")

    def test_validate_rejects_quality_rating(self):
        rs = RelevanceRatingSystem()
        with pytest.raises(ValueError, match="RelevanceRating"):
            rs.rate("user1", "card1", QualityRating.FIVE_STARS)

    def test_validate_rejects_none(self):
        rs = RelevanceRatingSystem()
        with pytest.raises(ValueError):
            rs.rate("user1", "card1", None)

    def test_get_rating_returns_none_without_backend(self):
        rs = RelevanceRatingSystem()
        assert rs.get_rating("user1", "card1") is None

    def test_rating_record_has_relevance_type(self):
        rs = RelevanceRatingSystem()
        result = rs.rate("user1", "card1", RelevanceRating.UP)
        assert result["rating"]["rating_type"] == "relevance"


# --- _get_rating_distribution (from storage.py, testing the logic) ---


class TestRatingDistribution:
    """Tests for rating distribution calculation logic."""

    def _get_distribution(self, ratings):
        """Reproduce the distribution logic from SQLRatingStorage."""
        distribution = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
        for rating in ratings:
            if 1 <= rating <= 5:
                distribution[rating] += 1
        return distribution

    def test_empty_ratings(self):
        result = self._get_distribution([])
        assert result == {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}

    def test_single_rating(self):
        result = self._get_distribution([3])
        assert result == {1: 0, 2: 0, 3: 1, 4: 0, 5: 0}

    def test_multiple_same_rating(self):
        result = self._get_distribution([5, 5, 5])
        assert result == {1: 0, 2: 0, 3: 0, 4: 0, 5: 3}

    def test_full_distribution(self):
        result = self._get_distribution([1, 2, 3, 4, 5])
        assert result == {1: 1, 2: 1, 3: 1, 4: 1, 5: 1}

    def test_out_of_range_ignored(self):
        result = self._get_distribution([0, 6, -1, 100])
        assert result == {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}

    def test_mixed_valid_and_invalid(self):
        result = self._get_distribution([1, 0, 3, 6, 5])
        assert result == {1: 1, 2: 0, 3: 1, 4: 0, 5: 1}

    def test_boundary_values(self):
        result = self._get_distribution([1, 5])
        assert result[1] == 1
        assert result[5] == 1

    def test_heavy_on_one_value(self):
        result = self._get_distribution([4] * 100)
        assert result[4] == 100


# --- Ratings summary calculation patterns ---


class TestRatingsSummaryCalculation:
    """Tests for the ratings summary aggregation logic."""

    def test_quality_average_calculation(self):
        quality_values = [3, 4, 5, 4, 3]
        avg = sum(quality_values) / len(quality_values)
        assert avg == 3.8

    def test_quality_average_empty(self):
        quality_values = []
        avg = sum(quality_values) / len(quality_values) if quality_values else 0
        assert avg == 0

    def test_relevance_net_score(self):
        up_votes = 15
        down_votes = 5
        net = up_votes - down_votes
        assert net == 10

    def test_relevance_negative_net_score(self):
        up_votes = 3
        down_votes = 8
        net = up_votes - down_votes
        assert net == -5

    def test_quality_isdigit_filter(self):
        """Rating values that aren't digits should be filtered out."""
        raw_values = ["3", "4", "five", "2", ""]
        quality_values = [int(v) for v in raw_values if v.isdigit()]
        assert quality_values == [3, 4, 2]

    def test_relevance_vote_counting(self):
        rating_values = ["up", "down", "up", "up", "down"]
        up = sum(1 for v in rating_values if v == "up")
        down = sum(1 for v in rating_values if v == "down")
        assert up == 3
        assert down == 2
