"""Integration tests for document deletion endpoint."""

from datetime import datetime, timezone
from pathlib import Path
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


@pytest.fixture
def test_api_key():
    """Create a test API key in the database."""
    test_key = "test_api_key_for_deletion_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 Deletion 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()
    file_path = f"/tmp/test_storage/{doc_id}/pending.xlsx"

    # Create directory and file
    Path(file_path).parent.mkdir(parents=True, exist_ok=True)
    Path(file_path).touch()

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

        yield doc_id, file_path

        # Cleanup - delete document if still exists
        doc = session.get(Document, doc_id)
        if doc:
            session.delete(doc)
            session.commit()

        # Cleanup - delete file if still exists
        if Path(file_path).exists():
            Path(file_path).unlink()
        # Cleanup - delete directory if empty
        if Path(file_path).parent.exists():
            try:
                Path(file_path).parent.rmdir()
            except OSError:
                pass  # Directory not empty


@pytest.fixture
def processing_document(test_api_key):
    """Create a processing document in the database."""
    _, api_key_id = test_api_key
    doc_id = uuid4()
    file_path = f"/tmp/test_storage/{doc_id}/processing.xlsx"

    # Create directory and file
    Path(file_path).parent.mkdir(parents=True, exist_ok=True)
    Path(file_path).touch()

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

        yield doc_id, file_path

        # Cleanup
        doc = session.get(Document, doc_id)
        if doc:
            session.delete(doc)
            session.commit()

        if Path(file_path).exists():
            Path(file_path).unlink()
        if Path(file_path).parent.exists():
            try:
                Path(file_path).parent.rmdir()
            except OSError:
                pass


@pytest.fixture
def completed_document(test_api_key):
    """Create a completed document in the database."""
    _, api_key_id = test_api_key
    doc_id = uuid4()
    file_path = f"/tmp/test_storage/{doc_id}/completed.xlsx"

    # Create directory and file
    Path(file_path).parent.mkdir(parents=True, exist_ok=True)
    Path(file_path).touch()

    with get_sync_session() as session:
        document = Document(
            id=doc_id,
            filename="completed.xlsx",
            file_path=file_path,
            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, file_path

        # Cleanup
        doc = session.get(Document, doc_id)
        if doc:
            session.delete(doc)
            session.commit()

        if Path(file_path).exists():
            Path(file_path).unlink()
        if Path(file_path).parent.exists():
            try:
                Path(file_path).parent.rmdir()
            except OSError:
                pass


@pytest.fixture
def failed_document(test_api_key):
    """Create a failed document in the database."""
    _, api_key_id = test_api_key
    doc_id = uuid4()
    file_path = f"/tmp/test_storage/{doc_id}/failed.xlsx"

    # Create directory and file
    Path(file_path).parent.mkdir(parents=True, exist_ok=True)
    Path(file_path).touch()

    with get_sync_session() as session:
        document = Document(
            id=doc_id,
            filename="failed.xlsx",
            file_path=file_path,
            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, file_path

        # Cleanup
        doc = session.get(Document, doc_id)
        if doc:
            session.delete(doc)
            session.commit()

        if Path(file_path).exists():
            Path(file_path).unlink()
        if Path(file_path).parent.exists():
            try:
                Path(file_path).parent.rmdir()
            except OSError:
                pass


class TestDeletePendingDocument:
    """Test DELETE endpoint for pending documents."""

    def test_delete_pending_document_returns_200(
        self, test_client: TestClient, test_api_key, pending_document
    ):
        """Should return 200 and delete pending document."""
        test_key, _ = test_api_key
        doc_id, file_path = pending_document

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

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

        assert data["document_id"] == str(doc_id)
        assert data["status"] == "deleted"
        assert "removed" in data["message"].lower()

    def test_delete_pending_document_removes_from_database(
        self, test_client: TestClient, test_api_key, pending_document
    ):
        """Should remove document from database."""
        test_key, _ = test_api_key
        doc_id, _ = pending_document

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

        assert response.status_code == 200

        # Verify document removed from database
        with get_sync_session() as session:
            doc = session.get(Document, doc_id)
            assert doc is None

    def test_delete_pending_document_removes_file(
        self, test_client: TestClient, test_api_key, pending_document
    ):
        """Should remove physical file from storage."""
        test_key, _ = test_api_key
        doc_id, file_path = pending_document

        # Verify file exists before deletion
        assert Path(file_path).exists()

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

        assert response.status_code == 200

        # Verify file removed
        assert not Path(file_path).exists()


class TestDeleteProcessingDocument:
    """Test DELETE endpoint for processing documents."""

    def test_delete_processing_document_returns_409(
        self, test_client: TestClient, test_api_key, processing_document
    ):
        """Should return 409 Conflict for processing documents."""
        test_key, _ = test_api_key
        doc_id, _ = processing_document

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

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

        assert data["error"]["code"] == "DOCUMENT_PROCESSING"
        assert "processing" in data["error"]["message"].lower()

    def test_delete_processing_document_does_not_remove(
        self, test_client: TestClient, test_api_key, processing_document
    ):
        """Should not remove processing document."""
        test_key, _ = test_api_key
        doc_id, file_path = processing_document

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

        assert response.status_code == 409

        # Verify document still exists
        with get_sync_session() as session:
            doc = session.get(Document, doc_id)
            assert doc is not None
            assert doc.status == "processing"

        # Verify file still exists
        assert Path(file_path).exists()


class TestDeleteCompletedDocument:
    """Test DELETE endpoint for completed documents."""

    def test_delete_completed_document_returns_200(
        self, test_client: TestClient, test_api_key, completed_document
    ):
        """Should return 200 and delete completed document."""
        test_key, _ = test_api_key
        doc_id, _ = completed_document

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

        assert response.status_code == 200


class TestDeleteFailedDocument:
    """Test DELETE endpoint for failed documents."""

    def test_delete_failed_document_returns_200(
        self, test_client: TestClient, test_api_key, failed_document
    ):
        """Should return 200 and delete failed document."""
        test_key, _ = test_api_key
        doc_id, _ = failed_document

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

        assert response.status_code == 200


class TestDeleteNonexistentDocument:
    """Test DELETE endpoint for nonexistent documents."""

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

        response = test_client.delete(
            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 TestDeleteDocumentAuthorization:
    """Test DELETE endpoint authorization."""

    def test_delete_document_owned_by_different_api_key_returns_404(
        self, test_client: TestClient, test_api_key, pending_document
    ):
        """Should return 404 for document owned by different API key."""
        doc_id, _ = pending_document

        # 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.delete(
                    f"/documents/{doc_id}",
                    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_delete_document_without_api_key_returns_401(
        self, test_client: TestClient, pending_document
    ):
        """Should return 401 when API key is missing."""
        doc_id, _ = pending_document

        response = test_client.delete(f"/documents/{doc_id}")

        assert response.status_code == 401

    def test_delete_document_with_invalid_api_key_returns_401(
        self, test_client: TestClient, pending_document
    ):
        """Should return 401 when API key is invalid."""
        doc_id, _ = pending_document

        response = test_client.delete(
            f"/documents/{doc_id}",
            headers={"X-API-Key": "invalid-key"},
        )

        assert response.status_code == 401


class TestDeleteDocumentResponseStructure:
    """Test DELETE endpoint response structure."""

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

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

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

        # Required fields
        assert "document_id" in data
        assert "status" in data
        assert "message" in data

        # Validate types and values
        assert isinstance(data["document_id"], str)
        assert data["status"] == "deleted"
        assert isinstance(data["message"], str)
