"""Integration tests for VIN extraction endpoint and engine integration."""
import io
from unittest.mock import patch, MagicMock

import pytest
from fastapi.testclient import TestClient
from PIL import Image, ImageDraw

from app.engines.base_engine import OcrConfig, OcrEngineResult, WordBox
from app.main import app


@pytest.fixture
def client() -> TestClient:
    """Create test client."""
    return TestClient(app)


def create_vin_image(vin: str = "1HGBH41JXMN109186") -> bytes:
    """Create a test image with VIN text."""
    # Create white image
    image = Image.new("RGB", (400, 100), (255, 255, 255))
    draw = ImageDraw.Draw(image)

    # Draw VIN text (use default font)
    draw.text((50, 40), vin, fill=(0, 0, 0))

    buffer = io.BytesIO()
    image.save(buffer, format="PNG")
    return buffer.getvalue()


def create_empty_image() -> bytes:
    """Create an empty test image."""
    image = Image.new("RGB", (400, 100), (255, 255, 255))
    buffer = io.BytesIO()
    image.save(buffer, format="PNG")
    return buffer.getvalue()


class TestVinExtractionEndpoint:
    """Tests for POST /extract/vin endpoint."""

    def test_endpoint_exists(self, client: TestClient) -> None:
        """Test VIN endpoint is registered."""
        response = client.get("/")
        assert response.status_code == 200
        data = response.json()
        assert any("vin" in endpoint.lower() for endpoint in data.get("endpoints", []))

    def test_extract_vin_no_file(self, client: TestClient) -> None:
        """Test endpoint returns error when no file provided."""
        response = client.post("/extract/vin")
        assert response.status_code == 422  # Validation error

    def test_extract_vin_empty_file(self, client: TestClient) -> None:
        """Test endpoint returns error for empty file."""
        response = client.post(
            "/extract/vin",
            files={"file": ("empty.png", b"", "image/png")},
        )
        assert response.status_code == 400
        assert "empty" in response.json()["detail"].lower()

    def test_extract_vin_large_file(self, client: TestClient) -> None:
        """Test endpoint returns error for file too large."""
        # Create file larger than 10MB
        large_content = b"x" * (11 * 1024 * 1024)

        response = client.post(
            "/extract/vin",
            files={"file": ("large.png", large_content, "image/png")},
        )
        assert response.status_code == 413

    @patch("app.extractors.vin_extractor.vin_extractor.extract")
    def test_extract_vin_success(
        self, mock_extract: MagicMock, client: TestClient
    ) -> None:
        """Test successful VIN extraction."""
        from app.extractors.vin_extractor import VinExtractionResult

        mock_extract.return_value = VinExtractionResult(
            success=True,
            vin="1HGBH41JXMN109186",
            confidence=0.94,
            bounding_box=None,
            alternatives=[],
            processing_time_ms=500,
        )

        image_bytes = create_vin_image()
        response = client.post(
            "/extract/vin",
            files={"file": ("vin.png", image_bytes, "image/png")},
        )

        assert response.status_code == 200
        data = response.json()
        assert data["success"] is True
        assert data["vin"] == "1HGBH41JXMN109186"
        assert data["confidence"] == 0.94
        assert "processingTimeMs" in data

    @patch("app.extractors.vin_extractor.vin_extractor.extract")
    def test_extract_vin_not_found(
        self, mock_extract: MagicMock, client: TestClient
    ) -> None:
        """Test VIN not found returns success=false."""
        from app.extractors.vin_extractor import VinExtractionResult

        mock_extract.return_value = VinExtractionResult(
            success=False,
            vin=None,
            confidence=0.0,
            error="No VIN pattern found in image",
            processing_time_ms=300,
        )

        image_bytes = create_empty_image()
        response = client.post(
            "/extract/vin",
            files={"file": ("empty.png", image_bytes, "image/png")},
        )

        assert response.status_code == 200
        data = response.json()
        assert data["success"] is False
        assert data["vin"] is None
        assert data["error"] == "No VIN pattern found in image"

    @patch("app.extractors.vin_extractor.vin_extractor.extract")
    def test_extract_vin_with_alternatives(
        self, mock_extract: MagicMock, client: TestClient
    ) -> None:
        """Test VIN extraction with alternatives."""
        from app.extractors.vin_extractor import VinExtractionResult, VinAlternative

        mock_extract.return_value = VinExtractionResult(
            success=True,
            vin="1HGBH41JXMN109186",
            confidence=0.94,
            bounding_box=None,
            alternatives=[
                VinAlternative(vin="1HGBH41JXMN109186", confidence=0.72),
            ],
            processing_time_ms=600,
        )

        image_bytes = create_vin_image()
        response = client.post(
            "/extract/vin",
            files={"file": ("vin.png", image_bytes, "image/png")},
        )

        assert response.status_code == 200
        data = response.json()
        assert data["success"] is True
        assert len(data["alternatives"]) == 1
        assert data["alternatives"][0]["confidence"] == 0.72

    @patch("app.extractors.vin_extractor.vin_extractor.extract")
    def test_extract_vin_with_bounding_box(
        self, mock_extract: MagicMock, client: TestClient
    ) -> None:
        """Test VIN extraction includes bounding box."""
        from app.extractors.vin_extractor import VinExtractionResult
        from app.preprocessors.vin_preprocessor import BoundingBox

        mock_extract.return_value = VinExtractionResult(
            success=True,
            vin="1HGBH41JXMN109186",
            confidence=0.94,
            bounding_box=BoundingBox(x=50, y=40, width=300, height=20),
            alternatives=[],
            processing_time_ms=500,
        )

        image_bytes = create_vin_image()
        response = client.post(
            "/extract/vin",
            files={"file": ("vin.png", image_bytes, "image/png")},
        )

        assert response.status_code == 200
        data = response.json()
        assert data["boundingBox"] is not None
        assert data["boundingBox"]["x"] == 50
        assert data["boundingBox"]["y"] == 40
        assert data["boundingBox"]["width"] == 300
        assert data["boundingBox"]["height"] == 20


class TestVinExtractionContentTypes:
    """Tests for different content types."""

    @patch("app.extractors.vin_extractor.vin_extractor.extract")
    def test_accepts_jpeg(
        self, mock_extract: MagicMock, client: TestClient
    ) -> None:
        """Test endpoint accepts JPEG images."""
        from app.extractors.vin_extractor import VinExtractionResult

        mock_extract.return_value = VinExtractionResult(
            success=True,
            vin="1HGBH41JXMN109186",
            confidence=0.9,
            processing_time_ms=400,
        )

        # Create JPEG image
        image = Image.new("RGB", (400, 100), (255, 255, 255))
        buffer = io.BytesIO()
        image.save(buffer, format="JPEG")

        response = client.post(
            "/extract/vin",
            files={"file": ("vin.jpg", buffer.getvalue(), "image/jpeg")},
        )

        assert response.status_code == 200

    @patch("app.extractors.vin_extractor.vin_extractor.extract")
    def test_accepts_png(
        self, mock_extract: MagicMock, client: TestClient
    ) -> None:
        """Test endpoint accepts PNG images."""
        from app.extractors.vin_extractor import VinExtractionResult

        mock_extract.return_value = VinExtractionResult(
            success=True,
            vin="1HGBH41JXMN109186",
            confidence=0.9,
            processing_time_ms=400,
        )

        image_bytes = create_vin_image()
        response = client.post(
            "/extract/vin",
            files={"file": ("vin.png", image_bytes, "image/png")},
        )

        assert response.status_code == 200


# ---------------------------------------------------------------------------
# VIN extractor engine integration tests
# ---------------------------------------------------------------------------


class TestVinExtractorEngineIntegration:
    """Tests verifying VinExtractor integrates correctly with engine abstraction."""

    @patch("app.extractors.vin_extractor.create_engine")
    def test_perform_ocr_calls_engine_with_vin_config(
        self, mock_create_engine: MagicMock
    ) -> None:
        """_perform_ocr passes VIN whitelist and angle_cls to engine."""
        from app.extractors.vin_extractor import VinExtractor

        mock_engine = MagicMock()
        mock_engine.recognize.return_value = OcrEngineResult(
            text="1HGBH41JXMN109186",
            confidence=0.94,
            word_boxes=[WordBox(text="1HGBH41JXMN109186", confidence=0.94)],
            engine_name="paddleocr",
        )
        mock_create_engine.return_value = mock_engine

        extractor = VinExtractor()
        text, confidences = extractor._perform_ocr(b"fake_image")

        mock_engine.recognize.assert_called_once()
        call_config = mock_engine.recognize.call_args[0][1]
        assert isinstance(call_config, OcrConfig)
        assert call_config.char_whitelist == VinExtractor.VIN_WHITELIST
        assert call_config.use_angle_cls is True
        assert call_config.single_line is False
        assert call_config.single_word is False
        assert text == "1HGBH41JXMN109186"
        assert confidences == [0.94]

    @patch("app.extractors.vin_extractor.create_engine")
    def test_perform_ocr_single_line_mode(
        self, mock_create_engine: MagicMock
    ) -> None:
        """_perform_ocr passes single_line flag to engine config."""
        from app.extractors.vin_extractor import VinExtractor

        mock_engine = MagicMock()
        mock_engine.recognize.return_value = OcrEngineResult(
            text="VIN123", confidence=0.9, word_boxes=[], engine_name="paddleocr"
        )
        mock_create_engine.return_value = mock_engine

        extractor = VinExtractor()
        extractor._perform_ocr(b"img", single_line=True)

        call_config = mock_engine.recognize.call_args[0][1]
        assert call_config.single_line is True
        assert call_config.single_word is False

    @patch("app.extractors.vin_extractor.create_engine")
    def test_perform_ocr_single_word_mode(
        self, mock_create_engine: MagicMock
    ) -> None:
        """_perform_ocr passes single_word flag to engine config."""
        from app.extractors.vin_extractor import VinExtractor

        mock_engine = MagicMock()
        mock_engine.recognize.return_value = OcrEngineResult(
            text="VIN123", confidence=0.9, word_boxes=[], engine_name="paddleocr"
        )
        mock_create_engine.return_value = mock_engine

        extractor = VinExtractor()
        extractor._perform_ocr(b"img", single_word=True)

        call_config = mock_engine.recognize.call_args[0][1]
        assert call_config.single_word is True
        assert call_config.single_line is False

    def test_calculate_base_confidence_empty_returns_default(self) -> None:
        """Empty word confidences return 0.5 default."""
        from app.extractors.vin_extractor import VinExtractor

        extractor = VinExtractor.__new__(VinExtractor)
        assert extractor._calculate_base_confidence([]) == 0.5

    def test_calculate_base_confidence_weighted_blend(self) -> None:
        """Confidence = 70% average + 30% minimum."""
        from app.extractors.vin_extractor import VinExtractor

        extractor = VinExtractor.__new__(VinExtractor)
        # avg = (0.9 + 0.8) / 2 = 0.85, min = 0.8
        # result = 0.7 * 0.85 + 0.3 * 0.8 = 0.595 + 0.24 = 0.835
        result = extractor._calculate_base_confidence([0.9, 0.8])
        assert abs(result - 0.835) < 0.001

    def test_calculate_base_confidence_single_value(self) -> None:
        """Single confidence value: avg == min, so result equals that value."""
        from app.extractors.vin_extractor import VinExtractor

        extractor = VinExtractor.__new__(VinExtractor)
        result = extractor._calculate_base_confidence([0.92])
        assert abs(result - 0.92) < 0.001
