"""Tests for collections API endpoints.

Tests the card collection management endpoints with mocked services.
"""

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

import pytest
from fastapi import FastAPI, status
from fastapi.testclient import TestClient

from app.api import deps as api_deps
from app.api.collections import router as collections_router
from app.db.models import User
from app.db.models.collection import CardSource
from app.repositories.protocols import CollectionEntry
from app.services.card_service import CardService
from app.services.collection_service import CollectionService
from app.services.jwt_service import create_access_token

# =============================================================================
# Fixtures
# =============================================================================


@pytest.fixture
def test_user():
    """Create a test user object."""
    user = User(
        email="test@example.com",
        display_name="Test User",
        avatar_url="https://example.com/avatar.jpg",
        oauth_provider="google",
        oauth_id="google-123",
        is_premium=False,
        premium_until=None,
    )
    user.id = uuid4()
    user.created_at = datetime.now(UTC)
    user.updated_at = datetime.now(UTC)
    user.last_login = None
    user.linked_accounts = []
    return user


@pytest.fixture
def access_token(test_user):
    """Create a valid access token for the test user."""
    return create_access_token(test_user.id)


@pytest.fixture
def auth_headers(access_token):
    """Create Authorization headers with Bearer token."""
    return {"Authorization": f"Bearer {access_token}"}


@pytest.fixture
def mock_collection_service():
    """Create a mock CollectionService."""
    return MagicMock(spec=CollectionService)


@pytest.fixture
def mock_card_service():
    """Create a mock CardService."""
    service = MagicMock(spec=CardService)
    service.get_card.return_value = MagicMock()  # Card exists
    return service


@pytest.fixture
def app(test_user, mock_collection_service, mock_card_service):
    """Create a test FastAPI app with mocked dependencies."""
    test_app = FastAPI()
    test_app.include_router(collections_router, prefix="/api")

    # Override dependencies
    async def override_get_current_user():
        return test_user

    def override_get_collection_service():
        return mock_collection_service

    test_app.dependency_overrides[api_deps.get_current_user] = override_get_current_user
    test_app.dependency_overrides[api_deps.get_collection_service] = override_get_collection_service

    yield test_app
    test_app.dependency_overrides.clear()


@pytest.fixture
def unauthenticated_app():
    """Create a test FastAPI app without auth override (for 401 tests)."""
    test_app = FastAPI()
    test_app.include_router(collections_router, prefix="/api")
    yield test_app


@pytest.fixture
def client(app):
    """Create a test client for the app."""
    return TestClient(app)


@pytest.fixture
def unauthenticated_client(unauthenticated_app):
    """Create a test client without auth for 401 tests."""
    return TestClient(unauthenticated_app)


def make_collection_entry(
    card_id: str = "a1-001-bulbasaur",
    quantity: int = 4,
    source: CardSource = CardSource.BOOSTER,
) -> CollectionEntry:
    """Create a CollectionEntry for testing."""
    return CollectionEntry(
        id=uuid4(),
        user_id=uuid4(),
        card_definition_id=card_id,
        quantity=quantity,
        source=source,
        obtained_at=datetime.now(UTC),
        created_at=datetime.now(UTC),
        updated_at=datetime.now(UTC),
    )


# =============================================================================
# GET /collections/me Tests
# =============================================================================


class TestGetMyCollection:
    """Tests for GET /api/collections/me endpoint."""

    def test_returns_empty_collection_for_new_user(
        self, client: TestClient, auth_headers, mock_collection_service
    ):
        """
        Test that endpoint returns empty collection for new user.

        New users should have no cards until they select a starter.
        """
        mock_collection_service.get_collection = AsyncMock(return_value=[])

        response = client.get("/api/collections/me", headers=auth_headers)

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["total_unique_cards"] == 0
        assert data["total_card_count"] == 0
        assert data["entries"] == []

    def test_returns_collection_with_cards(
        self, client: TestClient, auth_headers, mock_collection_service
    ):
        """
        Test that endpoint returns cards in collection.

        Should include all cards with quantities and totals.
        """
        entries = [
            make_collection_entry("a1-001-bulbasaur", 4),
            make_collection_entry("a1-033-charmander", 2),
        ]
        mock_collection_service.get_collection = AsyncMock(return_value=entries)

        response = client.get("/api/collections/me", headers=auth_headers)

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["total_unique_cards"] == 2
        assert data["total_card_count"] == 6  # 4 + 2
        assert len(data["entries"]) == 2

    def test_requires_authentication(self, unauthenticated_client: TestClient):
        """
        Test that endpoint requires authentication.

        Unauthenticated requests should return 401.
        """
        response = unauthenticated_client.get("/api/collections/me")

        assert response.status_code == status.HTTP_401_UNAUTHORIZED


# =============================================================================
# GET /collections/me/cards/{card_id} Tests
# =============================================================================


class TestGetCardQuantity:
    """Tests for GET /api/collections/me/cards/{card_id} endpoint."""

    def test_returns_card_quantity(self, client: TestClient, auth_headers, mock_collection_service):
        """
        Test that endpoint returns quantity for owned card.

        Should return the card ID and quantity.
        """
        mock_collection_service.get_card_quantity = AsyncMock(return_value=4)

        response = client.get("/api/collections/me/cards/a1-001-bulbasaur", headers=auth_headers)

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["card_definition_id"] == "a1-001-bulbasaur"
        assert data["quantity"] == 4

    def test_returns_404_for_unowned_card(
        self, client: TestClient, auth_headers, mock_collection_service
    ):
        """
        Test that endpoint returns 404 for cards not in collection.

        Cards with quantity 0 should return 404.
        """
        mock_collection_service.get_card_quantity = AsyncMock(return_value=0)

        response = client.get("/api/collections/me/cards/unowned-card", headers=auth_headers)

        assert response.status_code == status.HTTP_404_NOT_FOUND

    def test_requires_authentication(self, unauthenticated_client: TestClient):
        """
        Test that endpoint requires authentication.

        Unauthenticated requests should return 401.
        """
        response = unauthenticated_client.get("/api/collections/me/cards/a1-001-bulbasaur")

        assert response.status_code == status.HTTP_401_UNAUTHORIZED


# =============================================================================
# POST /collections/admin/{user_id}/add Tests
# =============================================================================


class TestAdminAddCards:
    """Tests for POST /api/collections/admin/{user_id}/add endpoint.

    Admin endpoint requires X-Admin-API-Key header instead of user authentication.
    """

    @pytest.fixture
    def admin_app(self, mock_collection_service):
        """Create a test app with admin auth bypassed."""
        from app.api import deps as api_deps

        test_app = FastAPI()
        test_app.include_router(collections_router, prefix="/api")

        # Override admin auth to allow access
        async def override_verify_admin_token():
            return None

        def override_get_collection_service():
            return mock_collection_service

        test_app.dependency_overrides[api_deps.verify_admin_token] = override_verify_admin_token
        test_app.dependency_overrides[api_deps.get_collection_service] = (
            override_get_collection_service
        )

        yield test_app
        test_app.dependency_overrides.clear()

    @pytest.fixture
    def admin_client(self, admin_app):
        """Create a test client with admin auth bypassed."""
        return TestClient(admin_app)

    def test_adds_cards_to_collection(self, admin_client: TestClient, mock_collection_service):
        """
        Test that admin can add cards to user's collection.

        Should create/update collection entry and return it.
        """
        entry = make_collection_entry("a1-001-bulbasaur", 5, CardSource.GIFT)
        mock_collection_service.add_cards = AsyncMock(return_value=entry)

        user_id = str(uuid4())
        response = admin_client.post(
            f"/api/collections/admin/{user_id}/add",
            json={
                "card_definition_id": "a1-001-bulbasaur",
                "quantity": 5,
                "source": "gift",
            },
        )

        assert response.status_code == status.HTTP_201_CREATED
        data = response.json()
        assert data["card_definition_id"] == "a1-001-bulbasaur"
        assert data["quantity"] == 5

    def test_returns_400_for_invalid_card(self, admin_client: TestClient, mock_collection_service):
        """
        Test that endpoint returns 400 for invalid card ID.

        Non-existent card IDs should be rejected.
        """
        mock_collection_service.add_cards = AsyncMock(side_effect=ValueError("Invalid card ID"))

        user_id = str(uuid4())
        response = admin_client.post(
            f"/api/collections/admin/{user_id}/add",
            json={
                "card_definition_id": "invalid-card",
                "quantity": 1,
                "source": "gift",
            },
        )

        assert response.status_code == status.HTTP_400_BAD_REQUEST

    def test_requires_admin_api_key(self, unauthenticated_client: TestClient):
        """
        Test that endpoint returns 401 without admin API key header.

        Requests without X-Admin-API-Key header are rejected with 401.
        """
        user_id = str(uuid4())
        response = unauthenticated_client.post(
            f"/api/collections/admin/{user_id}/add",
            json={
                "card_definition_id": "a1-001-bulbasaur",
                "quantity": 1,
                "source": "gift",
            },
        )

        assert response.status_code == status.HTTP_401_UNAUTHORIZED
        assert "not authenticated" in response.json()["detail"].lower()

    def test_rejects_invalid_admin_api_key(self, unauthenticated_client: TestClient):
        """
        Test that endpoint returns 403 for invalid admin API key.

        Requests with wrong X-Admin-API-Key are rejected with 403.
        """
        user_id = str(uuid4())
        response = unauthenticated_client.post(
            f"/api/collections/admin/{user_id}/add",
            headers={"X-Admin-API-Key": "wrong-key"},
            json={
                "card_definition_id": "a1-001-bulbasaur",
                "quantity": 1,
                "source": "gift",
            },
        )

        # Returns 403 because the key is invalid (or not configured in test env)
        assert response.status_code == status.HTTP_403_FORBIDDEN
