"""Tests for sort specification serializer."""

import pytest

from therismos.sorting import SortCriterion, SortOrder, SortSpec
from therismos.sorting.serializer import Serializer


class TestBasicSerialization:
    """Tests for basic serialization functionality."""

    def test_serialize_empty_spec(self):
        """Test serializing an empty sort spec."""
        spec = SortSpec()
        serializer = Serializer()

        result = serializer.serialize(spec)

        assert result == ""

    def test_serialize_single_ascending(self):
        """Test serializing a single ascending criterion."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
            ]
        )
        serializer = Serializer()

        result = serializer.serialize(spec)

        assert result == "age"

    def test_serialize_single_descending(self):
        """Test serializing a single descending criterion."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.DESCENDING),
            ]
        )
        serializer = Serializer()

        result = serializer.serialize(spec)

        assert result == "-age"

    def test_serialize_multiple_criteria(self):
        """Test serializing multiple criteria."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.DESCENDING),
                SortCriterion("score", SortOrder.ASCENDING),
            ]
        )
        serializer = Serializer()

        result = serializer.serialize(spec)

        assert result == "age,-name,score"

    def test_serialize_skips_none_order(self):
        """Test that NONE orders are skipped during serialization."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.NONE),
                SortCriterion("score", SortOrder.DESCENDING),
            ]
        )
        serializer = Serializer()

        result = serializer.serialize(spec)

        assert result == "age,-score"
        assert "name" not in result

    def test_serialize_dotted_field_names(self):
        """Test serializing fields with dot notation."""
        spec = SortSpec(
            [
                SortCriterion("user.name", SortOrder.ASCENDING),
                SortCriterion("profile.age", SortOrder.DESCENDING),
            ]
        )
        serializer = Serializer()

        result = serializer.serialize(spec)

        assert result == "user.name,-profile.age"


class TestSerializationWithTypes:
    """Tests for basic deserialization functionality."""

    def test_deserialize_empty_string(self):
        """Test deserializing an empty string."""
        serializer = Serializer()

        result = serializer.deserialize("")

        assert len(result) == 0

    def test_deserialize_whitespace_only(self):
        """Test deserializing whitespace-only string."""
        serializer = Serializer()

        result = serializer.deserialize("   ")

        assert len(result) == 0

    def test_deserialize_single_ascending(self):
        """Test deserializing a single ascending criterion."""
        serializer = Serializer()

        result = serializer.deserialize("age")

        assert len(result) == 1
        assert result[0].field == "age"
        assert result[0].order == SortOrder.ASCENDING

    def test_deserialize_single_ascending_explicit(self):
        """Test deserializing with explicit + prefix."""
        serializer = Serializer()

        result = serializer.deserialize("+age")

        assert len(result) == 1
        assert result[0].field == "age"
        assert result[0].order == SortOrder.ASCENDING

    def test_deserialize_single_descending(self):
        """Test deserializing a single descending criterion."""
        serializer = Serializer()

        result = serializer.deserialize("-age")

        assert len(result) == 1
        assert result[0].field == "age"
        assert result[0].order == SortOrder.DESCENDING

    def test_deserialize_multiple_criteria(self):
        """Test deserializing multiple criteria."""
        serializer = Serializer()

        result = serializer.deserialize("age,-name,+score")

        assert len(result) == 3
        assert result[0].field == "age"
        assert result[0].order == SortOrder.ASCENDING
        assert result[1].field == "name"
        assert result[1].order == SortOrder.DESCENDING
        assert result[2].field == "score"
        assert result[2].order == SortOrder.ASCENDING

    def test_deserialize_with_spaces(self):
        """Test deserializing with spaces around commas."""
        serializer = Serializer()

        result = serializer.deserialize("age, -name , +score")

        assert len(result) == 3
        assert result[0].field == "age"
        assert result[1].field == "name"
        assert result[2].field == "score"

    def test_deserialize_dotted_field_names(self):
        """Test deserializing fields with dot notation."""
        serializer = Serializer()

        result = serializer.deserialize("user.name,-profile.age")

        assert len(result) == 2
        assert result[0].field == "user.name"
        assert result[1].field == "profile.age"


class TestDeserializationWithTypes:
    """Tests for serialization/deserialization round trips."""

    def test_roundtrip_simple(self):
        """Test round trip with simple criteria."""
        original = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.DESCENDING),
            ]
        )
        serializer = Serializer()

        text = serializer.serialize(original)
        restored = serializer.deserialize(text)

        assert len(restored) == len(original)
        assert restored[0].field == original[0].field
        assert restored[0].order == original[0].order
        assert restored[1].field == original[1].field
        assert restored[1].order == original[1].order

    def test_roundtrip_skips_none_orders(self):
        """Test that NONE orders are not preserved in round trip."""
        original = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.NONE),
                SortCriterion("score", SortOrder.DESCENDING),
            ]
        )
        serializer = Serializer()

        text = serializer.serialize(original)
        restored = serializer.deserialize(text)

        # NONE order should be skipped
        assert len(restored) == 2
        assert restored[0].field == "age"
        assert restored[1].field == "score"


class TestCustomTypes:
    """Tests for edge cases and error handling."""

    def test_deserialize_trailing_comma_ignored(self):
        """Test that trailing commas are handled gracefully."""
        serializer = Serializer()

        result = serializer.deserialize("age,-name,")

        assert len(result) == 2

    def test_deserialize_multiple_commas_ignored(self):
        """Test that multiple consecutive commas are handled."""
        serializer = Serializer()

        result = serializer.deserialize("age,,,-name")

        assert len(result) == 2


@pytest.mark.parametrize(
    "text,expected_count,expected_fields,expected_orders",
    [
        ("age", 1, ["age"], [SortOrder.ASCENDING]),
        ("-age", 1, ["age"], [SortOrder.DESCENDING]),
        ("+age", 1, ["age"], [SortOrder.ASCENDING]),
        ("age,-name", 2, ["age", "name"], [SortOrder.ASCENDING, SortOrder.DESCENDING]),
        (
            "a,-b,+c,-d",
            4,
            ["a", "b", "c", "d"],
            [
                SortOrder.ASCENDING,
                SortOrder.DESCENDING,
                SortOrder.ASCENDING,
                SortOrder.DESCENDING,
            ],
        ),
    ],
)
def test_deserialization_parametrized(text, expected_count, expected_fields, expected_orders):
    """Parametrized test for various deserialization cases."""
    serializer = Serializer()
    result = serializer.deserialize(text)

    assert len(result) == expected_count
    for i, (field_name, order) in enumerate(zip(expected_fields, expected_orders, strict=True)):
        assert result[i].field == field_name
        assert result[i].order == order
