"""Tests for queue management features.

This module tests the queue page functionality including:
- Display of queued items in organized lists
- Drag-and-drop reordering
- Starting and stopping queue processing
- Filtering completed and failed downloads
"""
import pytest
from httpx import ASGITransport, AsyncClient

from src.server.fastapi_app import app


@pytest.fixture
async def client():
    """Create an async test client."""
    transport = ASGITransport(app=app)
    async with AsyncClient(
        transport=transport, base_url="http://test"
    ) as client:
        yield client


@pytest.fixture
async def auth_headers(client: AsyncClient):
    """Get authentication headers with valid JWT token."""
    # Setup auth
    await client.post(
        "/api/auth/setup",
        json={"master_password": "TestPass123!"}
    )
    
    # Login
    response = await client.post(
        "/api/auth/login",
        json={"password": "TestPass123!"}
    )
    
    data = response.json()
    token = data["access_token"]
    
    return {"Authorization": f"Bearer {token}"}


@pytest.fixture
def sample_download_request():
    """Sample download request for testing.
    
    Note: serie_id is the primary identifier (key) used for all lookups.
    serie_folder is metadata only used for filesystem operations.
    """
    return {
        "serie_id": "test-series-key",  # Provider key (primary identifier)
        "serie_folder": "Test Series (2024)",  # Filesystem folder (metadata)
        "serie_name": "Test Series",
        "episodes": [
            {"season": 1, "episode": 1},
            {"season": 1, "episode": 2}
        ],
        "priority": "normal"
    }


class TestQueueDisplay:
    """Test queue display and organization."""

    @pytest.mark.asyncio
    async def test_queue_status_includes_all_sections(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test queue status includes all sections."""
        response = await client.get(
            "/api/queue/status",
            headers=auth_headers
        )
        
        assert response.status_code == 200
        data = response.json()
        
        # Verify top-level structure
        assert "status" in data
        assert "statistics" in data
        
        # Verify status nested structure
        status = data["status"]
        assert "active_downloads" in status
        assert "pending_queue" in status
        assert "completed_downloads" in status
        assert "failed_downloads" in status
        assert "is_running" in status
        assert "is_paused" in status

    @pytest.mark.asyncio
    async def test_queue_items_have_required_fields(
        self, client: AsyncClient, auth_headers: dict,
        sample_download_request: dict
    ):
        """Test queue items have required display fields."""
        # Add an item to the queue
        add_response = await client.post(
            "/api/queue/add",
            json=sample_download_request,
            headers=auth_headers
        )
        assert add_response.status_code == 201
        
        # Get the added item IDs from response
        add_data = add_response.json()
        added_ids = add_data.get("added_items", [])
        assert len(added_ids) > 0, "No items were added"
        
        # Get queue status
        response = await client.get(
            "/api/queue/status",
            headers=auth_headers
        )
        assert response.status_code == 200
        
        data = response.json()
        # Updated for nested status structure
        pending = data["status"]["pending_queue"]
        
        assert len(pending) > 0
        
        # Find the item we just added by ID
        item = next((i for i in pending if i["id"] in added_ids), None)
        assert item is not None, f"Could not find added item in queue. Added IDs: {added_ids}"
        
        # Verify required fields for display
        assert "id" in item
        assert "serie_id" in item  # Key - primary identifier
        assert "serie_folder" in item  # Metadata for filesystem ops
        assert "serie_name" in item
        assert "episode" in item
        assert "priority" in item
        assert "added_at" in item
        
        # Verify serie_id (key) matches what we sent
        assert item["serie_id"] == sample_download_request["serie_id"]
        
        # Verify episode structure
        episode = item["episode"]
        assert "season" in episode
        assert "episode" in episode

    @pytest.mark.asyncio
    async def test_queue_item_uses_key_as_identifier(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test that queue items use serie_id (key) as primary identifier.
        
        Verifies that:
        - serie_id is the provider-assigned key (URL-safe identifier)
        - serie_folder is metadata only (not used for identification)
        - Both fields are present in queue item responses
        """
        # Add an item with explicit key and folder
        request = {
            "serie_id": "my-test-anime-key",  # Provider key (primary ID)
            "serie_folder": "My Test Anime (2024)",  # Display name/folder
            "serie_name": "My Test Anime",
            "episodes": [{"season": 1, "episode": 1}],
            "priority": "normal"
        }
        
        add_response = await client.post(
            "/api/queue/add",
            json=request,
            headers=auth_headers
        )
        assert add_response.status_code == 201
        
        # Get queue status
        response = await client.get(
            "/api/queue/status",
            headers=auth_headers
        )
        assert response.status_code == 200
        
        data = response.json()
        pending = data["status"]["pending_queue"]
        
        # Find our item by key
        matching_items = [
            item for item in pending
            if item["serie_id"] == "my-test-anime-key"
        ]
        assert len(matching_items) >= 1, "Item should be findable by key"
        
        item = matching_items[0]
        
        # Verify key is used as identifier
        assert item["serie_id"] == "my-test-anime-key"
        
        # Verify folder is preserved as metadata
        assert item["serie_folder"] == "My Test Anime (2024)"
        
        # Verify serie_name is also present
        assert item["serie_name"] == "My Test Anime"


class TestQueueReordering:
    """Test queue reordering functionality."""

    @pytest.mark.asyncio
    async def test_reorder_queue_with_item_ids(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test reordering queue using item_ids array."""
        # Clear existing queue first
        status_response = await client.get(
            "/api/queue/status",
            headers=auth_headers
        )
        existing_items = [
            item["id"]
            for item in status_response.json()["status"]["pending_queue"]
        ]
        if existing_items:
            await client.request(
                "DELETE",
                "/api/queue/",
                json={"item_ids": existing_items},
                headers=auth_headers
            )
        
        # Add exactly 3 items
        added_ids = []
        for i in range(3):
            response = await client.post(
                "/api/queue/add",
                json={
                    "serie_id": f"reorder-test-key-{i}",  # Key (primary ID)
                    "serie_folder": f"Reorder Test {i} (2024)",  # Metadata
                    "serie_name": f"Reorder Test {i}",
                    "episodes": [{"season": 1, "episode": i+1}],
                    "priority": "normal"
                },
                headers=auth_headers
            )
            if response.status_code == 201:
                data = response.json()
                if "added_items" in data and data["added_items"]:
                    added_ids.extend(data["added_items"])
        
        assert len(added_ids) == 3, f"Expected 3 items, got {len(added_ids)}"
        
        # Reverse the order
        new_order = list(reversed(added_ids))
        
        # Reorder
        reorder_response = await client.post(
            "/api/queue/reorder",
            json={"item_ids": new_order},
            headers=auth_headers
        )
        
        assert reorder_response.status_code == 200
        assert reorder_response.json()["status"] == "success"
        
        # Verify new order
        status_response = await client.get(
            "/api/queue/status",
            headers=auth_headers
        )
        current_order = [
            item["id"]
            for item in status_response.json()["status"]["pending_queue"]
        ]
        
        assert current_order == new_order

    @pytest.mark.asyncio
    async def test_reorder_with_invalid_ids(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test reordering with non-existent IDs succeeds (idempotent)."""
        response = await client.post(
            "/api/queue/reorder",
            json={"item_ids": ["invalid-id-1", "invalid-id-2"]},
            headers=auth_headers
        )
        
        # Bulk reorder is idempotent and succeeds even with invalid IDs
        # It just ignores items that don't exist
        assert response.status_code == 200

    @pytest.mark.asyncio
    async def test_reorder_empty_list(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test reordering with empty list."""
        response = await client.post(
            "/api/queue/reorder",
            json={"item_ids": []},
            headers=auth_headers
        )
        
        # Should succeed but do nothing
        assert response.status_code in [200, 404]


class TestQueueControl:
    """Test queue start/stop functionality."""

    @pytest.mark.asyncio
    async def test_start_queue(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test starting the download queue."""
        response = await client.post(
            "/api/queue/start",
            headers=auth_headers
        )
        
        # 200 = started, 400 = empty queue (no pending downloads)
        assert response.status_code in [200, 400]
        data = response.json()
        if response.status_code == 200:
            assert data["status"] == "success"

    @pytest.mark.asyncio
    async def test_stop_queue(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test stopping the download queue."""
        # Start first
        await client.post("/api/queue/start", headers=auth_headers)
        
        # Then stop
        response = await client.post(
            "/api/queue/stop",
            headers=auth_headers
        )
        
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "success"

    @pytest.mark.asyncio
    async def test_queue_status_reflects_running_state(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test queue status reflects running state."""
        # Initially not running
        status = await client.get(
            "/api/queue/status",
            headers=auth_headers
        )
        assert status.json()["status"]["is_running"] is False
        
        # Start queue — may return 400 if queue is empty
        start_resp = await client.post("/api/queue/start", headers=auth_headers)
        
        if start_resp.status_code == 200:
            # Should be running
            status = await client.get(
                "/api/queue/status",
                headers=auth_headers
            )
            assert status.json()["status"]["is_running"] is True
            
            # Stop queue
            await client.post("/api/queue/stop", headers=auth_headers)
            
            # Should not be running
            status = await client.get(
                "/api/queue/status",
                headers=auth_headers
            )
            assert status.json()["status"]["is_running"] is False
        else:
            # Queue was empty, start returned 400 — is_running stays False
            status = await client.get(
                "/api/queue/status",
                headers=auth_headers
            )
            assert status.json()["status"]["is_running"] is False


class TestCompletedDownloads:
    """Test completed downloads management."""

    @pytest.mark.asyncio
    async def test_clear_completed_downloads(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test clearing completed downloads."""
        response = await client.delete(
            "/api/queue/completed",
            headers=auth_headers
        )
        
        assert response.status_code == 200
        data = response.json()
        assert "count" in data
        assert data["status"] == "success"

    @pytest.mark.asyncio
    async def test_completed_section_count(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test that completed count is accurate."""
        status = await client.get(
            "/api/queue/status",
            headers=auth_headers
        )
        data = status.json()
        
        completed_count = data["statistics"]["completed_count"]
        completed_list = len(data["status"]["completed_downloads"])
        
        # Count should match list length
        assert completed_count == completed_list


class TestFailedDownloads:
    """Test failed downloads management."""

    @pytest.mark.asyncio
    async def test_clear_failed_downloads(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test clearing failed downloads."""
        response = await client.delete(
            "/api/queue/failed",
            headers=auth_headers
        )
        
        assert response.status_code == 200
        data = response.json()
        assert "count" in data
        assert data["status"] == "success"

    @pytest.mark.asyncio
    async def test_retry_failed_downloads(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test retrying failed downloads."""
        response = await client.post(
            "/api/queue/retry",
            json={"item_ids": []},
            headers=auth_headers
        )
        
        assert response.status_code == 200
        data = response.json()
        assert "retried_count" in data
        assert data["status"] == "success"

    @pytest.mark.asyncio
    async def test_retry_specific_failed_download(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test retrying a specific failed download."""
        # Test the endpoint accepts the format
        response = await client.post(
            "/api/queue/retry",
            json={"item_ids": ["some-id"]},
            headers=auth_headers
        )
        
        # Should succeed even if ID doesn't exist (idempotent)
        assert response.status_code == 200

    @pytest.mark.asyncio
    async def test_failed_section_count(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test that failed count is accurate."""
        status = await client.get(
            "/api/queue/status",
            headers=auth_headers
        )
        data = status.json()
        
        failed_count = data["statistics"]["failed_count"]
        failed_list = len(data["status"]["failed_downloads"])
        
        # Count should match list length
        assert failed_count == failed_list


class TestBulkOperations:
    """Test bulk queue operations."""

    @pytest.mark.asyncio
    async def test_remove_multiple_items(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test removing multiple items from queue."""
        # Add multiple items
        item_ids = []
        for i in range(3):
            add_response = await client.post(
                "/api/queue/add",
                json={
                    "serie_id": f"bulk-test-key-{i}",  # Key (primary ID)
                    "serie_folder": f"Bulk Test {i} (2024)",  # Metadata
                    "serie_name": f"Bulk Test {i}",
                    "episodes": [{"season": 1, "episode": i+1}],
                    "priority": "normal"
                },
                headers=auth_headers
            )
            if add_response.status_code == 201:
                data = add_response.json()
                if "added_items" in data and len(data["added_items"]) > 0:
                    item_ids.append(data["added_items"][0])
        
        # Remove all at once
        if item_ids:
            response = await client.request(
                "DELETE",
                "/api/queue/",
                json={"item_ids": item_ids},
                headers=auth_headers
            )
            
            assert response.status_code == 204

    @pytest.mark.asyncio
    async def test_clear_entire_pending_queue(
        self, client: AsyncClient, auth_headers: dict
    ):
        """Test clearing entire pending queue."""
        # Get all pending items
        status = await client.get(
            "/api/queue/status",
            headers=auth_headers
        )
        pending = status.json()["status"]["pending_queue"]
        
        if pending:
            item_ids = [item["id"] for item in pending]
            
            # Remove all
            response = await client.request(
                "DELETE",
                "/api/queue/",
                json={"item_ids": item_ids},
                headers=auth_headers
            )
            
            assert response.status_code == 204
            
            # Verify queue is empty
            status = await client.get(
                "/api/queue/status",
                headers=auth_headers
            )
            assert len(status.json()["status"]["pending_queue"]) == 0
