"""Integration tests for complete download flow.

This module tests the end-to-end download flow including:
- Adding episodes to the queue
- Queue status updates
- Download processing
- Progress tracking
- Queue control operations (pause, resume, clear)
- Error handling and retries
- WebSocket notifications
"""
import asyncio
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List
from unittest.mock import AsyncMock, Mock

import pytest
from httpx import ASGITransport, AsyncClient

from src.server.fastapi_app import app
from src.server.models.download import (
    DownloadPriority,
    DownloadStatus,
    EpisodeIdentifier,
)
from src.server.services.anime_service import AnimeService
from src.server.services.auth_service import auth_service
from src.server.services.config_service import get_config_service
from src.server.services.download_service import DownloadService
from src.server.services.progress_service import get_progress_service
from src.server.services.websocket_service import get_websocket_service


@pytest.fixture(autouse=True)
def setup_temp_config(tmp_path):
    """Setup temporary config directory for tests."""
    config_service = get_config_service()
    original_path = config_service.config_path
    original_backup_dir = config_service.backup_dir
    
    # Set temporary paths
    temp_data = tmp_path / "data"
    temp_data.mkdir(exist_ok=True)
    config_service.config_path = temp_data / "config.json"
    config_service.backup_dir = temp_data / "config_backups"
    config_service.backup_dir.mkdir(exist_ok=True)
    
    yield
    
    # Restore original paths
    config_service.config_path = original_path
    config_service.backup_dir = original_backup_dir


@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."""
    # Setup master password
    await client.post(
        "/api/auth/setup",
        json={"master_password": "TestPassword123!"}
    )
    
    # Login to get token
    response = await client.post(
        "/api/auth/login",
        json={"password": "TestPassword123!"}
    )
    token = response.json()["access_token"]
    
    # Add token to default headers
    client.headers.update({"Authorization": f"Bearer {token}"})
    yield client


@pytest.fixture
def mock_series_app():
    """Mock SeriesApp for testing."""
    app_mock = Mock()
    app_mock.series_list = []
    app_mock.search = Mock(return_value=[])
    app_mock.ReScan = Mock()
    app_mock.download = Mock(return_value=True)
    return app_mock


@pytest.fixture
def mock_anime_service(mock_series_app, tmp_path):
    """Create a mock AnimeService."""
    # Create a temporary directory for the service
    test_dir = tmp_path / "anime"
    test_dir.mkdir()
    
    # Create AnimeService with the mocked SeriesApp
    service = AnimeService(series_app=mock_series_app)
    service.download = AsyncMock(return_value=True)
    return service


@pytest.fixture
def temp_queue_file(tmp_path):
    """Create a temporary queue persistence file."""
    return str(tmp_path / "test_queue.json")


class TestDownloadFlowEndToEnd:
    """Test complete download flow from queue addition to completion."""

    async def test_add_episodes_to_queue(self, authenticated_client, mock_anime_service):
        """Test adding episodes to the download queue."""
        # Add episodes to queue
        response = await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": "test-series-1",
                "serie_folder": "Test Anime Series (2024)",
                "serie_name": "Test Anime Series",
                "episodes": [
                    {"season": 1, "episode": 1, "title": "Episode 1"},
                    {"season": 1, "episode": 2, "title": "Episode 2"},
                ],
                "priority": "normal"
            }
        )
        
        assert response.status_code == 201
        data = response.json()
        
        # Verify response structure
        assert data["status"] == "success"
        assert "item_ids" in data
        assert len(data["item_ids"]) == 2
        assert "message" in data

    async def test_queue_status_after_adding_items(self, authenticated_client):
        """Test retrieving queue status after adding items."""
        # Add episodes to queue
        await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": "test-series-2",
                "serie_folder": "Another Series (2024)",
                "serie_name": "Another Series",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "high"
            }
        )
        
        # Get queue status
        response = await authenticated_client.get("/api/queue/status")
        
        assert response.status_code in [200, 503]
        
        if response.status_code == 200:
            data = response.json()
            
            # Verify response structure (status and statistics at top level)
            assert "status" in data
            assert "statistics" in data
            
            # Verify status fields
            status_data = data["status"]
            assert "is_running" in status_data
            assert "is_paused" in status_data
            assert "pending_queue" in status_data
            assert "active_downloads" in status_data
            assert "completed_downloads" in status_data
            assert "failed_downloads" in status_data

    async def test_add_with_different_priorities(self, authenticated_client):
        """Test adding episodes with different priority levels."""
        priorities = ["high", "normal", "low"]
        
        for priority in priorities:
            response = await authenticated_client.post(
                "/api/queue/add",
                json={
                    "serie_id": f"series-{priority}",
                    "serie_folder": f"Series {priority.title()} (2024)",
                    "serie_name": f"Series {priority.title()}",
                    "episodes": [{"season": 1, "episode": 1}],
                    "priority": priority
                }
            )
            
            assert response.status_code in [201, 503]

    async def test_validation_error_for_empty_episodes(self, authenticated_client):
        """Test validation error when no episodes are specified."""
        response = await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": "test-series",
                "serie_folder": "Test Series (2024)",
                "serie_name": "Test Series",
                "episodes": [],
                "priority": "normal"
            }
        )
        
        assert response.status_code == 400
        data = response.json()
        # API returns 'message' for error responses
        assert "message" in data

    async def test_validation_error_for_invalid_priority(self, authenticated_client):
        """Test validation error for invalid priority level."""
        response = await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": "test-series",
                "serie_folder": "Test Series (2024)",
                "serie_name": "Test Series",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "invalid"
            }
        )
        
        assert response.status_code == 422  # Validation error


class TestQueueControlOperations:
    """Test queue control operations (start, pause, resume, clear)."""

    async def test_start_queue_processing(self, authenticated_client):
        """Test starting the queue processor."""
        response = await authenticated_client.post("/api/queue/start")
        
        # 200 = started, 400 = empty queue, 503 = service unavailable
        assert response.status_code in [200, 400, 503]
        
        if response.status_code == 200:
            data = response.json()
            assert data["status"] == "success"

    async def test_clear_completed_downloads(self, authenticated_client):
        """Test clearing completed downloads from the queue."""
        response = await authenticated_client.delete("/api/queue/completed")
        
        assert response.status_code in [200, 503]
        
        if response.status_code == 200:
            data = response.json()
            assert data["status"] == "success"


class TestQueueItemOperations:
    """Test operations on individual queue items."""

    async def test_remove_item_from_queue(self, authenticated_client):
        """Test removing a specific item from the queue."""
        # First add an item
        add_response = await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": "test-series",
                "serie_folder": "Test Series (2024)",
                "serie_name": "Test Series",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "normal"
            }
        )
        
        if add_response.status_code == 201:
            item_id = add_response.json()["item_ids"][0]
            
            # Remove the item
            response = await authenticated_client.delete(f"/api/queue/items/{item_id}")
            
            assert response.status_code in [200, 404, 503]

    async def test_retry_failed_item(self, authenticated_client):
        """Test retrying a failed download item."""
        # This would typically require a failed item to exist
        # For now, test the endpoint with a dummy ID
        response = await authenticated_client.post("/api/queue/items/dummy-id/retry")
        
        # Should return 404 if item doesn't exist, or 503 if unavailable
        assert response.status_code in [200, 404, 503]


class TestDownloadProgressTracking:
    """Test progress tracking during downloads."""

    async def test_queue_status_includes_progress(self, authenticated_client):
        """Test that queue status includes progress information."""
        # Add an item
        await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": "test-series",
                "serie_folder": "Test Series (2024)",
                "serie_name": "Test Series",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "normal"
            }
        )
        
        # Get status
        response = await authenticated_client.get("/api/queue/status")
        
        assert response.status_code in [200, 503]
        
        if response.status_code == 200:
            data = response.json()
            # Updated for new nested response format
            assert "status" in data
            status_data = data["status"]
            assert "active_downloads" in status_data
            
            # Check that items can have progress
            for item in status_data.get("active_downloads", []):
                if "progress" in item and item["progress"]:
                    assert "percent" in item["progress"]
                    assert "downloaded_mb" in item["progress"]
                    assert "total_mb" in item["progress"]

    async def test_queue_statistics(self, authenticated_client):
        """Test that queue statistics are calculated correctly."""
        response = await authenticated_client.get("/api/queue/status")
        
        assert response.status_code in [200, 503]
        
        if response.status_code == 200:
            data = response.json()
            assert "statistics" in data
            
            stats = data["statistics"]
            assert "total_items" in stats
            assert "pending_count" in stats
            assert "active_count" in stats
            assert "completed_count" in stats
            assert "failed_count" in stats
            # Note: success_rate not currently in QueueStats model


class TestErrorHandlingAndRetries:
    """Test error handling and retry mechanisms."""

    async def test_handle_download_failure(self, authenticated_client):
        """Test handling of download failures."""
        # This would require mocking a failure scenario
        # For integration testing, we verify the error handling structure
        
        # Add an item that might fail
        response = await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": "invalid-series",
                "serie_folder": "Invalid Series (2024)",
                "serie_name": "Invalid Series",
                "episodes": [{"season": 99, "episode": 99}],
                "priority": "normal"
            }
        )
        
        # The system should handle the request gracefully
        assert response.status_code in [201, 400, 503]

    async def test_retry_count_increments(self, authenticated_client):
        """Test that retry count increments on failures."""
        # Add a potentially failing item
        add_response = await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": "test-series",
                "serie_folder": "Test Series (2024)",
                "serie_name": "Test Series",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "normal"
            }
        )
        
        if add_response.status_code == 201:
            # Get queue status to check retry count
            status_response = await authenticated_client.get(
                "/api/queue/status"
            )

            if status_response.status_code == 200:
                data = status_response.json()
                # Verify structure includes retry_count field
                # Updated to match new response structure
                for item_list in [
                    data.get("pending_queue", []),
                    data.get("failed_downloads", [])
                ]:
                    for item in item_list:
                        assert "retry_count" in item


class TestAuthenticationRequirements:
    """Test that download endpoints require authentication."""

    async def test_queue_status_requires_auth(self, client):
        """Test that queue status endpoint requires authentication."""
        response = await client.get("/api/queue/status")
        assert response.status_code == 401

    async def test_add_to_queue_requires_auth(self, client):
        """Test that add to queue endpoint requires authentication."""
        response = await client.post(
            "/api/queue/add",
            json={
                "serie_id": "test-series",
                "serie_folder": "Test Series (2024)",
                "serie_name": "Test Series",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "normal"
            }
        )
        assert response.status_code == 401

    async def test_queue_control_requires_auth(self, client):
        """Test that queue control endpoints require authentication."""
        response = await client.post("/api/queue/start")
        assert response.status_code == 401

    async def test_item_operations_require_auth(self, client):
        """Test that item operations require authentication."""
        response = await client.delete("/api/queue/items/dummy-id")
        # 404 is acceptable - endpoint exists but item doesn't
        # 401 is also acceptable - auth was checked before routing
        assert response.status_code in [401, 404]


class TestConcurrentOperations:
    """Test concurrent download operations."""

    async def test_multiple_concurrent_downloads(self, authenticated_client):
        """Test handling multiple concurrent download requests."""
        # Add multiple items concurrently
        tasks = []
        for i in range(5):
            task = authenticated_client.post(
                "/api/queue/add",
                json={
                    "serie_id": f"series-{i}",
                    "serie_folder": f"Series {i} (2024)",
                    "serie_name": f"Series {i}",
                    "episodes": [{"season": 1, "episode": 1}],
                    "priority": "normal"
                }
            )
            tasks.append(task)
        
        # Wait for all requests to complete
        responses = await asyncio.gather(*tasks, return_exceptions=True)
        
        # Verify all requests were handled
        for response in responses:
            if not isinstance(response, Exception):
                assert response.status_code in [201, 503]

    async def test_concurrent_status_requests(self, authenticated_client):
        """Test handling concurrent status requests."""
        # Make multiple concurrent status requests
        tasks = [
            authenticated_client.get("/api/queue/status")
            for _ in range(10)
        ]
        
        responses = await asyncio.gather(*tasks, return_exceptions=True)
        
        # Verify all requests were handled
        for response in responses:
            if not isinstance(response, Exception):
                assert response.status_code in [200, 503]


class TestQueuePersistence:
    """Test queue state persistence."""

    async def test_queue_survives_restart(self, authenticated_client, temp_queue_file):
        """Test that queue state persists across service restarts."""
        # This would require actually restarting the service
        # For integration testing, we verify the persistence mechanism exists
        
        # Add items to queue
        response = await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": "persistent-series",
                "serie_folder": "Persistent Series (2024)",
                "serie_name": "Persistent Series",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "normal"
            }
        )
        
        # Verify the request was processed
        assert response.status_code in [201, 503]
        
        # In a full integration test, we would restart the service here
        # and verify the queue state is restored

    async def test_failed_items_are_persisted(self, authenticated_client):
        """Test that failed items are persisted."""
        # Get initial queue state
        initial_response = await authenticated_client.get("/api/queue/status")
        
        assert initial_response.status_code in [200, 503]
        
        # The persistence mechanism should handle failed items
        # In a real scenario, we would trigger a failure and verify persistence


class TestWebSocketIntegrationWithDownloads:
    """Test WebSocket notifications during download operations."""

    async def test_websocket_notifies_on_queue_changes(self, authenticated_client):
        """Test that WebSocket broadcasts queue changes."""
        # This is a basic integration test
        # Full WebSocket testing is in test_websocket.py
        
        # Add an item to trigger potential WebSocket notification
        response = await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": "ws-series",
                "serie_folder": "WebSocket Series (2024)",
                "serie_name": "WebSocket Series",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "normal"
            }
        )
        
        # Verify the operation succeeded
        assert response.status_code in [201, 503]
        
        # In a full test, we would verify WebSocket clients received notifications


class TestCompleteDownloadWorkflow:
    """Test complete end-to-end download workflow."""

    async def test_full_download_cycle(self, authenticated_client):
        """Test complete download cycle from add to completion."""
        # 1. Add episode to queue
        add_response = await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": "workflow-series",
                "serie_folder": "Workflow Test Series (2024)",
                "serie_name": "Workflow Test Series",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "high"
            }
        )
        
        assert add_response.status_code in [201, 503]
        
        if add_response.status_code == 201:
            item_id = add_response.json()["item_ids"][0]
            
            # 2. Verify item is in queue
            status_response = await authenticated_client.get("/api/queue/status")
            assert status_response.status_code in [200, 503]
            
            # 3. Start queue processing
            start_response = await authenticated_client.post("/api/queue/start")
            assert start_response.status_code in [200, 503]
            
            # 4. Check status during processing
            await asyncio.sleep(0.1)  # Brief delay
            progress_response = await authenticated_client.get("/api/queue/status")
            assert progress_response.status_code in [200, 503]
            
            # 5. Verify final state (completed or still processing)
            final_response = await authenticated_client.get(
                "/api/queue/status"
            )
            assert final_response.status_code in [200, 503]
