"""Tests for grouping specification serializer."""

import pytest

from therismos.grouping import Aggregation, AggregationFunction, GroupSpec
from therismos.grouping.serializer import Serializer


class TestGroupingSerializer:
    """Tests for grouping specification serialization."""

    def test_serialize_simple_spec(self):
        """Test serializing a simple GroupSpec."""
        spec = GroupSpec(
            group_by=["category"],
            aggregations=[Aggregation("total", AggregationFunction.COUNT)],
        )

        serializer = Serializer()
        result = serializer.serialize(spec)

        assert result == '("category", "total:count")'

    def test_serialize_multiple_fields(self):
        """Test serializing GroupSpec with multiple grouping fields."""
        spec = GroupSpec(
            group_by=["category", "region", "status"],
            aggregations=[Aggregation("total", AggregationFunction.COUNT)],
        )

        serializer = Serializer()
        result = serializer.serialize(spec)

        assert result == '("category,region,status", "total:count")'

    def test_serialize_multiple_aggregations(self):
        """Test serializing GroupSpec with multiple aggregations."""
        spec = GroupSpec(
            group_by=["category"],
            aggregations=[
                Aggregation("total", AggregationFunction.COUNT),
                Aggregation("min_price", AggregationFunction.MIN, "price"),
                Aggregation("avg_price", AggregationFunction.AVERAGE, "price"),
            ],
        )

        serializer = Serializer()
        result = serializer.serialize(spec)

        assert result == '("category", "total:count,min_price:min:price,avg_price:average:price")'

    def test_serialize_empty_group_by(self):
        """Test serializing GroupSpec with no grouping fields."""
        spec = GroupSpec(
            group_by=[],
            aggregations=[Aggregation("total", AggregationFunction.COUNT)],
        )

        serializer = Serializer()
        result = serializer.serialize(spec)

        assert result == '("", "total:count")'

    def test_serialize_empty_aggregations(self):
        """Test serializing GroupSpec with no aggregations."""
        spec = GroupSpec(
            group_by=["category"],
            aggregations=[],
        )

        serializer = Serializer()
        result = serializer.serialize(spec)

        assert result == '("category", "")'

    def test_deserialize_simple_spec(self):
        """Test deserializing a simple GroupSpec."""
        text = '("category", "total:count")'

        serializer = Serializer()
        spec = serializer.deserialize(text)

        assert spec.group_by == ("category",)
        assert len(spec.aggregations) == 1
        assert "total" in spec.aggregations
        assert spec.aggregations["total"].function == AggregationFunction.COUNT
        assert spec.aggregations["total"].field is None

    def test_deserialize_multiple_fields(self):
        """Test deserializing GroupSpec with multiple grouping fields."""
        text = '("category,region,status", "total:count")'

        serializer = Serializer()
        spec = serializer.deserialize(text)

        assert spec.group_by == ("category", "region", "status")

    def test_deserialize_multiple_aggregations(self):
        """Test deserializing GroupSpec with multiple aggregations."""
        text = '("category", "total:count,min_price:min:price,avg_price:average:price")'

        serializer = Serializer()
        spec = serializer.deserialize(text)

        assert len(spec.aggregations) == 3
        assert spec.aggregations["total"].function == AggregationFunction.COUNT
        assert spec.aggregations["min_price"].function == AggregationFunction.MIN
        assert spec.aggregations["min_price"].field == "price"
        assert spec.aggregations["avg_price"].function == AggregationFunction.AVERAGE
        assert spec.aggregations["avg_price"].field == "price"

    def test_deserialize_empty_group_by(self):
        """Test deserializing GroupSpec with no grouping fields."""
        text = '("", "total:count")'

        serializer = Serializer()
        spec = serializer.deserialize(text)

        assert spec.group_by == ()
        assert len(spec.aggregations) == 1

    def test_deserialize_empty_aggregations(self):
        """Test deserializing GroupSpec with no aggregations."""
        text = '("category", "")'

        serializer = Serializer()
        spec = serializer.deserialize(text)

        assert spec.group_by == ("category",)
        assert len(spec.aggregations) == 0

    def test_roundtrip_serialization(self):
        """Test that serialize -> deserialize produces equivalent spec."""
        original = GroupSpec(
            group_by=["category", "region"],
            aggregations=[
                Aggregation("total", AggregationFunction.COUNT),
                Aggregation("min_price", AggregationFunction.MIN, "price"),
                Aggregation("max_price", AggregationFunction.MAX, "price"),
                Aggregation("avg_price", AggregationFunction.AVERAGE, "price"),
            ],
        )

        serializer = Serializer()
        text = serializer.serialize(original)
        deserialized = serializer.deserialize(text)

        # Check equivalence
        assert deserialized.group_by == original.group_by
        assert len(deserialized.aggregations) == len(original.aggregations)
        for agg_id, agg in original.aggregations.items():
            assert agg_id in deserialized.aggregations
            assert deserialized.aggregations[agg_id].id == agg.id
            assert deserialized.aggregations[agg_id].function == agg.function
            assert deserialized.aggregations[agg_id].field == agg.field

    @pytest.mark.parametrize(
        "func,func_str",
        [
            (AggregationFunction.COUNT, "count"),
            (AggregationFunction.SUM, "sum"),
            (AggregationFunction.MIN, "min"),
            (AggregationFunction.MAX, "max"),
            (AggregationFunction.AVERAGE, "average"),
            (AggregationFunction.STDDEV, "stddev"),
            (AggregationFunction.MEDIAN, "median"),
            (AggregationFunction.Q1, "q1"),
            (AggregationFunction.Q3, "q3"),
            (AggregationFunction.P01, "p01"),
            (AggregationFunction.P05, "p05"),
            (AggregationFunction.P10, "p10"),
            (AggregationFunction.P90, "p90"),
            (AggregationFunction.P95, "p95"),
            (AggregationFunction.P99, "p99"),
        ],
    )
    def test_all_aggregation_functions(self, func, func_str):
        """Test serialization/deserialization of all aggregation functions."""
        if func == AggregationFunction.COUNT:
            agg = Aggregation("test", func)
            expected_agg_str = f"test:{func_str}"
        else:
            agg = Aggregation("test", func, "field")
            expected_agg_str = f"test:{func_str}:field"

        spec = GroupSpec(group_by=["category"], aggregations=[agg])

        serializer = Serializer()
        text = serializer.serialize(spec)

        assert expected_agg_str in text

        # Deserialize and check
        deserialized = serializer.deserialize(text)
        assert deserialized.aggregations["test"].function == func

    def test_deserialize_invalid_format(self):
        """Test that invalid format raises ValueError."""
        invalid_texts = [
            "invalid",
            '("category")',  # Missing second element
            "(category, total:count)",  # Missing quotes
            "category, total:count",  # Missing parentheses
        ]

        serializer = Serializer()

        for text in invalid_texts:
            with pytest.raises(ValueError, match="Invalid grouping specification format"):
                serializer.deserialize(text)

    def test_deserialize_invalid_aggregation_format(self):
        """Test that invalid aggregation format raises ValueError."""
        # Too many colons
        text = '("category", "total:count:extra:stuff")'

        serializer = Serializer()

        with pytest.raises(ValueError, match="Invalid aggregation format"):
            serializer.deserialize(text)

    def test_deserialize_unknown_function(self):
        """Test that unknown aggregation function raises ValueError."""
        text = '("category", "total:unknown_func:field")'

        serializer = Serializer()

        with pytest.raises(ValueError, match="Unknown aggregation function"):
            serializer.deserialize(text)

    def test_deserialize_count_with_field_ignored(self):
        """Test that COUNT with field is allowed but the field is ignored."""
        text = '("category", "total:count:price")'

        serializer = Serializer()
        spec = serializer.deserialize(text)

        assert spec.group_by == ("category",)
        assert "total" in spec.aggregations
        assert spec.aggregations["total"].function == AggregationFunction.COUNT
        assert spec.aggregations["total"].field is None  # Field should be ignored

    def test_deserialize_non_count_without_field_error(self):
        """Test that non-COUNT without field raises ValueError during deserialization."""
        text = '("category", "min_price:min")'

        serializer = Serializer()

        with pytest.raises(ValueError, match="min aggregation requires a field"):
            serializer.deserialize(text)

    def test_serialize_url_encode_not_implemented(self):
        """Test that URL encoding is not yet implemented."""
        spec = GroupSpec(
            group_by=["category"],
            aggregations=[Aggregation("total", AggregationFunction.COUNT)],
        )

        serializer = Serializer()

        with pytest.raises(NotImplementedError, match="URL encoding is not yet implemented"):
            serializer.serialize(spec, url_encode=True)

    def test_deserialize_with_whitespace(self):
        """Test deserializing with extra whitespace."""
        text = '( "category" , "total:count" )'

        serializer = Serializer()
        spec = serializer.deserialize(text)

        assert spec.group_by == ("category",)
        assert "total" in spec.aggregations

    def test_deserialize_complex_spec(self):
        """Test deserializing a complex GroupSpec."""
        text = (
            '("category,region,status", '
            '"count:count,min:min:price,max:max:price,avg:average:price,p95:p95:latency")'
        )

        serializer = Serializer()
        spec = serializer.deserialize(text)

        assert spec.group_by == ("category", "region", "status")
        assert len(spec.aggregations) == 5
        assert spec.aggregations["count"].function == AggregationFunction.COUNT
        assert spec.aggregations["min"].function == AggregationFunction.MIN
        assert spec.aggregations["min"].field == "price"
        assert spec.aggregations["p95"].function == AggregationFunction.P95
        assert spec.aggregations["p95"].field == "latency"
