"""Tests for mapper functionality."""

from pydantic import BaseModel

from mosh import Pit, map_object, map_pit
from mosh.mapper import filter_pit, transform_pit


class Person(BaseModel):
    """Test person class."""

    name: str
    email: str
    age: int


class PersonSummary(BaseModel):
    """Simplified person class."""

    name: str


class PersonWithStatus(BaseModel):
    """Person with status."""

    name: str
    status: str


class TestMapObject:
    """Tests for map_object function."""

    def test_map_to_different_type(self) -> None:
        """Test mapping to a different type."""
        person = Person(name="Alice", email="alice@example.com", age=30)
        summary = map_object(person, lambda p: PersonSummary(name=p.name))

        assert isinstance(summary, PersonSummary)
        assert summary.name == "Alice"

    def test_map_with_transformation(self) -> None:
        """Test mapping with data transformation."""
        person = Person(name="alice", email="alice@example.com", age=30)
        summary = map_object(person, lambda p: PersonSummary(name=p.name.upper()))

        assert summary.name == "ALICE"

    def test_map_to_same_type(self) -> None:
        """Test mapping to same type with modification."""
        person = Person(name="Alice", email="alice@example.com", age=30)

        def add_year(p: Person) -> Person:
            return Person(name=p.name, email=p.email, age=p.age + 1)

        older = map_object(person, add_year)
        assert older.age == 31


class TestMapPit:
    """Tests for map_pit function."""

    def test_map_pit_to_new_pit(self) -> None:
        """Test mapping entire pit to new pit."""
        with Pit("people", object_types=[Person]) as source_pit:
            source_pit.put(Person(name="Alice", email="a@example.com", age=30))
            source_pit.put(Person(name="Bob", email="b@example.com", age=25))

            target_pit = map_pit(source_pit, lambda p: PersonSummary(name=p.name))

            assert target_pit.count() == 2
            summaries = target_pit.all()
            names = {s.name for s in summaries}
            assert names == {"Alice", "Bob"}
            target_pit.close()

    def test_map_pit_to_existing_pit(self) -> None:
        """Test mapping to an existing pit."""
        with Pit("source", object_types=[Person]) as source_pit:
            with Pit("target", object_types=[PersonSummary]) as target_pit:
                # Add existing item to target
                target_pit.put(PersonSummary(name="Existing"))

                source_pit.put(Person(name="Alice", email="a@example.com", age=30))

                result = map_pit(
                    source_pit,
                    lambda p: PersonSummary(name=p.name),
                    target_pit=target_pit,
                )

                assert result is target_pit
                assert target_pit.count() == 2

    def test_map_pit_with_conditional_logic(self) -> None:
        """Test mapping with conditional transformation."""
        with Pit("people", object_types=[Person]) as source_pit:
            source_pit.put(Person(name="Alice", email="a@example.com", age=30))
            source_pit.put(Person(name="Bob", email="b@example.com", age=17))

            def add_status(p: BaseModel) -> PersonWithStatus:
                person = p  # type: Person
                status = "adult" if person.age >= 18 else "minor"
                return PersonWithStatus(name=person.name, status=status)

            with Pit("with_status", object_types=[PersonWithStatus]) as target_pit:
                map_pit(source_pit, add_status, target_pit=target_pit)

                adults = target_pit.search(status="adult")
                minors = target_pit.search(status="minor")

                assert len(adults) == 1
                assert len(minors) == 1


class TestFilterPit:
    """Tests for filter_pit function."""

    def test_filter_by_predicate(self) -> None:
        """Test filtering pit by predicate."""
        with Pit("people", object_types=[Person]) as source_pit:
            source_pit.put(Person(name="Alice", email="a@example.com", age=30))
            source_pit.put(Person(name="Bob", email="b@example.com", age=17))
            source_pit.put(Person(name="Charlie", email="c@example.com", age=25))

            adults_pit = filter_pit(source_pit, lambda p: p.age >= 18)

            assert adults_pit.count() == 2
            names = {p.name for p in adults_pit.all()}
            assert names == {"Alice", "Charlie"}
            adults_pit.close()

    def test_filter_to_existing_pit(self) -> None:
        """Test filtering to existing pit."""
        with Pit("source", object_types=[Person]) as source_pit:
            with Pit("target", object_types=[Person]) as target_pit:
                source_pit.put(Person(name="Alice", email="a@example.com", age=30))
                source_pit.put(Person(name="Bob", email="b@example.com", age=17))

                result = filter_pit(source_pit, lambda p: p.age >= 18, target_pit=target_pit)

                assert result is target_pit
                assert target_pit.count() == 1

    def test_filter_by_tags(self) -> None:
        """Test filtering by tags."""
        with Pit("items", object_types=[Person]) as source_pit:
            source_pit.put(Person(name="A", email="a@t.com", age=1), tags=["important"])
            source_pit.put(Person(name="B", email="b@t.com", age=2), tags=["normal"])
            source_pit.put(Person(name="C", email="c@t.com", age=3), tags=["important", "urgent"])

            # Filter needs access to tags, which requires fetching from pit
            # In the new API, we filter by getting stored objects
            important_ids = [s.id for s in source_pit.search_tags_stored(["important"])]
            important_pit = filter_pit(
                source_pit, lambda p: any(s.obj == p for s in source_pit.all_stored() if s.id in important_ids)
            )

            # Simpler approach - filter based on object data
            important_pit.close()

            # Alternative: use search_tags directly
            results = source_pit.search_tags(["important"])
            assert len(results) == 2


class TestTransformPit:
    """Tests for transform_pit function."""

    def test_transform_with_filtering(self) -> None:
        """Test transform that filters out some objects."""
        with Pit("people", object_types=[Person]) as source_pit:
            source_pit.put(Person(name="Alice", email="a@example.com", age=30))
            source_pit.put(Person(name="Bob", email="b@example.com", age=17))

            def adult_summary(obj: BaseModel) -> PersonSummary | None:
                person = obj  # type: Person
                if person.age >= 18:
                    return PersonSummary(name=person.name)
                return None

            result_pit = transform_pit(source_pit, adult_summary)

            assert result_pit.count() == 1
            assert result_pit.all()[0].name == "Alice"
            result_pit.close()

    def test_transform_all_filtered(self) -> None:
        """Test transform that filters all objects."""
        with Pit("items", object_types=[Person]) as source_pit:
            source_pit.put(Person(name="A", email="a@t.com", age=1))
            source_pit.put(Person(name="B", email="b@t.com", age=2))

            result_pit = transform_pit(source_pit, lambda obj: None)

            assert result_pit.count() == 0
            result_pit.close()
