from __future__ import annotations

import os
import tempfile

os.environ.setdefault("ROOT_APP_DIR", tempfile.mkdtemp())

from unittest.mock import AsyncMock, MagicMock

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

from api.v1.routes.plex_auth import router as auth_router
from api.v1.schemas.settings import PlexOAuthPinResponse, PlexOAuthPollResponse
from core.dependencies import get_plex_repository, get_preferences_service
from core.exceptions import PlexApiError


@pytest.fixture
def mock_preferences():
    mock = MagicMock()
    mock.get_setting = MagicMock(return_value="existing-client-id")
    mock.save_setting = MagicMock()
    mock.get_or_create_setting = MagicMock(return_value="existing-client-id")
    return mock


@pytest.fixture
def mock_repo():
    mock = MagicMock()
    pin = MagicMock()
    pin.id = 12345
    pin.code = "ABCD"
    mock.create_oauth_pin = AsyncMock(return_value=pin)
    mock.poll_oauth_pin = AsyncMock(return_value=None)
    return mock


@pytest.fixture
def auth_client(mock_preferences, mock_repo):
    app = FastAPI()
    app.include_router(auth_router)
    app.dependency_overrides[get_preferences_service] = lambda: mock_preferences
    app.dependency_overrides[get_plex_repository] = lambda: mock_repo
    return TestClient(app)


class TestCreatePlexPin:
    def test_creates_pin(self, auth_client):
        resp = auth_client.post("/plex/auth/pin")
        assert resp.status_code == 200
        data = resp.json()
        assert data["pin_id"] == 12345
        assert data["pin_code"] == "ABCD"
        assert "auth_url" in data
        assert "clientID=existing-client-id" in data["auth_url"]

    def test_generates_client_id_if_missing(self, auth_client, mock_preferences):
        import uuid
        generated = str(uuid.uuid4())
        mock_preferences.get_or_create_setting = MagicMock(return_value=generated)
        resp = auth_client.post("/plex/auth/pin")
        assert resp.status_code == 200
        mock_preferences.get_or_create_setting.assert_called_once()
        call_args = mock_preferences.get_or_create_setting.call_args
        assert call_args[0][0] == "plex_client_id"

    def test_502_on_plex_error(self, auth_client, mock_repo):
        mock_repo.create_oauth_pin = AsyncMock(side_effect=PlexApiError("timeout"))
        resp = auth_client.post("/plex/auth/pin")
        assert resp.status_code == 502

    def test_500_on_unexpected_error(self, auth_client, mock_repo):
        mock_repo.create_oauth_pin = AsyncMock(side_effect=RuntimeError("bad"))
        resp = auth_client.post("/plex/auth/pin")
        assert resp.status_code == 500


class TestPollPlexPin:
    def test_poll_pending(self, auth_client):
        resp = auth_client.get("/plex/auth/poll?pin_id=12345")
        assert resp.status_code == 200
        data = resp.json()
        assert data["completed"] is False
        assert data["auth_token"] == ""

    def test_poll_completed(self, auth_client, mock_repo):
        mock_repo.poll_oauth_pin = AsyncMock(return_value="auth-token-abc")
        resp = auth_client.get("/plex/auth/poll?pin_id=12345")
        assert resp.status_code == 200
        data = resp.json()
        assert data["completed"] is True
        assert data["auth_token"] == "auth-token-abc"

    def test_poll_502_on_error(self, auth_client, mock_repo):
        mock_repo.poll_oauth_pin = AsyncMock(side_effect=Exception("network error"))
        resp = auth_client.get("/plex/auth/poll?pin_id=12345")
        assert resp.status_code == 502
