"""Tests for MongoDB sort specification visitor."""

import pytest

from therismos.sorting import SortCriterion, SortOrder, SortSpec
from therismos.sorting.visitors.mongo import MongoVisitor


class TestMongoVisitorBasic:
    """Tests for basic MongoDB visitor functionality."""

    def test_single_ascending_criterion(self):
        """Test MongoDB format for single ascending criterion."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        assert result == {"age": 1}

    def test_single_descending_criterion(self):
        """Test MongoDB format for single descending criterion."""
        spec = SortSpec(
            [
                SortCriterion("name", SortOrder.DESCENDING),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        assert result == {"name": -1}

    def test_multiple_criteria(self):
        """Test MongoDB format for multiple criteria."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.DESCENDING),
                SortCriterion("score", SortOrder.ASCENDING),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        assert result == {"age": 1, "name": -1, "score": 1}

    def test_empty_spec(self):
        """Test MongoDB format for empty spec."""
        spec = SortSpec()
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        assert result == {}

    def test_result_is_dict(self):
        """Test that result is a dictionary."""
        spec = SortSpec(
            [
                SortCriterion("x", SortOrder.ASCENDING),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        assert isinstance(result, dict)


class TestMongoVisitorNoneOrder:
    """Tests for handling SortOrder.NONE."""

    def test_single_none_criterion_skipped(self):
        """Test that NONE order is skipped in MongoDB output."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.NONE),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        assert result == {}

    def test_none_mixed_with_other_orders(self):
        """Test that NONE orders are skipped while others are preserved."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.NONE),
                SortCriterion("score", SortOrder.DESCENDING),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        assert result == {"age": 1, "score": -1}
        assert "name" not in result

    def test_all_none_orders(self):
        """Test spec with all NONE orders results in empty dict."""
        spec = SortSpec(
            [
                SortCriterion("a", SortOrder.NONE),
                SortCriterion("b", SortOrder.NONE),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        assert result == {}


class TestMongoVisitorDuplicateFields:
    """Tests for handling duplicate field names."""

    def test_duplicate_field_last_wins(self):
        """Test that when a field appears multiple times, last occurrence wins."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.DESCENDING),
                SortCriterion("age", SortOrder.DESCENDING),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        # Last occurrence of "age" should be DESCENDING (-1)
        assert result == {"age": -1, "name": -1}

    def test_multiple_duplicates(self):
        """Test multiple duplicate fields."""
        spec = SortSpec(
            [
                SortCriterion("a", SortOrder.ASCENDING),
                SortCriterion("b", SortOrder.DESCENDING),
                SortCriterion("a", SortOrder.DESCENDING),
                SortCriterion("b", SortOrder.ASCENDING),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        assert result == {"a": -1, "b": 1}

    def test_triple_occurrence(self):
        """Test field occurring three times."""
        spec = SortSpec(
            [
                SortCriterion("x", SortOrder.ASCENDING),
                SortCriterion("x", SortOrder.DESCENDING),
                SortCriterion("x", SortOrder.ASCENDING),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        # Last occurrence is ASCENDING (1)
        assert result == {"x": 1}


class TestMongoVisitorDictOrdering:
    """Tests for dictionary ordering in results."""

    def test_dict_preserves_insertion_order(self):
        """Test that result dict preserves field order (Python 3.7+)."""
        spec = SortSpec(
            [
                SortCriterion("z", SortOrder.ASCENDING),
                SortCriterion("a", SortOrder.DESCENDING),
                SortCriterion("m", SortOrder.ASCENDING),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        # Python 3.7+ dicts preserve insertion order
        keys = list(result.keys())
        assert keys == ["z", "a", "m"]


@pytest.mark.parametrize(
    "criteria,expected_mongo",
    [
        # Single criteria
        (
            [SortCriterion("x", SortOrder.ASCENDING)],
            {"x": 1},
        ),
        (
            [SortCriterion("y", SortOrder.DESCENDING)],
            {"y": -1},
        ),
        # Multiple criteria
        (
            [
                SortCriterion("a", SortOrder.ASCENDING),
                SortCriterion("b", SortOrder.DESCENDING),
            ],
            {"a": 1, "b": -1},
        ),
        # With NONE
        (
            [
                SortCriterion("a", SortOrder.ASCENDING),
                SortCriterion("b", SortOrder.NONE),
            ],
            {"a": 1},
        ),
        # Empty
        ([], {}),
        # Duplicates
        (
            [
                SortCriterion("x", SortOrder.ASCENDING),
                SortCriterion("x", SortOrder.DESCENDING),
            ],
            {"x": -1},
        ),
    ],
)
def test_mongo_visitor_parametrized(criteria, expected_mongo):
    """Parametrized test for MongoDB visitor with various specs."""
    spec = SortSpec(criteria)
    visitor = MongoVisitor()

    result = spec.accept(visitor)

    assert result == expected_mongo


class TestMongoVisitorIntegration:
    """Integration tests for MongoDB visitor."""

    def test_realistic_sorting_spec(self):
        """Test realistic sorting specification."""
        spec = SortSpec(
            [
                SortCriterion("created_at", SortOrder.DESCENDING),
                SortCriterion("priority", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.ASCENDING),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)

        assert result == {"created_at": -1, "priority": 1, "name": 1}

    def test_mongo_dict_can_be_used_with_list_items(self):
        """Test that result can be converted to list of tuples for MongoDB."""
        spec = SortSpec(
            [
                SortCriterion("age", SortOrder.ASCENDING),
                SortCriterion("name", SortOrder.DESCENDING),
            ]
        )
        visitor = MongoVisitor()

        result = spec.accept(visitor)
        # MongoDB sort() accepts list of tuples
        sort_list = list(result.items())

        assert sort_list == [("age", 1), ("name", -1)]
