"""
图像处理器测试

测试ImageProcessor类的所有功能，包括加载、处理、转换、缓存和边界条件。
"""

import io
from pathlib import Path
from unittest.mock import Mock

import pytest
from PIL import Image

from src.symphra_excel.images.image_processor import (
    CellImageOptions,
    ImageFormat,
    ImageProcessor,
    ImageProperties,
)
from src.symphra_excel.utils.exceptions import ValidationError


class TestImageProcessorInit:
    """测试ImageProcessor初始化"""

    def test_init_default(self) -> None:
        """测试默认初始化"""
        processor = ImageProcessor()
        assert processor.memory_manager is None
        assert processor._image_cache == {}

    def test_init_with_memory_manager(self) -> None:
        """测试使用内存管理器初始化"""
        from src.symphra_excel.utils.memory_manager import MemoryManager

        manager = MemoryManager()
        processor = ImageProcessor(memory_manager=manager)
        assert processor.memory_manager is manager


class TestLoadImage:
    """测试图像加载功能"""

    @pytest.fixture
    def processor(self) -> ImageProcessor:
        """创建处理器实例"""
        return ImageProcessor()

    @pytest.fixture
    def test_image_bytes(self) -> bytes:
        """创建测试图像字节数据"""
        image = Image.new("RGB", (50, 50), color="red")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        return buffer.getvalue()

    def test_load_image_from_bytes(
        self, processor: ImageProcessor, test_image_bytes: bytes
    ) -> None:
        """测试从bytes加载图像"""
        image = processor.load_image(test_image_bytes)
        assert isinstance(image, Image.Image)
        assert image.size == (50, 50)

    def test_load_image_from_bytesio(
        self, processor: ImageProcessor, test_image_bytes: bytes
    ) -> None:
        """测试从BytesIO加载图像"""
        buffer = io.BytesIO(test_image_bytes)
        image = processor.load_image(buffer)
        assert isinstance(image, Image.Image)
        assert image.size == (50, 50)

    def test_load_image_from_path(
        self, processor: ImageProcessor, tmp_path: Path
    ) -> None:
        """测试从路径加载图像"""
        # 创建临时图像文件
        image_path = tmp_path / "test.png"
        test_image = Image.new("RGB", (100, 100), color="blue")
        test_image.save(image_path, format="PNG")

        # 加载
        loaded = processor.load_image(str(image_path))
        assert isinstance(loaded, Image.Image)
        assert loaded.size == (100, 100)

    def test_load_image_from_pathlib_path(
        self, processor: ImageProcessor, tmp_path: Path
    ) -> None:
        """测试从Path对象加载图像"""
        image_path = tmp_path / "test2.png"
        test_image = Image.new("RGB", (80, 80), color="green")
        test_image.save(image_path, format="PNG")

        loaded = processor.load_image(image_path)
        assert isinstance(loaded, Image.Image)
        assert loaded.size == (80, 80)

    def test_load_image_invalid_type(self, processor: ImageProcessor) -> None:
        """测试加载无效类型"""
        with pytest.raises(ValidationError):
            processor.load_image(12345)  # type: ignore

    def test_load_image_nonexistent_file(self, processor: ImageProcessor) -> None:
        """测试加载不存在的文件"""
        from src.symphra_excel.utils.exceptions import FileFormatError

        with pytest.raises((ValidationError, FileNotFoundError, FileFormatError)):
            processor.load_image("nonexistent_image.png")

    def test_load_image_corrupted_data(self, processor: ImageProcessor) -> None:
        """测试加载损坏的数据"""
        with pytest.raises((ValidationError, Exception)):
            processor.load_image(b"not an image")

    def test_load_image_with_memory_manager(self) -> None:
        """测试使用内存管理器加载图像"""
        manager = Mock()
        processor = ImageProcessor(memory_manager=manager)

        image = Image.new("RGB", (100, 100), color="yellow")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        buffer.seek(0)

        processor.load_image(buffer)
        manager.allocate_memory.assert_called_once()


class TestGetImageProperties:
    """测试获取图像属性"""

    @pytest.fixture
    def processor(self) -> ImageProcessor:
        """创建处理器实例"""
        return ImageProcessor()

    def test_get_properties_png(self, processor: ImageProcessor) -> None:
        """测试获取PNG图像属性"""
        image = Image.new("RGB", (200, 150), color="white")
        image.format = "PNG"

        props = processor.get_image_properties(image)

        assert props.width == 200
        assert props.height == 150
        assert props.format == ImageFormat.PNG
        assert props.size_bytes > 0
        assert props.mode == "RGB"

    def test_get_properties_jpeg(self, processor: ImageProcessor) -> None:
        """测试获取JPEG图像属性"""
        image = Image.new("RGB", (300, 200), color="black")
        image.format = "JPEG"

        props = processor.get_image_properties(image)

        assert props.width == 300
        assert props.height == 200
        assert props.format == ImageFormat.JPEG

    def test_get_properties_no_format(self, processor: ImageProcessor) -> None:
        """测试获取无格式图像属性"""
        image = Image.new("RGB", (100, 100), color="gray")
        # 不设置format

        props = processor.get_image_properties(image)

        # 应该默认为PNG
        assert props.format == ImageFormat.PNG


class TestResizeImage:
    """测试图像调整大小"""

    @pytest.fixture
    def processor(self) -> ImageProcessor:
        """创建处理器实例"""
        return ImageProcessor()

    @pytest.fixture
    def test_image(self) -> Image.Image:
        """创建测试图像"""
        return Image.new("RGB", (400, 300), color="purple")

    def test_resize_both_dimensions(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试同时指定宽高"""
        resized = processor.resize_image(
            test_image, width=200, height=150, maintain_aspect_ratio=False
        )
        assert resized.size == (200, 150)

    def test_resize_width_only_maintain_ratio(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试只指定宽度并保持宽高比"""
        resized = processor.resize_image(test_image, width=200)
        assert resized.size == (200, 150)  # 保持4:3比例

    def test_resize_height_only_maintain_ratio(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试只指定高度并保持宽高比"""
        resized = processor.resize_image(test_image, height=150)
        assert resized.size == (200, 150)  # 保持4:3比例

    def test_resize_both_dimensions_maintain_ratio(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试指定宽高并保持宽高比"""
        # 400x300 -> 100x100, 保持比例会变成 100x75
        resized = processor.resize_image(test_image, width=100, height=100)
        assert resized.size == (100, 75)

    def test_resize_none_dimensions(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试不指定尺寸"""
        resized = processor.resize_image(test_image)
        assert resized.size == test_image.size

    def test_resize_without_maintaining_ratio(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试不保持宽高比"""
        resized = processor.resize_image(
            test_image, width=100, height=200, maintain_aspect_ratio=False
        )
        assert resized.size == (100, 200)

    def test_resize_with_memory_manager(self) -> None:
        """测试使用内存管理器调整大小"""
        manager = Mock()
        processor = ImageProcessor(memory_manager=manager)

        image = Image.new("RGB", (200, 200), color="cyan")
        processor.resize_image(image, width=100, height=100)

        # 应该调用内存管理器
        assert manager.deallocate_memory.called or manager.allocate_memory.called


class TestConvertFormat:
    """测试格式转换"""

    @pytest.fixture
    def processor(self) -> ImageProcessor:
        """创建处理器实例"""
        return ImageProcessor()

    def test_convert_png_to_jpeg(self, processor: ImageProcessor) -> None:
        """测试PNG转JPEG"""
        image = Image.new("RGB", (100, 100), color="orange")
        buffer = processor.convert_format(image, ImageFormat.JPEG, quality=90)

        assert isinstance(buffer, io.BytesIO)
        loaded = Image.open(buffer)
        assert loaded.format == "JPEG"

    def test_convert_rgba_to_jpeg(self, processor: ImageProcessor) -> None:
        """测试RGBA转JPEG"""
        image = Image.new("RGBA", (100, 100), color=(255, 0, 0, 128))
        buffer = processor.convert_format(image, ImageFormat.JPEG)

        loaded = Image.open(buffer)
        assert loaded.format == "JPEG"
        assert loaded.mode == "RGB"

    def test_convert_to_png(self, processor: ImageProcessor) -> None:
        """测试转换为PNG"""
        image = Image.new("RGB", (50, 50), color="magenta")
        buffer = processor.convert_format(image, ImageFormat.PNG)

        loaded = Image.open(buffer)
        assert loaded.format == "PNG"

    def test_convert_with_quality(self, processor: ImageProcessor) -> None:
        """测试指定质量转换"""
        image = Image.new("RGB", (100, 100), color="brown")

        buffer_high = processor.convert_format(image, ImageFormat.JPEG, quality=95)
        buffer_low = processor.convert_format(image, ImageFormat.JPEG, quality=50)

        # 高质量应该有更大的文件
        assert len(buffer_high.getvalue()) > len(buffer_low.getvalue())


class TestOptimizeForExcel:
    """测试Excel优化"""

    @pytest.fixture
    def processor(self) -> ImageProcessor:
        """创建处理器实例"""
        return ImageProcessor()

    def test_optimize_small_image(self, processor: ImageProcessor) -> None:
        """测试优化小图像"""
        image = Image.new("RGB", (400, 300), color="pink")
        buffer = processor.optimize_for_excel(image)

        assert isinstance(buffer, io.BytesIO)
        loaded = Image.open(buffer)
        assert loaded.format == "PNG"

    def test_optimize_large_image(self, processor: ImageProcessor) -> None:
        """测试优化大图像"""
        image = Image.new("RGB", (2000, 1500), color="gray")
        buffer = processor.optimize_for_excel(image, max_width=800, max_height=600)

        loaded = Image.open(buffer)
        assert loaded.size[0] <= 800
        assert loaded.size[1] <= 600

    def test_optimize_with_custom_params(self, processor: ImageProcessor) -> None:
        """测试自定义参数优化"""
        image = Image.new("RGB", (1000, 800), color="navy")
        buffer = processor.optimize_for_excel(
            image, max_width=500, max_height=400, quality=70
        )

        loaded = Image.open(buffer)
        assert loaded.size[0] <= 500
        assert loaded.size[1] <= 400


class TestCacheOperations:
    """测试缓存操作"""

    @pytest.fixture
    def processor(self) -> ImageProcessor:
        """创建处理器实例"""
        return ImageProcessor()

    @pytest.fixture
    def test_image(self) -> Image.Image:
        """创建测试图像"""
        return Image.new("RGB", (100, 100), color="teal")

    def test_cache_image(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试缓存图像"""
        processor.cache_image("test_id", test_image)
        assert "test_id" in processor._image_cache

    def test_get_cached_image_exists(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试获取存在的缓存图像"""
        processor.cache_image("test_id", test_image)
        cached = processor.get_cached_image("test_id")

        assert cached is not None
        assert cached.size == test_image.size
        assert cached is not test_image  # 应该是副本

    def test_get_cached_image_not_exists(self, processor: ImageProcessor) -> None:
        """测试获取不存在的缓存图像"""
        cached = processor.get_cached_image("nonexistent")
        assert cached is None

    def test_clear_cache(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试清除缓存"""
        processor.cache_image("test1", test_image)
        processor.cache_image("test2", test_image)

        processor.clear_cache()
        assert len(processor._image_cache) == 0

    def test_clear_cache_with_memory_manager(self) -> None:
        """测试使用内存管理器清除缓存"""
        manager = Mock()
        processor = ImageProcessor(memory_manager=manager)

        processor.clear_cache()
        manager.optimize_memory.assert_called_once()


class TestValidateImageForExcel:
    """测试Excel图像验证"""

    @pytest.fixture
    def processor(self) -> ImageProcessor:
        """创建处理器实例"""
        return ImageProcessor()

    def test_validate_valid_image(self, processor: ImageProcessor) -> None:
        """测试有效图像验证"""
        image = Image.new("RGB", (800, 600), color="lime")
        image.format = "PNG"

        assert processor.validate_image_for_excel(image) is True

    def test_validate_too_large_file(self, processor: ImageProcessor) -> None:
        """测试文件过大"""
        # 创建一个会生成大文件的图像
        image = Image.new("RGB", (5000, 5000), color="white")
        image.format = "PNG"

        # 使用非常小的max_size_mb来测试
        assert processor.validate_image_for_excel(image, max_size_mb=0.01) is False

    def test_validate_too_large_dimensions(self, processor: ImageProcessor) -> None:
        """测试尺寸过大"""
        image = Image.new("RGB", (20000, 20000), color="black")
        image.format = "PNG"

        assert processor.validate_image_for_excel(image) is False

    def test_validate_valid_jpeg(self, processor: ImageProcessor) -> None:
        """测试有效JPEG"""
        image = Image.new("RGB", (400, 300), color="silver")
        image.format = "JPEG"

        assert processor.validate_image_for_excel(image) is True


class TestBase64Operations:
    """测试Base64转换"""

    @pytest.fixture
    def processor(self) -> ImageProcessor:
        """创建处理器实例"""
        return ImageProcessor()

    @pytest.fixture
    def test_image(self) -> Image.Image:
        """创建测试图像"""
        return Image.new("RGB", (50, 50), color="violet")

    def test_image_to_base64(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试图像转base64"""
        base64_str = processor.image_to_base64(test_image, ImageFormat.PNG)

        assert isinstance(base64_str, str)
        assert len(base64_str) > 0

    def test_base64_to_image(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试base64转图像"""
        base64_str = processor.image_to_base64(test_image, ImageFormat.PNG)
        decoded = processor.base64_to_image(base64_str)

        assert isinstance(decoded, Image.Image)
        assert decoded.size == test_image.size

    def test_roundtrip_base64(
        self, processor: ImageProcessor, test_image: Image.Image
    ) -> None:
        """测试base64往返转换"""
        original = test_image
        base64_str = processor.image_to_base64(original, ImageFormat.PNG)
        decoded = processor.base64_to_image(base64_str)

        assert decoded.size == original.size
        assert decoded.mode == original.mode

    def test_base64_to_image_invalid(self, processor: ImageProcessor) -> None:
        """测试无效base64"""
        with pytest.raises(Exception):
            processor.base64_to_image("invalid_base64_string")


class TestDataClasses:
    """测试数据类"""

    def test_image_properties_creation(self) -> None:
        """测试ImageProperties创建"""
        props = ImageProperties(
            width=100,
            height=200,
            format=ImageFormat.PNG,
            size_bytes=5000,
            dpi=(300, 300),
            mode="RGB",
        )

        assert props.width == 100
        assert props.height == 200
        assert props.format == ImageFormat.PNG
        assert props.size_bytes == 5000
        assert props.dpi == (300, 300)
        assert props.mode == "RGB"

    def test_image_properties_defaults(self) -> None:
        """测试ImageProperties默认值"""
        props = ImageProperties(
            width=100, height=200, format=ImageFormat.JPEG, size_bytes=3000
        )

        assert props.dpi is None
        assert props.mode == "RGB"

    def test_cell_image_options_defaults(self) -> None:
        """测试CellImageOptions默认值"""
        options = CellImageOptions()

        assert options.anchor_to_cell is True
        assert options.move_with_cells is True
        assert options.resize_with_cells is True
        assert options.maintain_aspect_ratio is True
        assert options.max_width is None
        assert options.max_height is None
        assert options.position == "center"
        assert options.margin == 2

    def test_cell_image_options_custom(self) -> None:
        """测试CellImageOptions自定义值"""
        options = CellImageOptions(
            anchor_to_cell=False,
            move_with_cells=False,
            maintain_aspect_ratio=False,
            max_width=500,
            max_height=400,
            position="top-left",
            margin=10,
        )

        assert options.anchor_to_cell is False
        assert options.move_with_cells is False
        assert options.maintain_aspect_ratio is False
        assert options.max_width == 500
        assert options.max_height == 400
        assert options.position == "top-left"
        assert options.margin == 10


class TestImageFormat:
    """测试ImageFormat枚举"""

    def test_image_format_values(self) -> None:
        """测试ImageFormat值"""
        assert ImageFormat.PNG.value == "PNG"
        assert ImageFormat.JPEG.value == "JPEG"
        assert ImageFormat.BMP.value == "BMP"
        assert ImageFormat.GIF.value == "GIF"
        assert ImageFormat.WEBP.value == "WEBP"

    def test_image_format_from_string(self) -> None:
        """测试从字符串创建ImageFormat"""
        fmt = ImageFormat("PNG")
        assert fmt == ImageFormat.PNG


class TestEdgeCases:
    """测试边界条件"""

    @pytest.fixture
    def processor(self) -> ImageProcessor:
        """创建处理器实例"""
        return ImageProcessor()

    def test_resize_zero_dimensions(self, processor: ImageProcessor) -> None:
        """测试调整到0尺寸"""
        image = Image.new("RGB", (100, 100), color="red")

        # PIL在某些情况下可能允许0尺寸,所以不强制要求抛出异常
        try:
            result = processor.resize_image(
                image, width=0, height=0, maintain_aspect_ratio=False
            )
            # 如果没有抛出异常,验证结果是否合理
            assert result.size[0] == 0 or result.size[1] == 0
        except (ValueError, Exception):
            # 也接受抛出异常的情况
            pass

    def test_resize_negative_dimensions(self, processor: ImageProcessor) -> None:
        """测试负数尺寸"""
        image = Image.new("RGB", (100, 100), color="blue")

        with pytest.raises((ValueError, Exception)):
            processor.resize_image(
                image, width=-10, height=-10, maintain_aspect_ratio=False
            )

    def test_crop_invalid_coordinates(self, processor: ImageProcessor) -> None:
        """测试无效裁剪坐标"""
        image = Image.new("RGB", (100, 100), color="green")

        # 测试超出边界的裁剪
        try:
            processor.crop_image(image, left=50, top=50, right=200, bottom=200)
        except Exception:
            pass  # 预期可能抛出异常

    def test_empty_image_cache(self, processor: ImageProcessor) -> None:
        """测试空缓存操作"""
        processor.clear_cache()  # 不应该抛出异常
        assert len(processor._image_cache) == 0

    def test_load_empty_bytesio(self, processor: ImageProcessor) -> None:
        """测试加载空BytesIO"""
        with pytest.raises((ValidationError, Exception)):
            processor.load_image(io.BytesIO(b""))

    def test_convert_unsupported_format(self, processor: ImageProcessor) -> None:
        """测试转换为不支持的格式"""
        image = Image.new("RGB", (100, 100), color="yellow")

        # ImageFormat只支持特定格式
        # 测试使用支持的格式
        buffer = processor.convert_format(image, ImageFormat.PNG)
        assert isinstance(buffer, io.BytesIO)
