"""Unit tests for VIN preprocessor."""
import io
from unittest.mock import patch, MagicMock

import numpy as np
import pytest
from PIL import Image

from app.preprocessors.vin_preprocessor import VinPreprocessor, vin_preprocessor


def create_test_image(width: int = 400, height: int = 100, color: int = 128) -> bytes:
    """Create a simple test image."""
    image = Image.new("RGB", (width, height), (color, color, color))
    buffer = io.BytesIO()
    image.save(buffer, format="PNG")
    return buffer.getvalue()


def create_grayscale_test_image(width: int = 400, height: int = 100) -> bytes:
    """Create a grayscale test image."""
    image = Image.new("L", (width, height), 128)
    buffer = io.BytesIO()
    image.save(buffer, format="PNG")
    return buffer.getvalue()


class TestVinPreprocessor:
    """Tests for VIN-optimized preprocessing."""

    def test_preprocess_returns_result(self) -> None:
        """Test basic preprocessing returns a result."""
        preprocessor = VinPreprocessor()
        image_bytes = create_test_image()

        result = preprocessor.preprocess(image_bytes)

        assert result.image_bytes is not None
        assert len(result.image_bytes) > 0
        assert "grayscale" in result.preprocessing_applied

    def test_preprocess_applies_all_steps(self) -> None:
        """Test preprocessing applies all requested steps."""
        preprocessor = VinPreprocessor()
        image_bytes = create_test_image()

        result = preprocessor.preprocess(
            image_bytes,
            apply_clahe=True,
            apply_deskew=True,
            apply_denoise=True,
            apply_threshold=True,
        )

        assert "grayscale" in result.preprocessing_applied
        assert "resolution_check" in result.preprocessing_applied
        assert "clahe" in result.preprocessing_applied
        assert "deskew" in result.preprocessing_applied
        assert "denoise" in result.preprocessing_applied
        assert "threshold" in result.preprocessing_applied

    def test_preprocess_skips_disabled_steps(self) -> None:
        """Test preprocessing skips disabled steps."""
        preprocessor = VinPreprocessor()
        image_bytes = create_test_image()

        result = preprocessor.preprocess(
            image_bytes,
            apply_clahe=False,
            apply_deskew=False,
            apply_denoise=False,
            apply_threshold=False,
        )

        assert "clahe" not in result.preprocessing_applied
        assert "deskew" not in result.preprocessing_applied
        assert "denoise" not in result.preprocessing_applied
        assert "threshold" not in result.preprocessing_applied

    def test_preprocess_output_is_valid_image(self) -> None:
        """Test preprocessing output is a valid PNG image."""
        preprocessor = VinPreprocessor()
        image_bytes = create_test_image()

        result = preprocessor.preprocess(image_bytes)

        # Should be able to open as image
        output_image = Image.open(io.BytesIO(result.image_bytes))
        assert output_image is not None
        assert output_image.format == "PNG"

    def test_preprocess_handles_grayscale_input(self) -> None:
        """Test preprocessing handles grayscale input."""
        preprocessor = VinPreprocessor()
        image_bytes = create_grayscale_test_image()

        result = preprocessor.preprocess(image_bytes)

        assert result.image_bytes is not None
        assert len(result.image_bytes) > 0

    def test_preprocess_handles_rgba_input(self) -> None:
        """Test preprocessing handles RGBA input."""
        preprocessor = VinPreprocessor()

        # Create RGBA image
        image = Image.new("RGBA", (400, 100), (128, 128, 128, 255))
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")

        result = preprocessor.preprocess(buffer.getvalue())

        assert result.image_bytes is not None
        assert "convert_rgb" in result.preprocessing_applied

    def test_singleton_instance(self) -> None:
        """Test singleton instance is available."""
        assert vin_preprocessor is not None
        assert isinstance(vin_preprocessor, VinPreprocessor)


class TestVinPreprocessorDeskew:
    """Tests for deskew functionality."""

    def test_deskew_no_change_for_straight_image(self) -> None:
        """Test deskew doesn't change a straight image significantly."""
        preprocessor = VinPreprocessor()

        # Create image with horizontal line (no skew)
        image = np.zeros((100, 400), dtype=np.uint8)
        image[50, 50:350] = 255  # Horizontal line

        result = preprocessor._deskew(image)

        # Shape should be similar (might change slightly due to processing)
        assert result.shape[0] > 0
        assert result.shape[1] > 0


class TestVinPreprocessorCLAHE:
    """Tests for CLAHE contrast enhancement."""

    def test_clahe_improves_contrast(self) -> None:
        """Test CLAHE changes the image."""
        preprocessor = VinPreprocessor()

        # Create low contrast image
        image = np.full((100, 400), 128, dtype=np.uint8)

        result = preprocessor._apply_clahe(image)

        # Result should be numpy array of same shape
        assert result.shape == image.shape


class TestVinPreprocessorDenoise:
    """Tests for denoising functionality."""

    def test_denoise_reduces_noise(self) -> None:
        """Test denoising works on noisy image."""
        preprocessor = VinPreprocessor()

        # Create noisy image
        image = np.random.randint(0, 256, (100, 400), dtype=np.uint8)

        result = preprocessor._denoise(image)

        # Should return array of same shape
        assert result.shape == image.shape


class TestVinPreprocessorThreshold:
    """Tests for adaptive thresholding."""

    def test_threshold_creates_binary_image(self) -> None:
        """Test thresholding creates binary output."""
        preprocessor = VinPreprocessor()

        # Create grayscale image
        image = np.full((100, 400), 128, dtype=np.uint8)

        result = preprocessor._adaptive_threshold(image)

        # Result should be binary (only 0 and 255)
        unique_values = np.unique(result)
        assert len(unique_values) <= 2


class TestVinPreprocessorOtsu:
    """Tests for Otsu's thresholding preprocessing."""

    def test_otsu_threshold_creates_binary_image(self) -> None:
        """Test Otsu's thresholding creates binary output."""
        preprocessor = VinPreprocessor()
        image = np.full((100, 400), 128, dtype=np.uint8)

        result = preprocessor._otsu_threshold(image)

        unique_values = np.unique(result)
        assert len(unique_values) <= 2

    def test_preprocess_otsu_returns_result(self) -> None:
        """Test Otsu preprocessing pipeline returns valid result."""
        preprocessor = VinPreprocessor()
        image_bytes = create_test_image()

        result = preprocessor.preprocess_otsu(image_bytes)

        assert result.image_bytes is not None
        assert len(result.image_bytes) > 0
        assert "otsu_threshold" in result.preprocessing_applied
        assert "grayscale" in result.preprocessing_applied


class TestVinPreprocessorResolution:
    """Tests for resolution upscaling."""

    def test_upscale_small_image(self) -> None:
        """Test small images are upscaled."""
        preprocessor = VinPreprocessor()
        small_image = np.full((50, 200), 128, dtype=np.uint8)

        result = preprocessor._ensure_minimum_resolution(small_image)

        assert result.shape[1] >= preprocessor.MIN_WIDTH_FOR_VIN

    def test_no_upscale_large_image(self) -> None:
        """Test large images are not upscaled."""
        preprocessor = VinPreprocessor()
        large_image = np.full((200, 800), 128, dtype=np.uint8)

        result = preprocessor._ensure_minimum_resolution(large_image)

        assert result.shape == large_image.shape


class TestVinRegionDetection:
    """Tests for VIN region detection."""

    def test_detect_vin_region_returns_none_for_empty(self) -> None:
        """Test region detection returns None for empty image."""
        preprocessor = VinPreprocessor()

        # Solid color image - no regions to detect
        image_bytes = create_test_image(color=128)

        result = preprocessor.detect_vin_region(image_bytes)

        # May return None for uniform image
        # This is expected behavior
        assert result is None or result.width > 0
