"""Tests for anime metadata edit (PUT /api/anime/{anime_key}) endpoint."""
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from httpx import ASGITransport, AsyncClient

from src.server.fastapi_app import app
from src.server.services.auth_service import auth_service


@pytest.fixture
def reset_auth():
    """Reset auth state before each test."""
    auth_service._hash = None
    auth_service._failed = {}


@pytest.fixture
async def client():
    """Create async test client."""
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        yield ac


@pytest.fixture
async def authenticated_client(client):
    """Get authenticated client with Bearer token."""
    # Setup auth
    await client.post("/api/auth/setup", json={"master_password": "TestPass123!"})
    response = await client.post(
        "/api/auth/login", json={"password": "TestPass123!"}
    )
    token = response.json()["access_token"]
    client.headers["Authorization"] = f"Bearer {token}"
    return client


@pytest.fixture
def mock_db_session():
    """Create a mock async database session."""
    session = AsyncMock()
    session.commit = AsyncMock()
    session.flush = AsyncMock()
    session.refresh = AsyncMock()
    return session


@pytest.fixture
def mock_series_in_db():
    """Create a mock AnimeSeries DB record."""
    series = MagicMock()
    series.id = 1
    series.key = "test-anime"
    series.name = "Test Anime"
    series.tmdb_id = 1234
    series.tvdb_id = 5678
    series.folder = "Test Anime (2023)"
    return series


@pytest.fixture
def override_db_dependency(mock_db_session):
    """Override database session dependency."""
    from src.server.utils.dependencies import get_database_session

    app.dependency_overrides[get_database_session] = lambda: mock_db_session
    yield mock_db_session
    app.dependency_overrides.pop(get_database_session, None)


class TestUpdateAnimeMetadata:
    """Tests for PUT /api/anime/{anime_key}."""

    @pytest.mark.asyncio
    async def test_update_tmdb_id_success(
        self, reset_auth, authenticated_client, override_db_dependency, mock_series_in_db
    ):
        """Test successful tmdb_id update."""
        with patch(
            "src.server.api.anime.AnimeSeriesService.get_by_key",
            new_callable=AsyncMock,
            return_value=mock_series_in_db,
        ), patch(
            "src.server.api.anime.AnimeSeriesService.update",
            new_callable=AsyncMock,
        ) as mock_update:
            mock_series_in_db.tmdb_id = 9999
            mock_update.return_value = mock_series_in_db

            response = await authenticated_client.put(
                "/api/anime/test-anime",
                json={"tmdb_id": 9999},
            )

            assert response.status_code == 200
            data = response.json()
            assert data["tmdb_id"] == 9999
            assert data["message"] == "Metadata updated successfully"

    @pytest.mark.asyncio
    async def test_update_tvdb_id_success(
        self, reset_auth, authenticated_client, override_db_dependency, mock_series_in_db
    ):
        """Test successful tvdb_id update."""
        with patch(
            "src.server.api.anime.AnimeSeriesService.get_by_key",
            new_callable=AsyncMock,
            return_value=mock_series_in_db,
        ), patch(
            "src.server.api.anime.AnimeSeriesService.update",
            new_callable=AsyncMock,
        ) as mock_update:
            mock_series_in_db.tvdb_id = 7777
            mock_update.return_value = mock_series_in_db

            response = await authenticated_client.put(
                "/api/anime/test-anime",
                json={"tvdb_id": 7777},
            )

            assert response.status_code == 200
            data = response.json()
            assert data["tvdb_id"] == 7777

    @pytest.mark.asyncio
    async def test_update_key_success(
        self, reset_auth, authenticated_client, override_db_dependency, mock_series_in_db
    ):
        """Test successful key rename."""
        with patch(
            "src.server.api.anime.AnimeSeriesService.get_by_key",
            new_callable=AsyncMock,
        ) as mock_get:
            # First call finds the series, second call checks uniqueness (returns None)
            mock_get.side_effect = [mock_series_in_db, None]

            mock_series_in_db.key = "new-anime-key"
            with patch(
                "src.server.api.anime.AnimeSeriesService.update",
                new_callable=AsyncMock,
                return_value=mock_series_in_db,
            ):
                response = await authenticated_client.put(
                    "/api/anime/test-anime",
                    json={"key": "new-anime-key"},
                )

                assert response.status_code == 200
                data = response.json()
                assert data["key"] == "new-anime-key"

    @pytest.mark.asyncio
    async def test_update_key_conflict_409(
        self, reset_auth, authenticated_client, override_db_dependency, mock_series_in_db
    ):
        """Test key rename conflict returns 409."""
        existing_series = MagicMock()
        existing_series.key = "existing-key"

        with patch(
            "src.server.api.anime.AnimeSeriesService.get_by_key",
            new_callable=AsyncMock,
        ) as mock_get:
            # First call finds original series, second call finds conflict
            mock_get.side_effect = [mock_series_in_db, existing_series]

            response = await authenticated_client.put(
                "/api/anime/test-anime",
                json={"key": "existing-key"},
            )

            assert response.status_code == 409
            assert "already exists" in response.json()["detail"]

    @pytest.mark.asyncio
    async def test_update_key_invalid_chars_422(
        self, reset_auth, authenticated_client, override_db_dependency
    ):
        """Test key with invalid characters returns 422."""
        response = await authenticated_client.put(
            "/api/anime/test-anime",
            json={"key": "Invalid Key With Spaces!"},
        )

        assert response.status_code == 422

    @pytest.mark.asyncio
    async def test_update_key_empty_422(
        self, reset_auth, authenticated_client, override_db_dependency
    ):
        """Test empty key returns 422."""
        response = await authenticated_client.put(
            "/api/anime/test-anime",
            json={"key": ""},
        )

        assert response.status_code == 422

    @pytest.mark.asyncio
    async def test_update_unauthenticated_401(self, reset_auth, client):
        """Test unauthenticated access returns 401."""
        response = await client.put(
            "/api/anime/test-anime",
            json={"tmdb_id": 1234},
        )

        assert response.status_code == 401

    @pytest.mark.asyncio
    async def test_update_nonexistent_anime_404(
        self, reset_auth, authenticated_client, override_db_dependency
    ):
        """Test update of non-existent anime returns 404."""
        with patch(
            "src.server.api.anime.AnimeSeriesService.get_by_key",
            new_callable=AsyncMock,
            return_value=None,
        ):
            response = await authenticated_client.put(
                "/api/anime/nonexistent-key",
                json={"tmdb_id": 1234},
            )

            assert response.status_code == 404

    @pytest.mark.asyncio
    async def test_update_no_changes(
        self, reset_auth, authenticated_client, override_db_dependency, mock_series_in_db
    ):
        """Test sending empty body returns no-op response."""
        with patch(
            "src.server.api.anime.AnimeSeriesService.get_by_key",
            new_callable=AsyncMock,
            return_value=mock_series_in_db,
        ):
            response = await authenticated_client.put(
                "/api/anime/test-anime",
                json={},
            )

            assert response.status_code == 200
            assert response.json()["message"] == "No changes"

    @pytest.mark.asyncio
    async def test_update_negative_tmdb_id_422(
        self, reset_auth, authenticated_client, override_db_dependency
    ):
        """Test negative TMDB ID returns 422."""
        response = await authenticated_client.put(
            "/api/anime/test-anime",
            json={"tmdb_id": -5},
        )

        assert response.status_code == 422
