"""Integration tests for document status endpoint."""

from datetime import datetime, timezone
from uuid import UUID, uuid4

import pytest
from fastapi.testclient import TestClient
from sqlalchemy import select

from src.api.main import app
from src.db.models import ApiKey, Document
from src.db.session import get_sync_session
from src.services.auth_service import hash_api_key
from src.services.progress_tracker import get_progress_tracker


@pytest.fixture
def test_api_key():
    """Create a test API key in the database."""
    test_key = "test_api_key_for_status_check_12345"
    key_hash = hash_api_key(test_key)

    with get_sync_session() as session:
        # Clean up any existing test API key
        existing = session.execute(
            select(ApiKey).where(ApiKey.key_hash == key_hash)
        ).scalar_one_or_none()

        if existing:
            session.delete(existing)
            session.commit()

        # Create new test API key
        api_key = ApiKey(
            key_hash=key_hash,
            name="Test Status API Key",
            is_active=True,
            rate_limit_per_minute=100,
        )
        session.add(api_key)
        session.commit()
        session.refresh(api_key)

        yield test_key, api_key.id

        # Cleanup
        session.delete(api_key)
        session.commit()


@pytest.fixture
def test_client() -> TestClient:
    """Create a test client for the API."""
    return TestClient(app)


@pytest.fixture
def pending_document(test_api_key):
    """Create a pending document in the database."""
    _, api_key_id = test_api_key
    doc_id = uuid4()

    with get_sync_session() as session:
        document = Document(
            id=doc_id,
            filename="pending.xlsx",
            file_path=f"/storage/{doc_id}/pending.xlsx",
            file_size_bytes=1024,
            status="pending",
            api_key_id=api_key_id,
        )
        session.add(document)
        session.commit()

        yield doc_id

        # Cleanup
        session.delete(document)
        session.commit()


@pytest.fixture
def processing_document(test_api_key):
    """Create a processing document in the database."""
    _, api_key_id = test_api_key
    doc_id = uuid4()

    with get_sync_session() as session:
        document = Document(
            id=doc_id,
            filename="processing.xlsx",
            file_path=f"/storage/{doc_id}/processing.xlsx",
            file_size_bytes=2048,
            status="processing",
            api_key_id=api_key_id,
        )
        session.add(document)
        session.commit()

        yield doc_id

        # Cleanup
        session.delete(document)
        session.commit()


@pytest.fixture
def completed_document(test_api_key):
    """Create a completed document in the database."""
    _, api_key_id = test_api_key
    doc_id = uuid4()

    with get_sync_session() as session:
        document = Document(
            id=doc_id,
            filename="completed.xlsx",
            file_path=f"/storage/{doc_id}/completed.xlsx",
            file_size_bytes=3072,
            status="completed",
            sheet_count=5,
            record_count=100,
            processed_at=datetime.now(timezone.utc),
            api_key_id=api_key_id,
        )
        session.add(document)
        session.commit()

        yield doc_id

        # Cleanup
        session.delete(document)
        session.commit()


@pytest.fixture
def failed_document(test_api_key):
    """Create a failed document in the database."""
    _, api_key_id = test_api_key
    doc_id = uuid4()

    with get_sync_session() as session:
        document = Document(
            id=doc_id,
            filename="failed.xlsx",
            file_path=f"/storage/{doc_id}/failed.xlsx",
            file_size_bytes=4096,
            status="failed",
            error_message="File validation failed: CORRUPTED_FILE",
            api_key_id=api_key_id,
        )
        session.add(document)
        session.commit()

        yield doc_id

        # Cleanup
        session.delete(document)
        session.commit()


class TestDocumentStatusPending:
    """Test status endpoint for pending documents."""

    def test_get_pending_document_status(
        self, test_client: TestClient, test_api_key, pending_document: UUID
    ):
        """Should return pending status with basic fields."""
        test_key, _ = test_api_key

        response = test_client.get(
            f"/documents/{pending_document}",
            headers={"X-API-Key": test_key},
        )

        assert response.status_code == 200
        data = response.json()

        assert data["document_id"] == str(pending_document)
        assert data["filename"] == "pending.xlsx"
        assert data["status"] == "pending"
        assert "created_at" in data

        # Pending documents should not have these fields
        assert data.get("progress") is None
        assert data.get("processed_at") is None
        assert data.get("sheet_count") is None
        assert data.get("record_count") is None
        assert data.get("error_message") is None


class TestDocumentStatusProcessing:
    """Test status endpoint for processing documents."""

    def test_get_processing_document_status_without_progress(
        self, test_client: TestClient, test_api_key, processing_document: UUID
    ):
        """Should return processing status even without progress data."""
        test_key, _ = test_api_key

        response = test_client.get(
            f"/documents/{processing_document}",
            headers={"X-API-Key": test_key},
        )

        assert response.status_code == 200
        data = response.json()

        assert data["status"] == "processing"
        # Progress may be None if worker hasn't started yet
        # This is acceptable behavior

    @pytest.mark.asyncio
    async def test_get_processing_document_status_with_progress(
        self, test_client: TestClient, test_api_key, processing_document: UUID
    ):
        """Should return processing status with progress object."""
        test_key, _ = test_api_key

        # Set progress data in Redis
        progress_tracker = get_progress_tracker()
        await progress_tracker.set_progress(
            processing_document, "extracting_tables", 3, 10
        )

        try:
            response = test_client.get(
                f"/documents/{processing_document}",
                headers={"X-API-Key": test_key},
            )

            assert response.status_code == 200
            data = response.json()

            assert data["status"] == "processing"
            assert "progress" in data
            assert data["progress"] is not None
            assert data["progress"]["stage"] == "extracting_tables"
            assert data["progress"]["sheets_processed"] == 3
            assert data["progress"]["sheets_total"] == 10

        finally:
            # Cleanup progress data
            await progress_tracker.clear_progress(processing_document)


class TestDocumentStatusCompleted:
    """Test status endpoint for completed documents."""

    def test_get_completed_document_status(
        self, test_client: TestClient, test_api_key, completed_document: UUID
    ):
        """Should return completed status with metadata."""
        test_key, _ = test_api_key

        response = test_client.get(
            f"/documents/{completed_document}",
            headers={"X-API-Key": test_key},
        )

        assert response.status_code == 200
        data = response.json()

        assert data["status"] == "completed"
        assert "processed_at" in data
        assert data["processed_at"] is not None
        assert data["sheet_count"] == 5
        assert data["record_count"] == 100

        # Completed documents should not have these fields
        assert data.get("progress") is None
        assert data.get("error_message") is None


class TestDocumentStatusFailed:
    """Test status endpoint for failed documents."""

    def test_get_failed_document_status(
        self, test_client: TestClient, test_api_key, failed_document: UUID
    ):
        """Should return failed status with error message."""
        test_key, _ = test_api_key

        response = test_client.get(
            f"/documents/{failed_document}",
            headers={"X-API-Key": test_key},
        )

        assert response.status_code == 200
        data = response.json()

        assert data["status"] == "failed"
        assert "error_message" in data
        assert data["error_message"] == "File validation failed: CORRUPTED_FILE"

        # Failed documents should not have these fields
        assert data.get("progress") is None
        assert data.get("processed_at") is None
        assert data.get("sheet_count") is None
        assert data.get("record_count") is None


class TestDocumentStatusNotFound:
    """Test status endpoint for nonexistent documents."""

    def test_get_nonexistent_document(
        self, test_client: TestClient, test_api_key
    ):
        """Should return 404 for nonexistent document."""
        test_key, _ = test_api_key
        nonexistent_id = uuid4()

        response = test_client.get(
            f"/documents/{nonexistent_id}",
            headers={"X-API-Key": test_key},
        )

        assert response.status_code == 404
        data = response.json()

        assert data["error"]["code"] == "DOCUMENT_NOT_FOUND"
        assert "not found" in data["error"]["message"].lower()


class TestDocumentStatusAuthorization:
    """Test status endpoint authorization."""

    def test_get_document_owned_by_different_api_key(
        self, test_client: TestClient, test_api_key, pending_document: UUID
    ):
        """Should return 404 for document owned by different API key."""
        # Create a different API key
        different_key = "different_api_key_12345"
        different_key_hash = hash_api_key(different_key)

        with get_sync_session() as session:
            different_api_key = ApiKey(
                key_hash=different_key_hash,
                name="Different API Key",
                is_active=True,
                rate_limit_per_minute=100,
            )
            session.add(different_api_key)
            session.commit()

            try:
                response = test_client.get(
                    f"/documents/{pending_document}",
                    headers={"X-API-Key": different_key},
                )

                # Should return 404 to avoid leaking document existence
                assert response.status_code == 404
                data = response.json()
                assert data["error"]["code"] == "DOCUMENT_NOT_FOUND"

            finally:
                # Cleanup
                session.delete(different_api_key)
                session.commit()

    def test_get_document_without_api_key(
        self, test_client: TestClient, pending_document: UUID
    ):
        """Should return 401 when API key is missing."""
        response = test_client.get(f"/documents/{pending_document}")

        assert response.status_code == 401

    def test_get_document_with_invalid_api_key(
        self, test_client: TestClient, pending_document: UUID
    ):
        """Should return 401 when API key is invalid."""
        response = test_client.get(
            f"/documents/{pending_document}",
            headers={"X-API-Key": "invalid-key"},
        )

        assert response.status_code == 401


class TestDocumentStatusResponseStructure:
    """Test status endpoint response structure."""

    def test_response_matches_schema(
        self, test_client: TestClient, test_api_key, pending_document: UUID
    ):
        """Should return response matching DocumentStatusResponse schema."""
        test_key, _ = test_api_key

        response = test_client.get(
            f"/documents/{pending_document}",
            headers={"X-API-Key": test_key},
        )

        assert response.status_code == 200
        data = response.json()

        # Required fields
        assert "document_id" in data
        assert "filename" in data
        assert "status" in data
        assert "created_at" in data

        # Validate types
        assert isinstance(data["document_id"], str)
        assert isinstance(data["filename"], str)
        assert isinstance(data["status"], str)
        assert data["status"] in ["pending", "processing", "completed", "failed"]
