"""Integration tests for series identifier consistency.

This module verifies that the 'key' identifier is used consistently
across all layers of the application (API, services, database, WebSocket).

The identifier standardization ensures:
- 'key' is the primary identifier (provider-assigned, URL-safe)
- 'folder' is metadata only (not used for lookups)
- Consistent identifier usage throughout the codebase
"""
import asyncio
from datetime import datetime, timezone
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 (
    DownloadItem,
    DownloadPriority,
    DownloadStatus,
    EpisodeIdentifier,
)
from src.server.services.anime_service import AnimeService
from src.server.services.auth_service import auth_service
from src.server.services.download_service import DownloadService
from src.server.services.progress_service import ProgressService


@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 progress_service():
    """Create a ProgressService instance for testing."""
    return ProgressService()


@pytest.fixture
async def download_service(mock_series_app, progress_service, tmp_path):
    """Create a DownloadService with mock repository for testing."""
    from tests.unit.test_download_service import MockQueueRepository
    
    mock_repo = MockQueueRepository()
    
    anime_service = AnimeService(
        series_app=mock_series_app,
        progress_service=progress_service,
    )
    anime_service.download = AsyncMock(return_value=True)
    
    service = DownloadService(
        anime_service=anime_service,
        progress_service=progress_service,
        queue_repository=mock_repo,
    )
    yield service
    await service.stop_downloads()


class TestAPIIdentifierConsistency:
    """Test that API endpoints use 'key' as the primary identifier."""

    @pytest.mark.asyncio
    async def test_queue_add_returns_key_in_response(
        self, authenticated_client
    ):
        """Test that adding to queue uses key as identifier.
        
        Verifies:
        - Request accepts serie_id (key) as primary identifier
        - serie_folder is accepted as metadata
        - Response reflects correct identifiers
        """
        request_data = {
            "serie_id": "attack-on-titan",  # Key (primary identifier)
            "serie_folder": "Attack on Titan (2013)",  # Metadata only
            "serie_name": "Attack on Titan",
            "episodes": [{"season": 1, "episode": 1}],
            "priority": "normal"
        }
        
        response = await authenticated_client.post(
            "/api/queue/add",
            json=request_data
        )
        
        assert response.status_code == 201
        data = response.json()
        
        # Verify response structure
        assert data["status"] == "success"
        assert len(data.get("added_items", [])) > 0

    @pytest.mark.asyncio
    async def test_queue_status_contains_key_identifier(
        self, authenticated_client
    ):
        """Test that queue status returns key as identifier.
        
        Verifies:
        - Queue items have serie_id (key) as identifier
        - Queue items have serie_folder as metadata
        - Both fields are present and distinct
        """
        import uuid

        # Add an item first with unique key
        unique_suffix = str(uuid.uuid4())[:8]
        unique_key = f"one-piece-{unique_suffix}"
        unique_folder = f"One Piece ({unique_suffix})"
        
        await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": unique_key,
                "serie_folder": unique_folder,
                "serie_name": "One Piece",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "normal"
            }
        )
        
        # Get queue status
        response = await authenticated_client.get("/api/queue/status")
        
        assert response.status_code == 200
        data = response.json()
        
        # Navigate to pending queue
        pending = data["status"]["pending_queue"]
        assert len(pending) > 0
        
        # Find the item we just added by key
        matching_items = [
            item for item in pending if item["serie_id"] == unique_key
        ]
        assert len(matching_items) == 1, (
            f"Expected to find item with key {unique_key}"
        )
        
        item = matching_items[0]
        
        # Verify identifier structure in queue item
        assert "serie_id" in item, "Queue item must have serie_id (key)"
        assert "serie_folder" in item, "Queue item must have serie_folder"
        
        # Verify key format (lowercase, hyphenated)
        assert item["serie_id"] == unique_key
        
        # Verify folder is preserved as metadata
        assert item["serie_folder"] == unique_folder
        
        # Verify both are present but different
        assert item["serie_id"] != item["serie_folder"]

    @pytest.mark.asyncio
    async def test_key_used_for_lookup_not_folder(
        self, authenticated_client
    ):
        """Test that lookups use key, not folder.
        
        Verifies:
        - Items can be identified by serie_id (key)
        - Multiple items with same folder but different keys are distinct
        """
        import uuid
        unique_suffix = str(uuid.uuid4())[:8]
        
        # Add two items with different keys but similar folders
        key1 = f"naruto-original-{unique_suffix}"
        key2 = f"naruto-shippuden-{unique_suffix}"
        shared_folder = f"Naruto Series ({unique_suffix})"
        
        await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": key1,
                "serie_folder": shared_folder,
                "serie_name": "Naruto",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "normal"
            }
        )
        
        await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": key2,
                "serie_folder": shared_folder,  # Same folder
                "serie_name": "Naruto Shippuden",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "normal"
            }
        )
        
        # Get queue status
        response = await authenticated_client.get("/api/queue/status")
        data = response.json()
        
        pending = data["status"]["pending_queue"]
        
        # Both items should be present (same folder doesn't cause collision)
        serie_ids = [item["serie_id"] for item in pending]
        assert key1 in serie_ids
        assert key2 in serie_ids


class TestServiceIdentifierConsistency:
    """Test that services use 'key' as the primary identifier."""

    @pytest.mark.asyncio
    async def test_download_service_uses_key(self, download_service):
        """Test that DownloadService uses key as identifier.
        
        Verifies:
        - Items are stored with serie_id (key)
        - Items can be retrieved by key
        - Queue operations use key consistently
        """
        # Add item to queue
        item_ids = await download_service.add_to_queue(
            serie_id="my-hero-academia",
            serie_folder="My Hero Academia (2016)",
            serie_name="My Hero Academia",
            episodes=[EpisodeIdentifier(season=1, episode=1)],
            priority=DownloadPriority.NORMAL,
        )
        
        assert len(item_ids) == 1
        
        # Verify item is stored correctly
        pending = download_service._pending_queue
        assert len(pending) == 1
        
        item = pending[0]
        assert item.serie_id == "my-hero-academia"
        assert item.serie_folder == "My Hero Academia (2016)"

    @pytest.mark.asyncio
    async def test_download_item_normalizes_key(self, download_service):
        """Test that serie_id is normalized (lowercase, stripped).
        
        Verifies:
        - Key is converted to lowercase
        - Whitespace is stripped
        """
        # Add item with uppercase key
        item_ids = await download_service.add_to_queue(
            serie_id="  DEMON-SLAYER  ",
            serie_folder="Demon Slayer (2019)",
            serie_name="Demon Slayer",
            episodes=[EpisodeIdentifier(season=1, episode=1)],
            priority=DownloadPriority.NORMAL,
        )
        
        assert len(item_ids) == 1
        
        # Verify key is normalized
        item = download_service._pending_queue[0]
        assert item.serie_id == "demon-slayer"

    @pytest.mark.asyncio
    async def test_queue_persistence_uses_key(
        self, download_service, tmp_path
    ):
        """Test that persisted queue data uses key as identifier.
        
        Verifies:
        - Persisted data contains serie_id (key)
        - Data can be restored with correct identifiers
        """
        # Add item to queue
        await download_service.add_to_queue(
            serie_id="jujutsu-kaisen",
            serie_folder="Jujutsu Kaisen (2020)",
            serie_name="Jujutsu Kaisen",
            episodes=[EpisodeIdentifier(season=1, episode=1)],
            priority=DownloadPriority.NORMAL,
        )
        
        # Verify item is in pending queue (in-memory cache synced with DB)
        pending_items = list(download_service._pending_queue)
        assert len(pending_items) == 1
        
        persisted_item = pending_items[0]
        assert persisted_item.serie_id == "jujutsu-kaisen"
        assert persisted_item.serie_folder == "Jujutsu Kaisen (2020)"


class TestWebSocketIdentifierConsistency:
    """Test that WebSocket events use 'key' in their payloads."""

    @pytest.mark.asyncio
    async def test_progress_events_include_key(
        self, download_service, progress_service
    ):
        """Test that progress events include key identifier.
        
        Verifies:
        - Progress events contain key information
        - Events use consistent identifier structure
        """
        broadcasts: List[Dict[str, Any]] = []

        async def mock_event_handler(event):
            broadcasts.append({
                "type": event.event_type,
                "data": event.progress.to_dict(),
                "room": event.room,
            })

        progress_service.subscribe("progress_updated", mock_event_handler)

        # Add item to trigger events
        await download_service.add_to_queue(
            serie_id="spy-x-family",
            serie_folder="Spy x Family (2022)",
            serie_name="Spy x Family",
            episodes=[EpisodeIdentifier(season=1, episode=1)],
            priority=DownloadPriority.NORMAL,
        )

        # Verify events were emitted
        assert len(broadcasts) >= 1

        # Check queue progress events for metadata
        queue_events = [
            b for b in broadcasts if b["type"] == "queue_progress"
        ]
        
        # Verify metadata structure includes identifier info
        for event in queue_events:
            metadata = event["data"].get("metadata", {})
            # Queue events should track items by their identifiers
            if "added_ids" in metadata:
                assert len(metadata["added_ids"]) > 0


class TestIdentifierValidation:
    """Test identifier validation and edge cases."""

    @pytest.mark.asyncio
    async def test_key_format_validation(self, authenticated_client):
        """Test that key format is validated correctly.
        
        Verifies:
        - Valid keys are accepted (lowercase, hyphenated)
        - Keys are normalized on input
        """
        import uuid
        unique_suffix = str(uuid.uuid4())[:8]
        
        # Valid key format
        response = await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": f"valid-key-format-{unique_suffix}",
                "serie_folder": f"Valid Key ({unique_suffix})",
                "serie_name": "Valid Key",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "normal"
            }
        )
        
        assert response.status_code == 201

    @pytest.mark.asyncio
    async def test_folder_not_used_for_identification(
        self, download_service
    ):
        """Test that folder changes don't affect identification.
        
        Verifies:
        - Same key with different folder is same series
        - Folder is metadata only, not identity
        """
        # Add item
        await download_service.add_to_queue(
            serie_id="chainsaw-man",
            serie_folder="Chainsaw Man (2022)",
            serie_name="Chainsaw Man",
            episodes=[EpisodeIdentifier(season=1, episode=1)],
            priority=DownloadPriority.NORMAL,
        )
        
        # Add another episode for same key, different folder
        await download_service.add_to_queue(
            serie_id="chainsaw-man",
            serie_folder="Chainsaw Man Updated (2022)",  # Different folder
            serie_name="Chainsaw Man",
            episodes=[EpisodeIdentifier(season=1, episode=2)],
            priority=DownloadPriority.NORMAL,
        )
        
        # Both should be added (same key, different episodes)
        assert len(download_service._pending_queue) == 2
        
        # Verify both use the same key
        keys = [item.serie_id for item in download_service._pending_queue]
        assert all(k == "chainsaw-man" for k in keys)


class TestEndToEndIdentifierFlow:
    """End-to-end tests for identifier consistency across layers."""

    @pytest.mark.asyncio
    async def test_complete_flow_with_key(
        self, authenticated_client
    ):
        """Test complete flow uses key consistently.
        
        Verifies:
        - API -> Service -> Storage uses key
        - All responses contain correct identifiers
        """
        import uuid

        # Use unique key to avoid conflicts with other tests
        unique_suffix = str(uuid.uuid4())[:8]
        unique_key = f"bleach-tybw-{unique_suffix}"
        unique_folder = f"Bleach: TYBW ({unique_suffix})"
        
        # 1. Add to queue via API
        add_response = await authenticated_client.post(
            "/api/queue/add",
            json={
                "serie_id": unique_key,
                "serie_folder": unique_folder,
                "serie_name": "Bleach: TYBW",
                "episodes": [{"season": 1, "episode": 1}],
                "priority": "high"
            }
        )
        
        assert add_response.status_code == 201
        
        # 2. Verify in queue status
        status_response = await authenticated_client.get("/api/queue/status")
        assert status_response.status_code == 200
        
        status_data = status_response.json()
        pending = status_data["status"]["pending_queue"]
        
        # Find our item by key
        items = [
            i for i in pending
            if i["serie_id"] == unique_key
        ]
        assert len(items) == 1, (
            f"Expected exactly 1 item with key {unique_key}, "
            f"found {len(items)}"
        )
        
        item = items[0]
        
        # 3. Verify identifier consistency
        assert item["serie_id"] == unique_key
        assert item["serie_folder"] == unique_folder
        assert item["serie_name"] == "Bleach: TYBW"
        
        # 4. Verify key and folder are different
        assert item["serie_id"] != item["serie_folder"]


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
