from __future__ import annotations

import uuid
from typing import Any
from unittest.mock import AsyncMock, MagicMock

import pytest

from models.friendships import Friendship, FriendshipStatus
from models.inbox_messages import InboxMessage, InboxMessageStatus, InboxMessageType


class FakeFriendshipRepository:
    """Fake implementation for testing."""

    def __init__(self) -> None:
        self.friendships: dict[uuid.UUID, Friendship] = {}
        self.inbox_messages: dict[uuid.UUID, InboxMessage] = {}

    async def create_request(
        self,
        initiator_id: uuid.UUID,
        recipient_id: uuid.UUID,
    ) -> tuple[Friendship, InboxMessage]:
        raise NotImplementedError

    async def get_friendship_between_users(
        self, user_id_1: uuid.UUID, user_id_2: uuid.UUID
    ) -> Friendship | None:
        raise NotImplementedError

    async def get_pending_inbox_for_recipient(
        self, recipient_id: uuid.UUID, friendship_id: uuid.UUID
    ) -> InboxMessage | None:
        raise NotImplementedError

    async def get_friendship_by_id(self, friendship_id: uuid.UUID) -> Friendship | None:
        raise NotImplementedError

    async def get_inbox_messages_for_user(
        self, user_id: uuid.UUID, status: InboxMessageStatus | None = None
    ) -> list[InboxMessage]:
        raise NotImplementedError

    async def get_outgoing_requests(self, user_id: uuid.UUID) -> list[Friendship]:
        raise NotImplementedError

    async def get_friends_list(self, user_id: uuid.UUID) -> list[Friendship]:
        raise NotImplementedError


class TestFriendshipRepository:
    """Tests for FriendshipRepository."""

    @pytest.fixture
    def mock_session(self) -> MagicMock:
        session = MagicMock()
        session.execute = AsyncMock()
        session.flush = AsyncMock()
        session.add = MagicMock()
        session.commit = AsyncMock()
        return session

    @pytest.fixture
    def repository(self, mock_session: MagicMock) -> Any:
        from v1.friendships.repository import SQLAlchemyFriendshipRepository

        return SQLAlchemyFriendshipRepository(mock_session)

    @pytest.mark.asyncio
    async def test_create_request_creates_friendship_and_inbox(
        self, repository: Any, mock_session: MagicMock
    ) -> None:
        initiator_id = uuid.uuid4()
        recipient_id = uuid.uuid4()

        result_friendship = Friendship(
            id=uuid.uuid4(),
            user_low_id=min(initiator_id, recipient_id),
            user_high_id=max(initiator_id, recipient_id),
            initiator_id=initiator_id,
            status=FriendshipStatus.PENDING,
            requested_at=uuid.uuid4(),
        )
        result_inbox = InboxMessage(
            id=uuid.uuid4(),
            recipient_id=recipient_id,
            sender_id=initiator_id,
            message_type=InboxMessageType.FRIEND_REQUEST,
            status=InboxMessageStatus.PENDING,
        )

        mock_execute = AsyncMock(
            return_value=MagicMock(scalar_one_or_none=MagicMock(return_value=None))
        )
        mock_session.execute = mock_execute
        mock_session.add = MagicMock()

        class MockExecuteResult:
            def __init__(self, returning):
                self._returning = returning

            def scalar_one_or_none(self):
                return self._returning

        async def mock_execute_func(stmt):
            if "INSERT INTO friendships" in str(stmt):
                return MockExecuteResult(result_friendship)
            elif "INSERT INTO inbox_messages" in str(stmt):
                return MockExecuteResult(result_inbox)
            return MockExecuteResult(None)

        mock_session.execute = AsyncMock(side_effect=mock_execute_func)

        friendship, inbox = await repository.create_request(initiator_id, recipient_id)

        assert friendship is not None
        assert inbox is not None
        assert friendship.initiator_id == initiator_id
        assert inbox.recipient_id == recipient_id

    @pytest.mark.asyncio
    async def test_get_friendship_between_users_returns_friendship(
        self, repository: Any, mock_session: MagicMock
    ) -> None:
        user_id_1 = uuid.uuid4()
        user_id_2 = uuid.uuid4()
        expected_friendship = Friendship(
            id=uuid.uuid4(),
            user_low_id=min(user_id_1, user_id_2),
            user_high_id=max(user_id_1, user_id_2),
            initiator_id=user_id_1,
            status=FriendshipStatus.ACCEPTED,
        )

        class MockExecuteResult:
            def scalar_one_or_none(self):
                return expected_friendship

        mock_session.execute = AsyncMock(return_value=MockExecuteResult())

        result = await repository.get_friendship_between_users(user_id_1, user_id_2)

        assert result is not None
        assert result.id == expected_friendship.id

    @pytest.mark.asyncio
    async def test_get_friendship_between_users_returns_none(
        self, repository: Any, mock_session: MagicMock
    ) -> None:
        user_id_1 = uuid.uuid4()
        user_id_2 = uuid.uuid4()

        class MockExecuteResult:
            def scalar_one_or_none(self):
                return None

        mock_session.execute = AsyncMock(return_value=MockExecuteResult())

        result = await repository.get_friendship_between_users(user_id_1, user_id_2)

        assert result is None

    @pytest.mark.asyncio
    async def test_get_pending_inbox_for_recipient_returns_inbox(
        self, repository: Any, mock_session: MagicMock
    ) -> None:
        recipient_id = uuid.uuid4()
        friendship_id = uuid.uuid4()
        expected_inbox = InboxMessage(
            id=uuid.uuid4(),
            recipient_id=recipient_id,
            sender_id=uuid.uuid4(),
            message_type=InboxMessageType.FRIEND_REQUEST,
            friendship_id=friendship_id,
            status=InboxMessageStatus.PENDING,
        )

        class MockExecuteResult:
            def scalar_one_or_none(self):
                return expected_inbox

        mock_session.execute = AsyncMock(return_value=MockExecuteResult())

        result = await repository.get_pending_inbox_for_recipient(
            recipient_id, friendship_id
        )

        assert result is not None
        assert result.id == expected_inbox.id

    @pytest.mark.asyncio
    async def test_get_pending_inbox_for_recipient_returns_none(
        self, repository: Any, mock_session: MagicMock
    ) -> None:
        recipient_id = uuid.uuid4()
        friendship_id = uuid.uuid4()

        class MockExecuteResult:
            def scalar_one_or_none(self):
                return None

        mock_session.execute = AsyncMock(return_value=MockExecuteResult())

        result = await repository.get_pending_inbox_for_recipient(
            recipient_id, friendship_id
        )

        assert result is None

    @pytest.mark.asyncio
    async def test_get_friendship_by_id_returns_friendship(
        self, repository: Any, mock_session: MagicMock
    ) -> None:
        friendship_id = uuid.uuid4()
        expected_friendship = Friendship(
            id=friendship_id,
            user_low_id=uuid.uuid4(),
            user_high_id=uuid.uuid4(),
            initiator_id=uuid.uuid4(),
            status=FriendshipStatus.ACCEPTED,
        )

        class MockExecuteResult:
            def scalar_one_or_none(self):
                return expected_friendship

        mock_session.execute = AsyncMock(return_value=MockExecuteResult())

        result = await repository.get_friendship_by_id(friendship_id)

        assert result is not None
        assert result.id == friendship_id

    @pytest.mark.asyncio
    async def test_get_friendship_by_id_returns_none(
        self, repository: Any, mock_session: MagicMock
    ) -> None:
        friendship_id = uuid.uuid4()

        class MockExecuteResult:
            def scalar_one_or_none(self):
                return None

        mock_session.execute = AsyncMock(return_value=MockExecuteResult())

        result = await repository.get_friendship_by_id(friendship_id)

        assert result is None

    @pytest.mark.asyncio
    async def test_get_inbox_messages_for_user_returns_messages(
        self, repository: Any, mock_session: MagicMock
    ) -> None:
        user_id = uuid.uuid4()
        expected_messages = [
            InboxMessage(
                id=uuid.uuid4(),
                recipient_id=user_id,
                sender_id=uuid.uuid4(),
                message_type=InboxMessageType.FRIEND_REQUEST,
                status=InboxMessageStatus.PENDING,
            ),
            InboxMessage(
                id=uuid.uuid4(),
                recipient_id=user_id,
                sender_id=uuid.uuid4(),
                message_type=InboxMessageType.FRIEND_REQUEST,
                status=InboxMessageStatus.ACCEPTED,
            ),
        ]

        class MockScalars:
            def all(self):
                return expected_messages

        class MockExecuteResult:
            def scalars(self):
                return MockScalars()

        mock_session.execute = AsyncMock(return_value=MockExecuteResult())

        result = await repository.get_inbox_messages_for_user(user_id)

        assert len(result) == 2

    @pytest.mark.asyncio
    async def test_get_inbox_messages_for_user_filters_by_status(
        self, repository: Any, mock_session: MagicMock
    ) -> None:
        user_id = uuid.uuid4()
        expected_messages = [
            InboxMessage(
                id=uuid.uuid4(),
                recipient_id=user_id,
                sender_id=uuid.uuid4(),
                message_type=InboxMessageType.FRIEND_REQUEST,
                status=InboxMessageStatus.PENDING,
            ),
        ]

        class MockScalars:
            def all(self):
                return expected_messages

        class MockExecuteResult:
            def scalars(self):
                return MockScalars()

        mock_session.execute = AsyncMock(return_value=MockExecuteResult())

        result = await repository.get_inbox_messages_for_user(
            user_id, status=InboxMessageStatus.PENDING
        )

        assert len(result) == 1
        assert result[0].status == InboxMessageStatus.PENDING

    @pytest.mark.asyncio
    async def test_get_outgoing_requests_returns_pending_requests(
        self, repository: Any, mock_session: MagicMock
    ) -> None:
        user_id = uuid.uuid4()
        expected_requests = [
            Friendship(
                id=uuid.uuid4(),
                user_low_id=user_id,
                user_high_id=uuid.uuid4(),
                initiator_id=user_id,
                status=FriendshipStatus.PENDING,
            ),
        ]

        class MockScalars:
            def all(self):
                return expected_requests

        class MockExecuteResult:
            def scalars(self):
                return MockScalars()

        mock_session.execute = AsyncMock(return_value=MockExecuteResult())

        result = await repository.get_outgoing_requests(user_id)

        assert len(result) == 1
        assert result[0].initiator_id == user_id
        assert result[0].status == FriendshipStatus.PENDING

    @pytest.mark.asyncio
    async def test_get_friends_list_returns_accepted_friends(
        self, repository: Any, mock_session: MagicMock
    ) -> None:
        user_id = uuid.uuid4()
        friend_id = uuid.uuid4()
        expected_friends = [
            Friendship(
                id=uuid.uuid4(),
                user_low_id=min(user_id, friend_id),
                user_high_id=max(user_id, friend_id),
                initiator_id=user_id,
                status=FriendshipStatus.ACCEPTED,
                accepted_at=uuid.uuid4(),
            ),
        ]

        class MockScalars:
            def all(self):
                return expected_friends

        class MockExecuteResult:
            def scalars(self):
                return MockScalars()

        mock_session.execute = AsyncMock(return_value=MockExecuteResult())

        result = await repository.get_friends_list(user_id)

        assert len(result) == 1
        assert result[0].status == FriendshipStatus.ACCEPTED
