"""Tests for NFO diagnostics and repair API endpoints."""
from unittest.mock import AsyncMock, Mock, 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(autouse=True)
def reset_auth():
    """Reset authentication state before each test."""
    original_hash = auth_service._hash
    auth_service._hash = None
    auth_service._failed.clear()
    yield
    auth_service._hash = original_hash
    auth_service._failed.clear()


@pytest.fixture
async def client():
    """Create an 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):
    """Create an authenticated test client with token."""
    await client.post(
        "/api/auth/setup",
        json={"master_password": "TestPassword123!"}
    )
    response = await client.post(
        "/api/auth/login",
        json={"password": "TestPassword123!"}
    )
    token = response.json()["access_token"]
    client.headers.update({"Authorization": f"Bearer {token}"})
    yield client


@pytest.fixture
def mock_series_app():
    """Create mock series app with one test series."""
    app_mock = Mock()
    serie = Mock()
    serie.key = "test-anime"
    serie.folder = "Test Anime (2024)"
    serie.name = "Test Anime"
    serie.ensure_folder_with_year = Mock(return_value="Test Anime (2024)")

    list_manager = Mock()
    list_manager.GetList = Mock(return_value=[serie])
    app_mock.list = list_manager

    return app_mock


@pytest.fixture
def mock_nfo_service():
    """Create mock NFO service."""
    service = Mock()
    service.check_nfo_exists = AsyncMock(return_value=False)
    service.create_tvshow_nfo = AsyncMock(return_value="/path/to/tvshow.nfo")
    service.update_tvshow_nfo = AsyncMock(return_value="/path/to/tvshow.nfo")
    return service


@pytest.fixture
def override_dependencies(mock_series_app, mock_nfo_service):
    """Override dependencies for NFO tests."""
    from src.server.api.nfo import get_nfo_service
    from src.server.utils.dependencies import get_series_app

    app.dependency_overrides[get_series_app] = lambda: mock_series_app
    app.dependency_overrides[get_nfo_service] = lambda: mock_nfo_service

    yield

    if get_series_app in app.dependency_overrides:
        del app.dependency_overrides[get_series_app]
    if get_nfo_service in app.dependency_overrides:
        del app.dependency_overrides[get_nfo_service]


class TestNfoDiagnostics:
    """Tests for GET /api/nfo/{serie_key}/diagnostics."""

    @pytest.mark.asyncio
    async def test_diagnostics_complete_nfo(
        self, authenticated_client, override_dependencies
    ):
        """Test diagnostics with complete NFO returns no missing tags."""
        with patch(
            "src.server.api.nfo.Path.exists", return_value=True
        ), patch(
            "src.server.api.nfo.find_missing_tags", return_value=[]
        ):
            response = await authenticated_client.get(
                "/api/nfo/test-anime/diagnostics"
            )

            assert response.status_code == 200
            data = response.json()
            assert data["has_nfo"] is True
            assert data["missing_tags"] == []
            assert len(data["required_tags"]) > 0

    @pytest.mark.asyncio
    async def test_diagnostics_missing_tags(
        self, authenticated_client, override_dependencies
    ):
        """Test diagnostics with missing tags returns them."""
        with patch(
            "src.server.api.nfo.Path.exists", return_value=True
        ), patch(
            "src.server.api.nfo.find_missing_tags",
            return_value=["plot", "genre", "actor/name"],
        ):
            response = await authenticated_client.get(
                "/api/nfo/test-anime/diagnostics"
            )

            assert response.status_code == 200
            data = response.json()
            assert data["has_nfo"] is True
            assert "plot" in data["missing_tags"]
            assert "genre" in data["missing_tags"]
            assert len(data["missing_tags"]) == 3

    @pytest.mark.asyncio
    async def test_diagnostics_no_nfo_file(
        self, authenticated_client, override_dependencies
    ):
        """Test diagnostics when no NFO exists returns all tags as missing."""
        with patch("src.server.api.nfo.Path") as MockPath:
            # Make nfo_path.exists() return False
            mock_path_instance = Mock()
            mock_path_instance.exists.return_value = False
            mock_path_instance.__truediv__ = Mock(return_value=mock_path_instance)
            MockPath.return_value = mock_path_instance

            response = await authenticated_client.get(
                "/api/nfo/test-anime/diagnostics"
            )

            assert response.status_code == 200
            data = response.json()
            assert data["has_nfo"] is False
            assert len(data["missing_tags"]) > 0
            # All required tags should be listed as missing
            assert data["missing_tags"] == data["required_tags"]

    @pytest.mark.asyncio
    async def test_diagnostics_nonexistent_series_404(
        self, authenticated_client, override_dependencies, mock_series_app
    ):
        """Test diagnostics for non-existent series returns 404."""
        # Override to return empty list
        mock_series_app.list.GetList.return_value = []

        response = await authenticated_client.get(
            "/api/nfo/nonexistent-key/diagnostics"
        )

        assert response.status_code == 404

    @pytest.mark.asyncio
    async def test_diagnostics_unauthenticated_401(self, client):
        """Test diagnostics requires authentication."""
        response = await client.get("/api/nfo/test-anime/diagnostics")
        # May return 401 or 503 depending on NFO service availability
        assert response.status_code in (401, 503)


class TestNfoRepair:
    """Tests for POST /api/nfo/{serie_key}/repair."""

    @pytest.mark.asyncio
    async def test_repair_success(
        self, authenticated_client, override_dependencies
    ):
        """Test successful NFO repair."""
        with patch("src.server.api.nfo.Path") as MockPath:
            mock_path = Mock()
            mock_path.exists.return_value = True
            mock_path.__truediv__ = Mock(return_value=mock_path)
            MockPath.return_value = mock_path

            with patch(
                "src.server.api.nfo.find_missing_tags",
                return_value=["plot", "genre"],
            ), patch(
                "src.server.api.nfo.NfoRepairService"
            ) as MockRepairService:
                mock_instance = Mock()
                mock_instance.repair_series = AsyncMock(return_value=True)
                MockRepairService.return_value = mock_instance

                response = await authenticated_client.post(
                    "/api/nfo/test-anime/repair", json={}
                )

                assert response.status_code == 200
                data = response.json()
                assert data["success"] is True
                assert "2" in data["message"]  # "Fixed 2 missing tags"
                assert "plot" in data["repaired_tags"]
                assert "genre" in data["repaired_tags"]

    @pytest.mark.asyncio
    async def test_repair_already_complete(
        self, authenticated_client, override_dependencies
    ):
        """Test repair when NFO is already complete."""
        with patch("src.server.api.nfo.Path") as MockPath:
            mock_path = Mock()
            mock_path.exists.return_value = True
            mock_path.__truediv__ = Mock(return_value=mock_path)
            MockPath.return_value = mock_path

            with patch(
                "src.server.api.nfo.find_missing_tags", return_value=[]
            ), patch(
                "src.server.api.nfo.NfoRepairService"
            ) as MockRepairService:
                mock_instance = Mock()
                mock_instance.repair_series = AsyncMock(return_value=False)
                MockRepairService.return_value = mock_instance

                response = await authenticated_client.post(
                    "/api/nfo/test-anime/repair", json={}
                )

                assert response.status_code == 200
                data = response.json()
                assert data["success"] is True
                assert "already complete" in data["message"]

    @pytest.mark.asyncio
    async def test_repair_creates_new_nfo(
        self, authenticated_client, override_dependencies, mock_nfo_service
    ):
        """Test repair when no NFO exists creates a new one."""
        with patch("src.server.api.nfo.Path") as MockPath:
            mock_path = Mock()
            mock_path.exists.return_value = False
            mock_path.__truediv__ = Mock(return_value=mock_path)
            MockPath.return_value = mock_path

            with patch(
                "src.server.api.nfo.REQUIRED_TAGS",
                {"./title": "title", "./plot": "plot"},
            ):
                response = await authenticated_client.post(
                    "/api/nfo/test-anime/repair", json={}
                )

                assert response.status_code == 200
                data = response.json()
                assert data["success"] is True
                mock_nfo_service.create_tvshow_nfo.assert_awaited_once()

    @pytest.mark.asyncio
    async def test_repair_nonexistent_series_404(
        self, authenticated_client, override_dependencies, mock_series_app
    ):
        """Test repair for non-existent series returns 404."""
        mock_series_app.list.GetList.return_value = []

        response = await authenticated_client.post(
            "/api/nfo/nonexistent-key/repair", json={}
        )

        assert response.status_code == 404

    @pytest.mark.asyncio
    async def test_repair_unauthenticated_401(self, client):
        """Test repair requires authentication."""
        response = await client.post("/api/nfo/test-anime/repair", json={})
        assert response.status_code in (401, 503)

    @pytest.mark.asyncio
    async def test_repair_tmdb_api_failure(
        self, authenticated_client, override_dependencies
    ):
        """Test repair handles TMDB API failure gracefully."""
        from src.core.services.tmdb_client import TMDBAPIError

        with patch("src.server.api.nfo.Path") as MockPath:
            mock_path = Mock()
            mock_path.exists.return_value = True
            mock_path.__truediv__ = Mock(return_value=mock_path)
            MockPath.return_value = mock_path

            with patch(
                "src.server.api.nfo.find_missing_tags",
                return_value=["plot"],
            ), patch(
                "src.server.api.nfo.NfoRepairService"
            ) as MockRepairService:
                mock_instance = Mock()
                mock_instance.repair_series = AsyncMock(
                    side_effect=TMDBAPIError("No TMDB ID found")
                )
                MockRepairService.return_value = mock_instance

                response = await authenticated_client.post(
                    "/api/nfo/test-anime/repair", json={}
                )

                assert response.status_code == 400
                assert "Cannot repair NFO" in response.json()["detail"]
