"""Tests for grouping specification optimizer."""

from therismos.grouping import Aggregation, AggregationFunction, GroupSpec
from therismos.grouping.optimizer import optimize


class TestGroupingOptimizer:
    """Tests for grouping specification optimization."""

    def test_no_optimization_needed(self):
        """Test that specs with no redundancy are not modified."""
        spec = GroupSpec(
            group_by=["category", "region"],
            aggregations=[
                Aggregation("total", AggregationFunction.COUNT),
                Aggregation("min_price", AggregationFunction.MIN, "price"),
            ],
        )

        optimized, records = optimize(spec)

        # Should be the same spec
        assert optimized is spec
        assert len(records) == 0

    def test_remove_duplicate_grouping_fields(self):
        """Test removing duplicate grouping fields."""
        spec = GroupSpec(
            group_by=["category", "region", "category"],
            aggregations=[Aggregation("total", AggregationFunction.COUNT)],
        )

        optimized, records = optimize(spec)

        # Should have removed one "category"
        assert optimized.group_by == ("region", "category")
        assert len(records) == 1
        assert "duplicate grouping field" in records[0].reason.lower()

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

        optimized, records = optimize(spec)

        # Should keep only last occurrence of each field
        assert optimized.group_by == ("category", "status", "region")
        assert len(records) == 1
        assert "2 duplicate grouping field" in records[0].reason

    def test_keep_last_occurrence_of_grouping_field(self):
        """Test that last occurrence of duplicate grouping field is kept."""
        spec = GroupSpec(
            group_by=["a", "b", "c", "a"],
            aggregations=[Aggregation("total", AggregationFunction.COUNT)],
        )

        optimized, records = optimize(spec)

        # Last "a" should be kept
        assert optimized.group_by == ("b", "c", "a")

    def test_remove_duplicate_aggregations(self):
        """Test removing duplicate aggregation IDs.

        Note: GroupSpec constructor already deduplicates when converting list to dict,
        so we need to create a spec with a dict that has duplicates in a different way.
        """
        # Create a spec with aggregations dict
        spec = GroupSpec(
            group_by=["category"],
            aggregations={
                "total": Aggregation("total", AggregationFunction.COUNT),
                "min_price": Aggregation("min_price", AggregationFunction.MIN, "price"),
            },
        )

        # Manually create a duplicate by reconstructing with a list that will deduplicate
        # Actually, since GroupSpec __init__ already deduplicates, there's nothing for
        # the optimizer to do. This test verifies that the optimizer doesn't break
        # already-valid specs.
        optimized, records = optimize(spec)

        # Should remain unchanged
        assert optimized is spec
        assert len(records) == 0

    def test_remove_multiple_duplicate_aggregations(self):
        """Test that GroupSpec constructor handles duplicate aggregation IDs.

        Note: Since GroupSpec __init__ already deduplicates, this test verifies
        the constructor behavior rather than optimizer behavior.
        """
        # When creating with a list, constructor automatically deduplicates
        spec = GroupSpec(
            group_by=["category"],
            aggregations=[
                Aggregation("agg1", AggregationFunction.COUNT),
                Aggregation("agg2", AggregationFunction.MIN, "price"),
                Aggregation("agg1", AggregationFunction.MAX, "price"),  # duplicate ID
                Aggregation("agg3", AggregationFunction.AVERAGE, "score"),
                Aggregation("agg2", AggregationFunction.STDDEV, "price"),  # duplicate ID
            ],
        )

        # Constructor already deduplicated during creation
        assert len(spec.aggregations) == 3
        # Last definitions should win (constructor behavior)
        assert spec.aggregations["agg1"].function == AggregationFunction.MAX
        assert spec.aggregations["agg2"].function == AggregationFunction.STDDEV
        assert spec.aggregations["agg3"].function == AggregationFunction.AVERAGE

        # Optimizer has nothing to do
        optimized, records = optimize(spec)
        assert optimized is spec
        assert len(records) == 0

    def test_optimize_both_grouping_and_aggregations(self):
        """Test optimization with duplicates in grouping fields.

        Note: Aggregation duplicates are handled by constructor, so only grouping
        field optimization is tested here.
        """
        spec = GroupSpec(
            group_by=["category", "region", "category"],
            aggregations=[
                Aggregation("total", AggregationFunction.COUNT),
                Aggregation("min_price", AggregationFunction.MIN, "price"),
            ],
        )

        optimized, records = optimize(spec)

        # Should optimize grouping fields
        assert optimized.group_by == ("region", "category")
        # Aggregations unchanged (no duplicates)
        assert len(optimized.aggregations) == 2

        # Should have 1 optimization record (for grouping fields)
        assert len(records) == 1
        assert "grouping field" in records[0].reason.lower()

    def test_optimization_record_structure(self):
        """Test that optimization records have correct structure."""
        spec = GroupSpec(
            group_by=["category", "region", "category"],
            aggregations=[Aggregation("total", AggregationFunction.COUNT)],
        )

        optimized, records = optimize(spec)

        assert len(records) == 1
        record = records[0]

        # Check record fields
        assert record.before is spec
        assert record.after is optimized
        assert isinstance(record.reason, str)
        assert len(record.reason) > 0

    def test_optimize_with_existing_records(self):
        """Test that optimize can append to existing records list."""
        spec1 = GroupSpec(
            group_by=["category", "category"],
            aggregations=[Aggregation("total", AggregationFunction.COUNT)],
        )

        # First optimization
        optimized1, records = optimize(spec1)
        assert len(records) == 1

        # Second optimization with same records list
        spec2 = GroupSpec(
            group_by=["region", "region"],
            aggregations=[Aggregation("count", AggregationFunction.COUNT)],
        )
        optimized2, records = optimize(spec2, records=records)

        # Should have accumulated records
        assert len(records) == 2

    def test_empty_group_by(self):
        """Test optimization with empty group_by."""
        spec = GroupSpec(
            group_by=[],
            aggregations=[Aggregation("total", AggregationFunction.COUNT)],
        )

        optimized, records = optimize(spec)

        # Should remain unchanged
        assert optimized is spec
        assert len(records) == 0

    def test_empty_aggregations(self):
        """Test optimization with empty aggregations."""
        spec = GroupSpec(
            group_by=["category", "region", "category"],
            aggregations=[],
        )

        optimized, records = optimize(spec)

        # Should still optimize grouping fields
        assert optimized.group_by == ("region", "category")
        assert len(records) == 1

    def test_all_grouping_fields_duplicates(self):
        """Test when all grouping fields are the same."""
        spec = GroupSpec(
            group_by=["category", "category", "category"],
            aggregations=[Aggregation("total", AggregationFunction.COUNT)],
        )

        optimized, records = optimize(spec)

        # Should collapse to single field
        assert optimized.group_by == ("category",)
        assert len(records) == 1
        assert "2 duplicate grouping field" in records[0].reason
