"""
API Integration Tests

Tests FastAPI endpoints with mocked services.
These tests verify the API layer works correctly with the service layer.
"""

import io
import tempfile
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch
from uuid import uuid4

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient


@dataclass
class MockServiceResult:
    """Mock result from inference service."""

    document_id: str = "test-doc-123"
    success: bool = True
    document_type: str = "invoice"
    fields: dict[str, str] = field(default_factory=lambda: {
        "InvoiceNumber": "INV-2024-001",
        "Amount": "1500.00",
        "InvoiceDate": "2024-01-15",
        "OCR": "12345678901234",
        "Bankgiro": "1234-5678",
    })
    confidence: dict[str, float] = field(default_factory=lambda: {
        "InvoiceNumber": 0.95,
        "Amount": 0.92,
        "InvoiceDate": 0.88,
        "OCR": 0.95,
        "Bankgiro": 0.90,
    })
    detections: list[dict[str, Any]] = field(default_factory=list)
    processing_time_ms: float = 150.5
    visualization_path: Path | None = None
    errors: list[str] = field(default_factory=list)

    # Business features (optional, populated when extract_line_items=True)
    line_items: dict | None = None
    vat_summary: dict | None = None
    vat_validation: dict | None = None


@pytest.fixture
def temp_storage_dir():
    """Create temporary storage directories."""
    with tempfile.TemporaryDirectory() as tmpdir:
        base = Path(tmpdir)
        uploads_dir = base / "uploads" / "inference"
        results_dir = base / "results"
        uploads_dir.mkdir(parents=True, exist_ok=True)
        results_dir.mkdir(parents=True, exist_ok=True)
        yield {
            "base": base,
            "uploads": uploads_dir,
            "results": results_dir,
        }


@pytest.fixture
def mock_inference_service():
    """Create a mock inference service."""
    service = MagicMock()
    service.is_initialized = True
    service.gpu_available = False

    # Create a realistic mock result
    mock_result = MockServiceResult()

    service.process_pdf.return_value = mock_result
    service.process_image.return_value = mock_result
    service.initialize.return_value = None

    return service


@pytest.fixture
def mock_storage_config(temp_storage_dir):
    """Create mock storage configuration."""
    from backend.web.config import StorageConfig

    return StorageConfig(
        upload_dir=temp_storage_dir["uploads"],
        result_dir=temp_storage_dir["results"],
        max_file_size_mb=50,
    )


@pytest.fixture
def mock_storage_helper(temp_storage_dir):
    """Create a mock storage helper."""
    helper = MagicMock()
    helper.get_uploads_base_path.return_value = temp_storage_dir["uploads"]
    helper.get_result_local_path.return_value = None
    helper.result_exists.return_value = False
    return helper


@pytest.fixture
def test_app(mock_inference_service, mock_storage_config, mock_storage_helper):
    """Create a test FastAPI application with mocked storage."""
    from backend.web.api.v1.public.inference import create_inference_router

    app = FastAPI()

    # Patch get_storage_helper to return our mock
    with patch(
        "backend.web.api.v1.public.inference.get_storage_helper",
        return_value=mock_storage_helper,
    ):
        inference_router = create_inference_router(mock_inference_service, mock_storage_config)
        app.include_router(inference_router)

    return app


@pytest.fixture
def client(test_app, mock_storage_helper):
    """Create a test client with storage helper patched."""
    with patch(
        "backend.web.api.v1.public.inference.get_storage_helper",
        return_value=mock_storage_helper,
    ):
        yield TestClient(test_app)


class TestHealthEndpoint:
    """Tests for health check endpoint."""

    def test_health_check(self, client, mock_inference_service):
        """Test health check returns status."""
        response = client.get("/api/v1/health")

        assert response.status_code == 200
        data = response.json()
        assert "status" in data
        assert "model_loaded" in data


class TestInferenceEndpoint:
    """Tests for inference endpoint."""

    def test_infer_pdf(self, client, mock_inference_service, mock_storage_helper, temp_storage_dir):
        """Test PDF inference endpoint."""
        # Create a minimal PDF content
        pdf_content = b"%PDF-1.4\n%test\n"

        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("test.pdf", io.BytesIO(pdf_content), "application/pdf")},
            )

        assert response.status_code == 200
        data = response.json()
        assert "result" in data
        assert data["result"]["success"] is True
        assert "InvoiceNumber" in data["result"]["fields"]

    def test_infer_image(self, client, mock_inference_service, mock_storage_helper):
        """Test image inference endpoint."""
        # Create minimal PNG header
        png_header = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100

        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("test.png", io.BytesIO(png_header), "image/png")},
            )

        assert response.status_code == 200
        data = response.json()
        assert "result" in data

    def test_infer_invalid_file_type(self, client, mock_storage_helper):
        """Test rejection of invalid file types."""
        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("test.txt", io.BytesIO(b"hello"), "text/plain")},
            )

        assert response.status_code == 400

    def test_infer_no_file(self, client, mock_storage_helper):
        """Test rejection when no file provided."""
        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post("/api/v1/infer")

        assert response.status_code == 422  # Validation error

    def test_infer_result_structure(self, client, mock_inference_service, mock_storage_helper):
        """Test that result has expected structure."""
        pdf_content = b"%PDF-1.4\n%test\n"

        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("test.pdf", io.BytesIO(pdf_content), "application/pdf")},
            )

        data = response.json()
        result = data["result"]

        # Check required fields
        assert "document_id" in result
        assert "success" in result
        assert "fields" in result
        assert "confidence" in result
        assert "processing_time_ms" in result


class TestInferenceResultFormat:
    """Tests for inference result formatting."""

    def test_result_fields_mapped_correctly(self, client, mock_inference_service, mock_storage_helper):
        """Test that fields are mapped to API response format."""
        pdf_content = b"%PDF-1.4\n%test\n"

        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("test.pdf", io.BytesIO(pdf_content), "application/pdf")},
            )

        data = response.json()
        fields = data["result"]["fields"]

        assert fields["InvoiceNumber"] == "INV-2024-001"
        assert fields["Amount"] == "1500.00"
        assert fields["InvoiceDate"] == "2024-01-15"

    def test_confidence_values_included(self, client, mock_inference_service, mock_storage_helper):
        """Test that confidence values are included."""
        pdf_content = b"%PDF-1.4\n%test\n"

        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("test.pdf", io.BytesIO(pdf_content), "application/pdf")},
            )

        data = response.json()
        confidence = data["result"]["confidence"]

        assert "InvoiceNumber" in confidence
        assert confidence["InvoiceNumber"] == 0.95


class TestErrorHandling:
    """Tests for error handling in API."""

    def test_service_error_handling(self, client, mock_inference_service, mock_storage_helper):
        """Test handling of service errors."""
        mock_inference_service.process_pdf.side_effect = Exception("Processing failed")

        pdf_content = b"%PDF-1.4\n%test\n"
        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("test.pdf", io.BytesIO(pdf_content), "application/pdf")},
            )

        # Should return error response
        assert response.status_code >= 400

    def test_empty_file_handling(self, client, mock_storage_helper):
        """Test handling of empty files."""
        # Empty file still has valid content type
        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("test.pdf", io.BytesIO(b""), "application/pdf")},
            )

        # Empty file may be processed or rejected depending on implementation
        # Just verify we get a response
        assert response.status_code in [200, 400, 422, 500]


class TestResponseFormat:
    """Tests for API response format consistency."""

    def test_success_response_format(self, client, mock_inference_service, mock_storage_helper):
        """Test successful response format."""
        pdf_content = b"%PDF-1.4\n%test\n"

        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("test.pdf", io.BytesIO(pdf_content), "application/pdf")},
            )

        assert response.status_code == 200
        assert response.headers["content-type"] == "application/json"

        data = response.json()
        assert isinstance(data, dict)
        assert "result" in data

    def test_json_serialization(self, client, mock_inference_service, mock_storage_helper):
        """Test that all result fields are JSON serializable."""
        pdf_content = b"%PDF-1.4\n%test\n"

        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("test.pdf", io.BytesIO(pdf_content), "application/pdf")},
            )

        # If this doesn't raise, JSON is valid
        data = response.json()
        assert data is not None


class TestDocumentIdGeneration:
    """Tests for document ID handling."""

    def test_document_id_generated(self, client, mock_inference_service, mock_storage_helper):
        """Test that document ID is generated."""
        pdf_content = b"%PDF-1.4\n%test\n"

        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("test.pdf", io.BytesIO(pdf_content), "application/pdf")},
            )

        data = response.json()
        assert "document_id" in data["result"]
        assert data["result"]["document_id"] is not None

    def test_document_id_from_filename(self, client, mock_inference_service, mock_storage_helper):
        """Test document ID derived from filename."""
        pdf_content = b"%PDF-1.4\n%test\n"

        with patch(
            "backend.web.api.v1.public.inference.get_storage_helper",
            return_value=mock_storage_helper,
        ):
            response = client.post(
                "/api/v1/infer",
                files={"file": ("my_invoice_123.pdf", io.BytesIO(pdf_content), "application/pdf")},
            )

        data = response.json()
        # Document ID should be set (either from filename or generated)
        assert data["result"]["document_id"] is not None
