"""
Deep behavioral tests for rating_system/storage.py pure logic.
Tests rating type mapping, aggregation logic, distribution calculation,
and list filtering patterns.
"""


# --- Rating distribution calculation ---


class TestRatingDistribution:
    """Tests for _get_rating_distribution logic."""

    def _get_distribution(self, ratings):
        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_5(self):
        result = self._get_distribution([5])
        assert result[5] == 1
        assert result[1] == 0

    def test_all_same_rating(self):
        result = self._get_distribution([3, 3, 3, 3])
        assert result[3] == 4
        assert result[1] == 0
        assert result[5] == 0

    def test_varied_ratings(self):
        result = self._get_distribution([1, 2, 3, 4, 5])
        assert all(v == 1 for v in result.values())

    def test_multiple_of_same(self):
        result = self._get_distribution([5, 5, 4, 4, 4])
        assert result[5] == 2
        assert result[4] == 3

    def test_ignores_zero(self):
        result = self._get_distribution([0, 1, 2])
        assert result[1] == 1
        assert result[2] == 1
        # 0 should be ignored
        assert sum(result.values()) == 2

    def test_ignores_negative(self):
        result = self._get_distribution([-1, 3, 5])
        assert result[3] == 1
        assert result[5] == 1

    def test_ignores_above_5(self):
        result = self._get_distribution([6, 7, 5])
        assert result[5] == 1
        assert sum(result.values()) == 1


# --- Relevance vote aggregation ---


class TestRelevanceVoteAggregation:
    """Tests for up/down vote aggregation."""

    def _aggregate_votes(self, votes):
        up = sum(1 for v in votes if v == "up")
        down = sum(1 for v in votes if v == "down")
        return {"up_votes": up, "down_votes": down, "net_score": up - down}

    def test_all_upvotes(self):
        result = self._aggregate_votes(["up", "up", "up"])
        assert result["up_votes"] == 3
        assert result["down_votes"] == 0
        assert result["net_score"] == 3

    def test_all_downvotes(self):
        result = self._aggregate_votes(["down", "down"])
        assert result["up_votes"] == 0
        assert result["down_votes"] == 2
        assert result["net_score"] == -2

    def test_mixed_votes(self):
        result = self._aggregate_votes(["up", "down", "up", "up"])
        assert result["up_votes"] == 3
        assert result["down_votes"] == 1
        assert result["net_score"] == 2

    def test_empty_votes(self):
        result = self._aggregate_votes([])
        assert result["up_votes"] == 0
        assert result["down_votes"] == 0
        assert result["net_score"] == 0

    def test_balanced_votes(self):
        result = self._aggregate_votes(["up", "down"])
        assert result["net_score"] == 0


# --- Quality rating average ---


class TestQualityRatingAverage:
    """Tests for quality rating average calculation."""

    def _calc_average(self, values):
        if not values:
            return 0
        return sum(values) / len(values)

    def test_empty_returns_zero(self):
        assert self._calc_average([]) == 0

    def test_single_value(self):
        assert self._calc_average([5]) == 5

    def test_multiple_values(self):
        assert self._calc_average([1, 2, 3, 4, 5]) == 3.0

    def test_all_same(self):
        assert self._calc_average([4, 4, 4]) == 4.0

    def test_decimal_average(self):
        result = self._calc_average([3, 4])
        assert result == 3.5


# --- Rating value parsing ---


class TestRatingValueParsing:
    """Tests for parsing rating values from strings."""

    def _parse_quality_value(self, value):
        if isinstance(value, str) and value.isdigit():
            return int(value)
        return None

    def test_digit_string(self):
        assert self._parse_quality_value("5") == 5

    def test_non_digit_string(self):
        assert self._parse_quality_value("up") is None

    def test_empty_string(self):
        assert self._parse_quality_value("") is None

    def test_numeric_with_spaces(self):
        assert self._parse_quality_value(" 3 ") is None  # isdigit fails

    def test_integer_already(self):
        # If already int, isinstance(..., str) is False
        result = self._parse_quality_value(5)
        assert result is None  # Because not a string


# --- Summary structure ---


class TestRatingSummaryStructure:
    """Tests for ratings summary structure."""

    def _build_summary(self, item_id, item_type, quality, relevance):
        return {
            "item_id": item_id,
            "item_type": item_type,
            "quality": quality,
            "relevance": relevance,
        }

    def test_has_item_id(self):
        result = self._build_summary("c1", "card", {}, {})
        assert result["item_id"] == "c1"

    def test_has_item_type(self):
        result = self._build_summary("c1", "card", {}, {})
        assert result["item_type"] == "card"

    def test_quality_section(self):
        quality = {"count": 5, "average": 4.2, "distribution": {}}
        result = self._build_summary("c1", "card", quality, {})
        assert result["quality"]["count"] == 5
        assert result["quality"]["average"] == 4.2

    def test_relevance_section(self):
        relevance = {"up_votes": 10, "down_votes": 2, "net_score": 8}
        result = self._build_summary("c1", "card", {}, relevance)
        assert result["relevance"]["net_score"] == 8


# --- List filter patterns ---


class TestRatingListFilters:
    """Tests for rating list filtering patterns."""

    def _should_filter(self, filters, key):
        return filters and key in filters

    def test_filter_user_id(self):
        assert self._should_filter({"user_id": "u1"}, "user_id") is True

    def test_filter_item_id(self):
        assert self._should_filter({"item_id": "i1"}, "item_id") is True

    def test_filter_item_type(self):
        assert self._should_filter({"item_type": "card"}, "item_type") is True

    def test_filter_card_id_backward_compat(self):
        assert self._should_filter({"card_id": "c1"}, "card_id") is True

    def test_no_filter(self):
        assert not self._should_filter(None, "user_id")

    def test_missing_key(self):
        assert self._should_filter({"user_id": "u1"}, "item_id") is False


# --- Rating dict mapping ---


class TestRatingDictMapping:
    """Tests for rating to dict mapping."""

    def _map_rating(self, rating_dict):
        return {
            "id": rating_dict.get("id"),
            "user_id": rating_dict.get("user_id"),
            "item_id": rating_dict.get("item_id"),
            "item_type": rating_dict.get("item_type"),
            "relevance_vote": rating_dict.get("relevance_vote"),
            "quality_rating": rating_dict.get("quality_rating"),
            "created_at": rating_dict.get("created_at"),
            "updated_at": rating_dict.get("updated_at"),
        }

    def test_preserves_id(self):
        result = self._map_rating({"id": 123})
        assert result["id"] == 123

    def test_preserves_user_id(self):
        result = self._map_rating({"user_id": "u1"})
        assert result["user_id"] == "u1"

    def test_preserves_item_id(self):
        result = self._map_rating({"item_id": "i1"})
        assert result["item_id"] == "i1"

    def test_missing_fields_none(self):
        result = self._map_rating({})
        assert result["relevance_vote"] is None
        assert result["quality_rating"] is None


# --- Create rating defaults ---


class TestCreateRatingDefaults:
    """Tests for create rating default values."""

    def _apply_defaults(self, data):
        return {
            "user_id": data["user_id"],
            "item_id": data["item_id"],
            "item_type": data.get("item_type", "card"),
            "relevance_vote": data.get("rating_value"),
            "quality_rating": data.get("quality_rating"),
        }

    def test_default_item_type(self):
        result = self._apply_defaults({"user_id": "u1", "item_id": "i1"})
        assert result["item_type"] == "card"

    def test_custom_item_type(self):
        result = self._apply_defaults(
            {"user_id": "u1", "item_id": "i1", "item_type": "news"}
        )
        assert result["item_type"] == "news"

    def test_rating_value_maps_to_relevance(self):
        result = self._apply_defaults(
            {"user_id": "u1", "item_id": "i1", "rating_value": "up"}
        )
        assert result["relevance_vote"] == "up"


# --- Upsert patterns ---


class TestUpsertRatingPatterns:
    """Tests for upsert rating logic."""

    def _should_update(self, existing):
        return existing is not None

    def test_update_when_exists(self):
        assert self._should_update({"id": 1}) is True

    def test_create_when_none(self):
        assert self._should_update(None) is False


class TestUpsertDataBuilding:
    """Tests for building upsert create data."""

    def _build_create_data(
        self, user_id, item_id, item_type, rating_type, rating_value
    ):
        return {
            "user_id": user_id,
            "item_id": item_id,
            "item_type": item_type,
            "rating_type": rating_type,
            "rating_value": rating_value,
        }

    def test_all_fields_set(self):
        result = self._build_create_data("u1", "i1", "card", "quality", "5")
        assert result["user_id"] == "u1"
        assert result["item_id"] == "i1"
        assert result["item_type"] == "card"
        assert result["rating_type"] == "quality"
        assert result["rating_value"] == "5"


# --- Item type query logic ---


class TestItemTypeQueryLogic:
    """Tests for item type based query selection."""

    def _get_filter_field(self, item_type):
        if item_type == "card":
            return "card_id"
        return "news_item_id"

    def test_card_type(self):
        assert self._get_filter_field("card") == "card_id"

    def test_news_type(self):
        assert self._get_filter_field("news") == "news_item_id"

    def test_other_type(self):
        assert self._get_filter_field("other") == "news_item_id"


# --- Get user ratings logic ---


class TestGetUserRatingsLogic:
    """Tests for get_user_ratings filter building."""

    def _build_filters(self, user_id, rating_type=None):
        filters = {"user_id": user_id}
        if rating_type:
            filters["rating_type"] = rating_type
        return filters

    def test_user_only(self):
        result = self._build_filters("u1")
        assert result == {"user_id": "u1"}

    def test_user_and_type(self):
        result = self._build_filters("u1", "quality")
        assert result == {"user_id": "u1", "rating_type": "quality"}

    def test_none_type_ignored(self):
        result = self._build_filters("u1", None)
        assert "rating_type" not in result


# --- Rating value update ---


class TestRatingValueUpdate:
    """Tests for rating value update patterns."""

    def _update_fields(self, data):
        updates = {}
        if "rating_value" in data:
            updates["rating_value"] = data["rating_value"]
        if "comment" in data:
            updates["comment"] = data["comment"]
        return updates

    def test_update_rating_value(self):
        result = self._update_fields({"rating_value": "down"})
        assert result["rating_value"] == "down"

    def test_update_comment(self):
        result = self._update_fields({"comment": "Great!"})
        assert result["comment"] == "Great!"

    def test_update_both(self):
        result = self._update_fields({"rating_value": "up", "comment": "Nice"})
        assert result["rating_value"] == "up"
        assert result["comment"] == "Nice"

    def test_ignore_other_fields(self):
        result = self._update_fields({"user_id": "u1", "rating_value": "up"})
        assert "user_id" not in result
        assert result["rating_value"] == "up"

    def test_empty_data(self):
        result = self._update_fields({})
        assert result == {}
