"""Tests for built-in sort specification visitors."""

import pytest

from therismos.sorting import SortCriterion, SortOrder, SortSpec
from therismos.sorting.visitors import DictVisitor, FieldGathererVisitor, StringVisitor


class TestStringVisitor:
    """Tests for StringVisitor."""

    def test_single_ascending_criterion(self):
        """Test string representation of single ascending criterion."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
            ]
        )
        visitor = StringVisitor()

        result = spec.accept(visitor)

        assert result == "age ASC"

    def test_single_descending_criterion(self):
        """Test string representation of single descending criterion."""
        spec = SortSpec(
            [
                SortCriterion("name", SortOrder.DESCENDING),
            ]
        )
        visitor = StringVisitor()

        result = spec.accept(visitor)

        assert result == "name DESC"

    def test_single_none_criterion(self):
        """Test string representation of single NONE criterion."""
        spec = SortSpec(
            [
                SortCriterion("score", SortOrder.NONE),
            ]
        )
        visitor = StringVisitor()

        result = spec.accept(visitor)

        assert result == "score NONE"

    def test_multiple_criteria(self):
        """Test string representation of multiple criteria."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.DESCENDING),
                SortCriterion("score", SortOrder.ASCENDING),
            ]
        )
        visitor = StringVisitor()

        result = spec.accept(visitor)

        assert result == "age ASC, name DESC, score ASC"

    def test_empty_spec(self):
        """Test string representation of empty spec."""
        spec = SortSpec()
        visitor = StringVisitor()

        result = spec.accept(visitor)

        assert result == ""

    def test_mixed_orders(self):
        """Test string representation with mixed orders including NONE."""
        spec = SortSpec(
            [
                SortCriterion("a", SortOrder.ASCENDING),
                SortCriterion("b", SortOrder.NONE),
                SortCriterion("c", SortOrder.DESCENDING),
            ]
        )
        visitor = StringVisitor()

        result = spec.accept(visitor)

        assert result == "a ASC, b NONE, c DESC"


class TestDictVisitor:
    """Tests for DictVisitor."""

    def test_single_ascending_criterion(self):
        """Test dict representation of single ascending criterion."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
            ]
        )
        visitor = DictVisitor()

        result = spec.accept(visitor)

        assert result == [{"field": "age", "order": "ASC"}]

    def test_single_descending_criterion(self):
        """Test dict representation of single descending criterion."""
        spec = SortSpec(
            [
                SortCriterion("name", SortOrder.DESCENDING),
            ]
        )
        visitor = DictVisitor()

        result = spec.accept(visitor)

        assert result == [{"field": "name", "order": "DESC"}]

    def test_single_none_criterion(self):
        """Test dict representation of single NONE criterion."""
        spec = SortSpec(
            [
                SortCriterion("score", SortOrder.NONE),
            ]
        )
        visitor = DictVisitor()

        result = spec.accept(visitor)

        assert result == [{"field": "score", "order": "NONE"}]

    def test_multiple_criteria(self):
        """Test dict representation of multiple criteria."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.DESCENDING),
            ]
        )
        visitor = DictVisitor()

        result = spec.accept(visitor)

        assert result == [
            {"field": "age", "order": "ASC"},
            {"field": "name", "order": "DESC"},
        ]

    def test_empty_spec(self):
        """Test dict representation of empty spec."""
        spec = SortSpec()
        visitor = DictVisitor()

        result = spec.accept(visitor)

        assert result == []

    def test_dict_structure(self):
        """Test that dict entries have correct structure."""
        spec = SortSpec(
            [
                SortCriterion("x", SortOrder.ASCENDING),
            ]
        )
        visitor = DictVisitor()

        result = spec.accept(visitor)

        assert isinstance(result, list)
        assert len(result) == 1
        assert isinstance(result[0], dict)
        assert "field" in result[0]
        assert "order" in result[0]


class TestFieldGathererVisitor:
    """Tests for FieldGathererVisitor."""

    def test_single_criterion(self):
        """Test gathering field from single criterion."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
            ]
        )
        visitor = FieldGathererVisitor()

        spec.accept(visitor)

        assert visitor.field_names == {"age"}

    def test_multiple_different_fields(self):
        """Test gathering multiple different fields."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.DESCENDING),
                SortCriterion("score", SortOrder.ASCENDING),
            ]
        )
        visitor = FieldGathererVisitor()

        spec.accept(visitor)

        assert visitor.field_names == {"age", "name", "score"}

    def test_duplicate_fields(self):
        """Test gathering fields when same field appears multiple times."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.DESCENDING),
                SortCriterion("age", SortOrder.DESCENDING),
            ]
        )
        visitor = FieldGathererVisitor()

        spec.accept(visitor)

        # Set should contain unique field names only
        assert visitor.field_names == {"age", "name"}

    def test_empty_spec(self):
        """Test gathering fields from empty spec."""
        spec = SortSpec()
        visitor = FieldGathererVisitor()

        spec.accept(visitor)

        assert visitor.field_names == set()

    def test_visitor_is_stateful(self):
        """Test that visitor accumulates fields across multiple visits."""
        spec1 = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
            ]
        )
        spec2 = SortSpec(
            [
                SortCriterion("name", SortOrder.DESCENDING),
            ]
        )
        visitor = FieldGathererVisitor()

        spec1.accept(visitor)
        spec2.accept(visitor)

        assert visitor.field_names == {"age", "name"}

    def test_visitor_initialization(self):
        """Test that visitor starts with empty field set."""
        visitor = FieldGathererVisitor()

        assert visitor.field_names == set()
        assert isinstance(visitor.field_names, set)


@pytest.mark.parametrize(
    "criteria,expected_string,expected_dict_count,expected_fields",
    [
        (
            [SortCriterion("x", SortOrder.ASCENDING)],
            "x ASC",
            1,
            {"x"},
        ),
        (
            [
                SortCriterion("a", SortOrder.ASCENDING),
                SortCriterion("b", SortOrder.DESCENDING),
            ],
            "a ASC, b DESC",
            2,
            {"a", "b"},
        ),
        (
            [],
            "",
            0,
            set(),
        ),
    ],
)
def test_all_visitors_parametrized(criteria, expected_string, expected_dict_count, expected_fields):
    """Parametrized test for all visitors with various specs."""
    spec = SortSpec(criteria)

    # Test StringVisitor
    string_visitor = StringVisitor()
    string_result = spec.accept(string_visitor)
    assert string_result == expected_string

    # Test DictVisitor
    dict_visitor = DictVisitor()
    dict_result = spec.accept(dict_visitor)
    assert len(dict_result) == expected_dict_count

    # Test FieldGathererVisitor
    field_visitor = FieldGathererVisitor()
    spec.accept(field_visitor)
    assert field_visitor.field_names == expected_fields
