from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock
from uuid import UUID

import pytest
from fastapi import HTTPException

from core.auth.models import CurrentUser
from models.profile import Profile
from v1.profile.repository import ProfileRepository
from v1.profile.schemas import ProfileUpdateRequest
from v1.profile.service import ProfileService


def _create_mock_profile(
    user_id: UUID = UUID("00000000-0000-0000-0000-000000000001"),
    username: str = "demo",
    avatar_url: str | None = None,
    bio: str | None = None,
) -> Profile:
    """Create a mock Profile ORM object."""
    profile = MagicMock(spec=Profile)
    profile.id = user_id
    profile.username = username
    profile.avatar_url = avatar_url
    profile.bio = bio
    return profile


class FakeRepo:
    """Fake repository for testing that conforms to ProfileRepository protocol."""

    def __init__(self, profile: Profile | None) -> None:
        self._profile = profile

    async def get_by_user_id(self, user_id: UUID) -> Profile | None:
        if self._profile and user_id == self._profile.id:
            return self._profile
        return None

    async def get_by_username(self, username: str) -> Profile | None:
        if self._profile and username == self._profile.username:
            return self._profile
        return None

    async def update_by_user_id(
        self, user_id: UUID, update_data: dict[str, str | None]
    ) -> Profile | None:
        if not self._profile or user_id != self._profile.id:
            return None
        # Apply updates to mock
        for key, value in update_data.items():
            if hasattr(self._profile, key):
                setattr(self._profile, key, value)
        return self._profile


# Verify FakeRepo implements the protocol
_repo_check: ProfileRepository = FakeRepo(None)


@pytest.fixture
def mock_session() -> AsyncMock:
    """Create a mock AsyncSession."""
    session = AsyncMock()
    session.commit = AsyncMock()
    session.rollback = AsyncMock()
    return session


@pytest.mark.asyncio
async def test_get_me_returns_profile(mock_session: AsyncMock) -> None:
    user_id = UUID("00000000-0000-0000-0000-000000000001")
    profile = _create_mock_profile(user_id=user_id, username="demo")
    user = CurrentUser(id=user_id)
    service = ProfileService(
        repository=FakeRepo(profile),
        session=mock_session,
        current_user=user,
    )

    result = await service.get_me()

    assert result.username == "demo"
    assert result.id == str(user_id)


@pytest.mark.asyncio
async def test_get_me_not_found_raises_404(mock_session: AsyncMock) -> None:
    user_id = UUID("00000000-0000-0000-0000-000000000001")
    user = CurrentUser(id=user_id)
    service = ProfileService(
        repository=FakeRepo(None),
        session=mock_session,
        current_user=user,
    )

    with pytest.raises(HTTPException) as exc_info:
        await service.get_me()

    assert exc_info.value.status_code == 404


@pytest.mark.asyncio
async def test_update_me_updates_fields(mock_session: AsyncMock) -> None:
    user_id = UUID("00000000-0000-0000-0000-000000000001")
    profile = _create_mock_profile(user_id=user_id, username="demo")
    user = CurrentUser(id=user_id)
    service = ProfileService(
        repository=FakeRepo(profile),
        session=mock_session,
        current_user=user,
    )

    result = await service.update_me(ProfileUpdateRequest(username="updated"))

    assert result.username == "updated"
    mock_session.commit.assert_awaited_once()


@pytest.mark.asyncio
async def test_update_me_no_fields_raises_400(mock_session: AsyncMock) -> None:
    user_id = UUID("00000000-0000-0000-0000-000000000001")
    profile = _create_mock_profile(user_id=user_id)
    user = CurrentUser(id=user_id)
    service = ProfileService(
        repository=FakeRepo(profile),
        session=mock_session,
        current_user=user,
    )

    # Create a request with all None values by bypassing validation
    update = MagicMock(spec=ProfileUpdateRequest)
    update.username = None
    update.avatar_url = None
    update.bio = None

    with pytest.raises(HTTPException) as exc_info:
        await service.update_me(update)

    assert exc_info.value.status_code == 400


@pytest.mark.asyncio
async def test_get_by_username_returns_profile(mock_session: AsyncMock) -> None:
    profile = _create_mock_profile(username="demo")
    service = ProfileService(
        repository=FakeRepo(profile),
        session=mock_session,
        current_user=CurrentUser(id=UUID("00000000-0000-0000-0000-000000000001")),
    )

    result = await service.get_by_username("demo")

    assert result.username == "demo"


@pytest.mark.asyncio
async def test_get_by_username_not_found_raises_404(mock_session: AsyncMock) -> None:
    service = ProfileService(
        repository=FakeRepo(None),
        session=mock_session,
        current_user=CurrentUser(id=UUID("00000000-0000-0000-0000-000000000001")),
    )

    with pytest.raises(HTTPException) as exc_info:
        await service.get_by_username("unknown")

    assert exc_info.value.status_code == 404
