"""Integration tests for configuration API endpoints."""

import tempfile
from pathlib import Path
from unittest.mock import AsyncMock, patch

import pytest
from httpx import ASGITransport, AsyncClient

from src.server.fastapi_app import app
from src.server.models.config import AppConfig
from src.server.services.auth_service import auth_service
from src.server.services.config_service import ConfigService


@pytest.fixture
def temp_config_dir():
    """Create temporary directory for test config files."""
    with tempfile.TemporaryDirectory() as tmpdir:
        yield Path(tmpdir)


@pytest.fixture
def config_service(temp_config_dir):
    """Create ConfigService instance with temporary paths."""
    config_path = temp_config_dir / "config.json"
    backup_dir = temp_config_dir / "backups"
    return ConfigService(
        config_path=config_path, backup_dir=backup_dir, max_backups=3
    )


@pytest.fixture
def mock_config_service(config_service):
    """Mock get_config_service to return test instance."""
    with patch(
        "src.server.api.config.get_config_service",
        return_value=config_service
    ):
        yield config_service


@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():
    """Create authenticated async test client."""
    # Setup auth if not configured
    if not auth_service.is_configured():
        auth_service.setup_master_password("TestPass123!")
    
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        # Login to get token
        r = await ac.post("/api/auth/login", json={"password": "TestPass123!"})
        if r.status_code == 200:
            token = r.json()["access_token"]
            ac.headers["Authorization"] = f"Bearer {token}"
        yield ac


@pytest.mark.asyncio
async def test_get_config_public(authenticated_client, mock_config_service):
    """Test getting configuration."""
    resp = await authenticated_client.get("/api/config")
    assert resp.status_code == 200
    data = resp.json()
    assert "name" in data
    assert "data_dir" in data


@pytest.mark.asyncio
async def test_validate_config(authenticated_client, mock_config_service):
    """Test configuration validation."""
    cfg = {
        "name": "Aniworld",
        "data_dir": "data",
        "scheduler": {"enabled": True, "interval_minutes": 30},
        "logging": {"level": "INFO"},
        "backup": {"enabled": False},
        "other": {},
    }
    resp = await authenticated_client.post("/api/config/validate", json=cfg)
    assert resp.status_code == 200
    body = resp.json()
    assert body.get("valid") is True


@pytest.mark.asyncio
async def test_validate_invalid_config(authenticated_client, mock_config_service):
    """Test validation of invalid configuration."""
    cfg = {
        "name": "Aniworld",
        "backup": {"enabled": True, "path": None},  # Invalid
    }
    resp = await authenticated_client.post("/api/config/validate", json=cfg)
    assert resp.status_code == 200
    body = resp.json()
    assert body.get("valid") is False
    assert len(body.get("errors", [])) > 0


@pytest.mark.asyncio
async def test_update_config_unauthorized(client):
    """Test that update requires authentication."""
    update = {"scheduler": {"enabled": False}}
    resp = await client.put("/api/config", json=update)
    assert resp.status_code in (401, 422)


@pytest.mark.asyncio
async def test_list_backups(authenticated_client, mock_config_service):
    """Test listing configuration backups."""
    # Create a sample config first
    sample_config = AppConfig(name="TestApp", data_dir="test_data")
    mock_config_service.save_config(sample_config, create_backup=False)
    mock_config_service.create_backup(name="test_backup")

    resp = await authenticated_client.get("/api/config/backups")
    assert resp.status_code == 200
    backups = resp.json()
    assert isinstance(backups, list)
    if len(backups) > 0:
        assert "name" in backups[0]
        assert "size_bytes" in backups[0]
        assert "created_at" in backups[0]


@pytest.mark.asyncio
async def test_create_backup(authenticated_client, mock_config_service):
    """Test creating a configuration backup."""
    # Create a sample config first
    sample_config = AppConfig(name="TestApp", data_dir="test_data")
    mock_config_service.save_config(sample_config, create_backup=False)

    resp = await authenticated_client.post("/api/config/backups")
    assert resp.status_code == 200
    data = resp.json()
    assert "name" in data
    assert "message" in data


@pytest.mark.asyncio
async def test_restore_backup(authenticated_client, mock_config_service):
    """Test restoring configuration from backup."""
    # Create initial config and backup
    sample_config = AppConfig(name="TestApp", data_dir="test_data")
    mock_config_service.save_config(sample_config, create_backup=False)
    mock_config_service.create_backup(name="restore_test")

    # Modify config
    sample_config.name = "Modified"
    mock_config_service.save_config(sample_config, create_backup=False)

    # Restore from backup
    resp = await authenticated_client.post("/api/config/backups/restore_test.json/restore")
    assert resp.status_code == 200
    data = resp.json()
    assert data["name"] == "TestApp"  # Original name restored


@pytest.mark.asyncio
async def test_delete_backup(authenticated_client, mock_config_service):
    """Test deleting a configuration backup."""
    # Create a sample config and backup
    sample_config = AppConfig(name="TestApp", data_dir="test_data")
    mock_config_service.save_config(sample_config, create_backup=False)
    mock_config_service.create_backup(name="delete_test")

    resp = await authenticated_client.delete("/api/config/backups/delete_test.json")
    assert resp.status_code == 200
    data = resp.json()
    assert "deleted successfully" in data["message"]


@pytest.mark.asyncio
async def test_config_persistence(authenticated_client, mock_config_service):
    """Test end-to-end configuration persistence."""
    # Get initial config
    resp = await authenticated_client.get("/api/config")
    assert resp.status_code == 200
    initial = resp.json()

    # Validate it can be loaded again
    resp2 = await authenticated_client.get("/api/config")
    assert resp2.status_code == 200
    assert resp2.json() == initial


@pytest.mark.asyncio
async def test_tmdb_validation_endpoint_exists(authenticated_client):
    """Test TMDB validation endpoint exists and is callable."""
    resp = await authenticated_client.post(
        "/api/config/tmdb/validate",
        json={"api_key": ""}
    )
    
    assert resp.status_code == 200
    data = resp.json()
    assert "valid" in data
    assert "message" in data
    assert data["valid"] is False  # Empty key should be invalid
    assert "required" in data["message"].lower()


@pytest.mark.asyncio
async def test_update_config_with_anime_directory_starts_scheduler(
    authenticated_client, mock_config_service
):
    """PUT /api/config with anime_directory syncs and starts scheduler."""
    mock_scheduler = AsyncMock()
    
    with patch("src.server.services.scheduler_service.get_scheduler_service") as mock_sched_fn:
        mock_sched_fn.return_value = mock_scheduler

        with patch("src.config.settings.settings") as mock_settings:
            mock_settings.anime_directory = None

            resp = await authenticated_client.put(
                "/api/config",
                json={"other": {"anime_directory": "/data/anime"}},
            )

            assert resp.status_code == 200
            mock_scheduler.ensure_started.assert_called_once()


@pytest.mark.asyncio
async def test_update_config_without_anime_directory_does_not_start_scheduler(
    authenticated_client, mock_config_service
):
    """PUT /api/config without new anime_directory does not call scheduler.ensure_started()."""
    mock_scheduler = AsyncMock()
    
    with patch("src.server.services.scheduler_service.get_scheduler_service") as mock_sched_fn:
        mock_sched_fn.return_value = mock_scheduler

        with patch("src.config.settings.settings") as mock_settings:
            mock_settings.anime_directory = "/already/set"

            resp = await authenticated_client.put(
                "/api/config", json={"other": {}}
            )

            assert resp.status_code == 200
            mock_scheduler.ensure_started.assert_not_called()
