"""Tests for user routes."""
# ruff: noqa: I001 - Imports structured for Jinja2 template conditionals

from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock

ServiceMock = AsyncMock
from uuid import uuid4

import pytest
from httpx import ASGITransport, AsyncClient

from app.api.deps import get_current_active_superuser, get_current_user, get_user_service
from app.api.deps import get_db_session
from app.api.deps import get_redis
from app.core.config import settings
from app.main import app


class MockUser:
    """Mock user for testing."""

    def __init__(
        self,
        id=None,
        email="test@example.com",
        full_name="Test User",
        is_active=True,
        role="user",
    ):
        self.id = id or uuid4()
        self.email = email
        self.full_name = full_name
        self.is_active = is_active
        self.role = role
        self.hashed_password = "hashed"
        self.created_at = datetime.now(UTC)
        self.updated_at = datetime.now(UTC)

    def has_role(self, role) -> bool:
        """Check if user has the specified role."""
        if hasattr(role, "value"):
            return self.role == role.value
        return self.role == role


@pytest.fixture
def mock_user() -> MockUser:
    """Create a mock regular user."""
    return MockUser()


@pytest.fixture
def mock_superuser() -> MockUser:
    """Create a mock superuser."""
    return MockUser(role="admin", email="admin@example.com")


@pytest.fixture
def mock_user_service(mock_user: MockUser) -> MagicMock:
    """Create a mock user service."""
    service = MagicMock()
    service.get_by_id = ServiceMock(return_value=mock_user)
    service.get_multi = ServiceMock(return_value=[mock_user])
    service.update = ServiceMock(return_value=mock_user)
    service.delete = ServiceMock(return_value=mock_user)
    return service


@pytest.fixture
async def auth_client(
    mock_user: MockUser,
    mock_user_service: MagicMock,
    mock_redis: MagicMock,
    mock_db_session,
) -> AsyncClient:
    """Client with authenticated regular user."""
    app.dependency_overrides[get_current_user] = lambda: mock_user
    app.dependency_overrides[get_user_service] = lambda: mock_user_service
    app.dependency_overrides[get_redis] = lambda: mock_redis
    app.dependency_overrides[get_db_session] = lambda: mock_db_session

    async with AsyncClient(
        transport=ASGITransport(app=app),
        base_url="http://test",
    ) as ac:
        yield ac

    app.dependency_overrides.clear()


@pytest.fixture
async def superuser_client(
    mock_superuser: MockUser,
    mock_user_service: MagicMock,
    mock_redis: MagicMock,
    mock_db_session,
) -> AsyncClient:
    """Client with authenticated superuser."""
    app.dependency_overrides[get_current_user] = lambda: mock_superuser
    app.dependency_overrides[get_current_active_superuser] = lambda: mock_superuser
    app.dependency_overrides[get_user_service] = lambda: mock_user_service
    app.dependency_overrides[get_redis] = lambda: mock_redis
    app.dependency_overrides[get_db_session] = lambda: mock_db_session

    async with AsyncClient(
        transport=ASGITransport(app=app),
        base_url="http://test",
    ) as ac:
        yield ac

    app.dependency_overrides.clear()


@pytest.mark.anyio
async def test_read_current_user(auth_client: AsyncClient, mock_user: MockUser):
    """Test getting current user."""
    response = await auth_client.get(f"{settings.API_V1_STR}/users/me")
    assert response.status_code == 200
    data = response.json()
    assert data["email"] == mock_user.email


@pytest.mark.anyio
async def test_update_current_user(auth_client: AsyncClient, mock_user_service: MagicMock):
    """Test updating current user."""
    response = await auth_client.patch(
        f"{settings.API_V1_STR}/users/me",
        json={"full_name": "Updated Name"},
    )
    assert response.status_code == 200
    mock_user_service.update.assert_called_once()


@pytest.mark.anyio
async def test_read_user_by_id(
    superuser_client: AsyncClient,
    mock_user: MockUser,
    mock_user_service: MagicMock,
):
    """Test getting user by ID as superuser."""
    response = await superuser_client.get(f"{settings.API_V1_STR}/users/{mock_user.id}")
    assert response.status_code == 200
    data = response.json()
    assert data["email"] == mock_user.email


@pytest.mark.anyio
async def test_read_user_by_id_not_found(
    superuser_client: AsyncClient,
    mock_user_service: MagicMock,
):
    """Test getting non-existent user."""
    from app.core.exceptions import NotFoundError

    mock_user_service.get_by_id = ServiceMock(side_effect=NotFoundError(message="User not found"))

    response = await superuser_client.get(f"{settings.API_V1_STR}/users/{uuid4()}")
    assert response.status_code == 404


@pytest.mark.anyio
async def test_update_user_by_id(
    superuser_client: AsyncClient,
    mock_user: MockUser,
    mock_user_service: MagicMock,
):
    """Test updating user by ID as superuser."""
    response = await superuser_client.patch(
        f"{settings.API_V1_STR}/users/{mock_user.id}",
        json={"full_name": "Admin Updated"},
    )
    assert response.status_code == 200
    mock_user_service.update.assert_called_once()


@pytest.mark.anyio
async def test_delete_user_by_id(
    superuser_client: AsyncClient,
    mock_user: MockUser,
    mock_user_service: MagicMock,
):
    """Test deleting user by ID as superuser."""
    response = await superuser_client.delete(f"{settings.API_V1_STR}/users/{mock_user.id}")
    assert response.status_code == 204
    mock_user_service.delete.assert_called_once()


@pytest.mark.anyio
async def test_delete_user_by_id_not_found(
    superuser_client: AsyncClient,
    mock_user_service: MagicMock,
):
    """Test deleting non-existent user."""
    from app.core.exceptions import NotFoundError

    mock_user_service.delete = ServiceMock(side_effect=NotFoundError(message="User not found"))

    response = await superuser_client.delete(f"{settings.API_V1_STR}/users/{uuid4()}")
    assert response.status_code == 404
