"""Integration tests for batch document upload endpoint."""

import io

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

from src.api.main import app
from src.db.models import ApiKey
from src.db.session import get_sync_session
from src.services.auth_service import hash_api_key


def create_excel_file(filename: str, has_data: bool = True) -> tuple[str, bytes]:
    """Helper to create an Excel file for testing.

    Args:
        filename: Name of the file.
        has_data: Whether the Excel file should contain data.

    Returns:
        Tuple of (filename, file_content).
    """
    wb = Workbook()
    ws = wb.active

    if has_data:
        ws["A1"] = "Header"
        ws["A2"] = "Data"

    buffer = io.BytesIO()
    wb.save(buffer)
    buffer.seek(0)
    return filename, buffer.read()


@pytest.fixture
def test_api_key():
    """Create a test API key in the database."""
    test_key = "test_api_key_for_batch_upload_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 Batch API Key",
            is_active=True,
            rate_limit_per_minute=100,
        )
        session.add(api_key)
        session.commit()
        session.refresh(api_key)

        yield test_key

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


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


class TestBatchUploadSuccess:
    """Test successful batch upload scenarios."""

    def test_batch_with_three_valid_files(self, test_client: TestClient, test_api_key: str):
        """Should accept all 3 valid files with 202 status."""
        files = [
            ("files", create_excel_file(f"file{i}.xlsx")) for i in range(3)
        ]

        response = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": test_api_key},
        )

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

        assert "batch_id" in data
        assert len(data["documents"]) == 3
        assert len(data["errors"]) == 0
        assert "3 documents queued" in data["message"]

        # Verify document structure
        for doc in data["documents"]:
            assert "document_id" in doc
            assert "filename" in doc
            assert doc["status"] == "accepted"

    def test_batch_with_single_valid_file(self, test_client: TestClient, test_api_key: str):
        """Should accept single file with 202 status."""
        files = [("files", create_excel_file("single.xlsx"))]

        response = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": test_api_key},
        )

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

        assert len(data["documents"]) == 1
        assert len(data["errors"]) == 0


class TestBatchUploadPartialSuccess:
    """Test batch upload with mixed success/failure."""

    def test_batch_with_valid_and_invalid_files(self, test_client: TestClient, test_api_key: str):
        """Should return 207 with 2 accepted, 1 failed."""
        files = [
            ("files", create_excel_file("file1.xlsx")),
            ("files", create_excel_file("file2.xlsx")),
            ("files", ("invalid.txt", b"This is not an Excel file")),
        ]

        response = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": test_api_key},
        )

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

        assert len(data["documents"]) == 2
        assert len(data["errors"]) == 1
        assert "2 documents queued" in data["message"]
        assert "1 failed validation" in data["message"]

        # Verify error structure
        error = data["errors"][0]
        assert error["filename"] == "invalid.txt"
        assert error["code"] == "INVALID_EXTENSION"
        assert "message" in error

    def test_batch_with_all_invalid_files(self, test_client: TestClient, test_api_key: str):
        """Should return 207 with empty documents array."""
        files = [
            ("files", ("file1.txt", b"Not Excel 1")),
            ("files", ("file2.pdf", b"Not Excel 2")),
        ]

        response = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": test_api_key},
        )

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

        assert len(data["documents"]) == 0
        assert len(data["errors"]) == 2
        assert "All 2 files failed" in data["message"]

    def test_batch_with_empty_excel_file(self, test_client: TestClient, test_api_key: str):
        """Should return 207 with empty file in errors."""
        files = [
            ("files", create_excel_file("valid.xlsx", has_data=True)),
            ("files", create_excel_file("empty.xlsx", has_data=False)),
        ]

        response = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": test_api_key},
        )

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

        assert len(data["documents"]) == 1
        assert len(data["errors"]) == 1
        assert data["errors"][0]["filename"] == "empty.xlsx"
        assert data["errors"][0]["code"] == "EMPTY_FILE"


class TestBatchUploadBatchLevelValidation:
    """Test batch-level validation failures."""

    def test_batch_with_too_many_files(self, test_client: TestClient, test_api_key: str):
        """Should return 400 for 21 files (exceeds limit of 20)."""
        files = [
            ("files", create_excel_file(f"file{i}.xlsx")) for i in range(21)
        ]

        response = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": test_api_key},
        )

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

        assert data["error"]["code"] == "TOO_MANY_FILES"
        assert "21 files" in data["error"]["message"]
        assert "maximum allowed is 20" in data["error"]["message"]

    def test_batch_with_total_size_exceeding_limit(self, test_client: TestClient, test_api_key: str):
        """Should return 400 when total batch size exceeds 500MB."""
        # Create files totaling > 500MB
        # Note: This test creates large files in memory - may be slow
        large_file_size = 110 * 1024 * 1024  # 110MB each
        files = [
            ("files", (f"file{i}.xlsx", b"x" * large_file_size))
            for i in range(5)  # 5 x 110MB = 550MB total
        ]

        response = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": test_api_key},
        )

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

        assert data["error"]["code"] == "BATCH_TOO_LARGE"
        assert "500MB" in data["error"]["message"]


class TestBatchUploadErrorHandling:
    """Test error handling and edge cases."""

    def test_batch_with_duplicate_filenames(self, test_client: TestClient, test_api_key: str):
        """Should process all files even with duplicate names."""
        files = [
            ("files", create_excel_file("duplicate.xlsx")),
            ("files", create_excel_file("duplicate.xlsx")),
        ]

        response = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": test_api_key},
        )

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

        assert len(data["documents"]) == 2
        # Both files should be accepted (no deduplication)

    def test_batch_without_api_key(self, test_client: TestClient):
        """Should return 401 when API key is missing."""
        files = [("files", create_excel_file("file1.xlsx"))]

        response = test_client.post(
            "/documents/batch",
            files=files,
        )

        assert response.status_code == 401

    def test_batch_with_invalid_api_key(self, test_client: TestClient):
        """Should return 401 when API key is invalid."""
        files = [("files", create_excel_file("file1.xlsx"))]

        response = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": "invalid-key"},
        )

        assert response.status_code == 401


class TestBatchUploadResponseStructure:
    """Test response structure and data integrity."""

    def test_batch_id_is_unique(self, test_client: TestClient, test_api_key: str):
        """Should generate unique batch_id for each request."""
        files = [("files", create_excel_file("file1.xlsx"))]

        response1 = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": test_api_key},
        )
        response2 = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": test_api_key},
        )

        assert response1.status_code == 202
        assert response2.status_code == 202

        batch_id1 = response1.json()["batch_id"]
        batch_id2 = response2.json()["batch_id"]

        assert batch_id1 != batch_id2

    def test_error_response_structure(self, test_client: TestClient, test_api_key: str):
        """Should return properly structured error details."""
        files = [("files", ("invalid.txt", b"Not Excel"))]

        response = test_client.post(
            "/documents/batch",
            files=files,
            headers={"X-API-Key": test_api_key},
        )

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

        # Verify error structure
        assert len(data["errors"]) == 1
        error = data["errors"][0]
        assert "filename" in error
        assert "code" in error
        assert "message" in error
        assert isinstance(error["filename"], str)
        assert isinstance(error["code"], str)
        assert isinstance(error["message"], str)
