"""Tests for starter deck API endpoints.

Tests the starter deck selection endpoints in the users API with proper mocking.
"""

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.users import router as users_router
from app.db.models import User
from app.repositories.protocols import DeckEntry
from app.services.deck_service import DeckService
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_deck_service():
    """Create a mock DeckService."""
    return MagicMock(spec=DeckService)


def make_deck_entry(
    name: str = "Starter Deck",
    starter_type: str = "grass",
) -> DeckEntry:
    """Create a DeckEntry for testing."""
    return DeckEntry(
        id=uuid4(),
        user_id=uuid4(),
        name=name,
        cards={"a1-001-bulbasaur": 4},
        energy_cards={"grass": 20},
        is_valid=True,
        validation_errors=None,
        is_starter=True,
        starter_type=starter_type,
        description="A starter deck",
        created_at=datetime.now(UTC),
        updated_at=datetime.now(UTC),
    )


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

    # Override dependencies
    async def override_get_current_user():
        return test_user

    def override_get_deck_service():
        return mock_deck_service

    test_app.dependency_overrides[api_deps.get_current_user] = override_get_current_user
    test_app.dependency_overrides[api_deps.get_deck_service] = override_get_deck_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(users_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)


# =============================================================================
# GET /users/me/starter-status Tests
# =============================================================================


class TestGetStarterStatus:
    """Tests for GET /api/users/me/starter-status endpoint."""

    def test_returns_no_starter_for_new_user(
        self, client: TestClient, auth_headers, mock_deck_service
    ):
        """
        Test that new users have no starter deck selected.

        has_starter should be False and starter_type should be None.
        """
        mock_deck_service.has_starter_deck = AsyncMock(return_value=(False, None))

        response = client.get("/api/users/me/starter-status", headers=auth_headers)

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["has_starter"] is False
        assert data["starter_type"] is None

    def test_returns_starter_type_when_selected(
        self, client: TestClient, auth_headers, mock_deck_service
    ):
        """
        Test that endpoint returns starter type when user has selected one.

        has_starter should be True with the selected starter_type.
        """
        mock_deck_service.has_starter_deck = AsyncMock(return_value=(True, "grass"))

        response = client.get("/api/users/me/starter-status", headers=auth_headers)

        assert response.status_code == status.HTTP_200_OK
        data = response.json()
        assert data["has_starter"] is True
        assert data["starter_type"] == "grass"

    def test_requires_authentication(self, unauthenticated_client: TestClient):
        """Test that endpoint returns 401 without authentication."""
        response = unauthenticated_client.get("/api/users/me/starter-status")
        assert response.status_code == status.HTTP_401_UNAUTHORIZED


# =============================================================================
# POST /users/me/starter-deck Tests
# =============================================================================


class TestSelectStarterDeck:
    """Tests for POST /api/users/me/starter-deck endpoint."""

    def test_creates_starter_deck(self, client: TestClient, auth_headers, mock_deck_service):
        """
        Test that endpoint creates starter deck and grants cards atomically.

        Uses the combined select_and_grant_starter_deck method.
        """
        mock_deck_service.select_and_grant_starter_deck = AsyncMock(
            return_value=make_deck_entry("Forest Guardian", "grass")
        )

        response = client.post(
            "/api/users/me/starter-deck",
            headers=auth_headers,
            json={"starter_type": "grass"},
        )

        assert response.status_code == status.HTTP_201_CREATED
        data = response.json()
        assert data["is_starter"] is True
        assert data["starter_type"] == "grass"

    def test_returns_400_if_already_selected(
        self, client: TestClient, auth_headers, mock_deck_service
    ):
        """
        Test that endpoint returns 400 if starter already selected.

        Users can only select a starter deck once.
        """
        from app.services.deck_service import StarterAlreadySelectedError

        mock_deck_service.select_and_grant_starter_deck = AsyncMock(
            side_effect=StarterAlreadySelectedError("Starter deck already selected: fire")
        )

        response = client.post(
            "/api/users/me/starter-deck",
            headers=auth_headers,
            json={"starter_type": "grass"},
        )

        assert response.status_code == status.HTTP_400_BAD_REQUEST
        assert "already selected" in response.json()["detail"].lower()

    def test_returns_400_for_invalid_starter_type(
        self, client: TestClient, auth_headers, mock_deck_service
    ):
        """
        Test that endpoint returns 400 for invalid starter type.

        Only valid starter types should be accepted.
        """
        mock_deck_service.select_and_grant_starter_deck = AsyncMock(
            side_effect=ValueError("Invalid starter type: invalid")
        )

        response = client.post(
            "/api/users/me/starter-deck",
            headers=auth_headers,
            json={"starter_type": "invalid"},
        )

        assert response.status_code == status.HTTP_400_BAD_REQUEST

    def test_accepts_custom_deck_config(self, client: TestClient, auth_headers, mock_deck_service):
        """
        Test that endpoint accepts custom DeckConfig from frontend.

        The backend is stateless - rules come from the request.
        """
        mock_deck_service.select_and_grant_starter_deck = AsyncMock(return_value=make_deck_entry())

        response = client.post(
            "/api/users/me/starter-deck",
            headers=auth_headers,
            json={
                "starter_type": "grass",
                "deck_config": {"min_size": 40, "energy_deck_size": 20},
            },
        )

        assert response.status_code == status.HTTP_201_CREATED

    def test_requires_authentication(self, unauthenticated_client: TestClient):
        """Test that endpoint returns 401 without authentication."""
        response = unauthenticated_client.post(
            "/api/users/me/starter-deck",
            json={"starter_type": "grass"},
        )
        assert response.status_code == status.HTTP_401_UNAUTHORIZED
