"""
Excel图像集成测试

测试ExcelImage类的所有功能，包括插入、调整大小、批量处理和边界条件。
"""

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

import pytest
from PIL import Image

from src.symphra_excel.core.workbook import Workbook
from src.symphra_excel.core.worksheet import Worksheet
from src.symphra_excel.images.excel_image import ExcelImage
from src.symphra_excel.images.image_processor import CellImageOptions
from src.symphra_excel.utils.exceptions import ValidationError


class TestExcelImageBasic:
    """测试ExcelImage基本功能"""

    @pytest.fixture
    def workbook(self) -> Workbook:
        """创建测试工作簿"""
        return Workbook()

    @pytest.fixture
    def worksheet(self, workbook: Workbook) -> Worksheet:
        """创建测试工作表"""
        return workbook.active

    @pytest.fixture
    def excel_image(self) -> ExcelImage:
        """创建ExcelImage实例"""
        return ExcelImage()

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

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

    @pytest.fixture
    def test_image_bytesio(self, test_image_bytes: bytes) -> io.BytesIO:
        """创建测试图像的BytesIO"""
        return io.BytesIO(test_image_bytes)

    def test_excel_image_init(self) -> None:
        """测试ExcelImage初始化"""
        excel_image = ExcelImage()
        assert excel_image.image_processor is not None

    def test_excel_image_init_with_processor(self) -> None:
        """测试使用自定义处理器初始化"""
        from src.symphra_excel.images.image_processor import ImageProcessor

        processor = ImageProcessor()
        excel_image = ExcelImage(image_processor=processor)
        assert excel_image.image_processor is processor

    @patch("src.symphra_excel.images.excel_image.validate_cell_reference")
    @patch("src.symphra_excel.images.excel_image.OpenPyXLImage")
    def test_insert_image_validates_cell(
        self,
        mock_openpyxl_image: Mock,
        mock_validate: Mock,
        excel_image: ExcelImage,
        worksheet: Worksheet,
    ) -> None:
        """测试插入图像时验证单元格引用"""
        # 模拟PIL图像
        mock_pil_image = Mock()
        mock_pil_image.size = (100, 100)

        with patch.object(
            excel_image.image_processor, "load_image", return_value=mock_pil_image
        ):
            with patch.object(
                excel_image.image_processor,
                "optimize_for_excel",
                return_value=io.BytesIO(b"test"),
            ):
                with patch.object(worksheet._worksheet, "add_image"):
                    excel_image.insert_image(worksheet, io.BytesIO(b"fake"), "A1")
                    mock_validate.assert_called_once_with("A1")


class TestInsertImage:
    """测试图像插入功能"""

    @pytest.fixture
    def workbook(self) -> Workbook:
        """创建测试工作簿"""
        return Workbook()

    @pytest.fixture
    def worksheet(self, workbook: Workbook) -> Worksheet:
        """创建测试工作表"""
        return workbook.active

    @pytest.fixture
    def excel_image(self) -> ExcelImage:
        """创建ExcelImage实例"""
        return ExcelImage()

    @pytest.fixture
    def test_image_bytesio(self) -> io.BytesIO:
        """创建测试图像"""
        image = Image.new("RGB", (100, 100), color="blue")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        buffer.seek(0)
        return buffer

    def test_insert_image_from_bytesio(
        self,
        excel_image: ExcelImage,
        worksheet: Worksheet,
        test_image_bytesio: io.BytesIO,
    ) -> None:
        """测试从BytesIO插入图像"""
        initial_count = len(worksheet._worksheet._images)
        excel_image.insert_image(worksheet, test_image_bytesio, "B2")
        assert len(worksheet._worksheet._images) == initial_count + 1

    def test_insert_image_with_width(
        self,
        excel_image: ExcelImage,
        worksheet: Worksheet,
        test_image_bytesio: io.BytesIO,
    ) -> None:
        """测试指定宽度插入图像"""
        with patch.object(worksheet._worksheet, "add_image"):
            with patch.object(
                excel_image.image_processor, "resize_image"
            ) as mock_resize:
                mock_resize.return_value = Image.new("RGB", (50, 100))
                excel_image.insert_image(worksheet, test_image_bytesio, "C3", width=50)
                mock_resize.assert_called_once()

    def test_insert_image_with_height(
        self,
        excel_image: ExcelImage,
        worksheet: Worksheet,
        test_image_bytesio: io.BytesIO,
    ) -> None:
        """测试指定高度插入图像"""
        with patch.object(worksheet._worksheet, "add_image"):
            with patch.object(
                excel_image.image_processor, "resize_image"
            ) as mock_resize:
                mock_resize.return_value = Image.new("RGB", (100, 50))
                excel_image.insert_image(worksheet, test_image_bytesio, "D4", height=50)
                mock_resize.assert_called_once()

    def test_insert_image_with_width_and_height(
        self,
        excel_image: ExcelImage,
        worksheet: Worksheet,
        test_image_bytesio: io.BytesIO,
    ) -> None:
        """测试指定宽高插入图像"""
        with patch.object(worksheet._worksheet, "add_image"):
            with patch.object(
                excel_image.image_processor, "resize_image"
            ) as mock_resize:
                mock_resize.return_value = Image.new("RGB", (80, 60))
                excel_image.insert_image(
                    worksheet, test_image_bytesio, "E5", width=80, height=60
                )
                mock_resize.assert_called_once()

    def test_insert_image_with_options(
        self,
        excel_image: ExcelImage,
        worksheet: Worksheet,
        test_image_bytesio: io.BytesIO,
    ) -> None:
        """测试带选项插入图像"""
        options = CellImageOptions(
            maintain_aspect_ratio=True, anchor_to_cell=True, margin=5
        )
        with patch.object(worksheet._worksheet, "add_image"):
            excel_image.insert_image(
                worksheet, test_image_bytesio, "F6", options=options
            )
            # 验证选项被使用（通过内部方法调用）

    def test_insert_image_invalid_source(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试插入无效图像源"""
        with pytest.raises(ValidationError):
            excel_image.insert_image(worksheet, "nonexistent.png", "A1")

    def test_insert_image_from_bytes(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试从bytes插入图像"""
        image = Image.new("RGB", (50, 50), color="green")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        image_bytes = buffer.getvalue()

        with patch.object(worksheet._worksheet, "add_image"):
            excel_image.insert_image(worksheet, image_bytes, "G7")


class TestInsertImageWithSize:
    """测试按单元格大小插入图像"""

    @pytest.fixture
    def workbook(self) -> Workbook:
        """创建测试工作簿"""
        return Workbook()

    @pytest.fixture
    def worksheet(self, workbook: Workbook) -> Worksheet:
        """创建测试工作表"""
        return workbook.active

    @pytest.fixture
    def excel_image(self) -> ExcelImage:
        """创建ExcelImage实例"""
        return ExcelImage()

    @pytest.fixture
    def test_image_bytesio(self) -> io.BytesIO:
        """创建测试图像"""
        image = Image.new("RGB", (200, 200), color="yellow")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        buffer.seek(0)
        return buffer

    def test_insert_image_with_cell_size(
        self,
        excel_image: ExcelImage,
        worksheet: Worksheet,
        test_image_bytesio: io.BytesIO,
    ) -> None:
        """测试按单元格大小插入"""
        with patch.object(worksheet._worksheet, "add_image"):
            excel_image.insert_image_with_size(
                worksheet, test_image_bytesio, "A1", cell_width=100, cell_height=100
            )

    def test_insert_image_auto_detect_cell_size(
        self,
        excel_image: ExcelImage,
        worksheet: Worksheet,
        test_image_bytesio: io.BytesIO,
    ) -> None:
        """测试自动检测单元格大小"""
        with patch.object(worksheet._worksheet, "add_image"):
            with patch.object(
                excel_image, "_get_cell_dimensions", return_value=(100, 100)
            ):
                excel_image.insert_image_with_size(worksheet, test_image_bytesio, "B2")

    def test_insert_image_maintain_aspect_ratio(
        self,
        excel_image: ExcelImage,
        worksheet: Worksheet,
        test_image_bytesio: io.BytesIO,
    ) -> None:
        """测试保持宽高比插入"""
        options = CellImageOptions(maintain_aspect_ratio=True, margin=2)
        with patch.object(worksheet._worksheet, "add_image"):
            excel_image.insert_image_with_size(
                worksheet,
                test_image_bytesio,
                "C3",
                cell_width=120,
                cell_height=80,
                options=options,
            )

    def test_insert_image_fill_cell(
        self,
        excel_image: ExcelImage,
        worksheet: Worksheet,
        test_image_bytesio: io.BytesIO,
    ) -> None:
        """测试填充整个单元格"""
        options = CellImageOptions(maintain_aspect_ratio=False, margin=0)
        with patch.object(worksheet._worksheet, "add_image"):
            excel_image.insert_image_with_size(
                worksheet,
                test_image_bytesio,
                "D4",
                cell_width=150,
                cell_height=100,
                options=options,
            )

    def test_insert_image_with_margin(
        self,
        excel_image: ExcelImage,
        worksheet: Worksheet,
        test_image_bytesio: io.BytesIO,
    ) -> None:
        """测试带边距插入"""
        options = CellImageOptions(margin=10)
        with patch.object(worksheet._worksheet, "add_image"):
            excel_image.insert_image_with_size(
                worksheet,
                test_image_bytesio,
                "E5",
                cell_width=100,
                cell_height=100,
                options=options,
            )


class TestBatchOperations:
    """测试批量操作"""

    @pytest.fixture
    def workbook(self) -> Workbook:
        """创建测试工作簿"""
        return Workbook()

    @pytest.fixture
    def worksheet(self, workbook: Workbook) -> Worksheet:
        """创建测试工作表"""
        return workbook.active

    @pytest.fixture
    def excel_image(self) -> ExcelImage:
        """创建ExcelImage实例"""
        return ExcelImage()

    def test_insert_images_batch_empty_list(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试批量插入空列表"""
        excel_image.insert_images_batch(worksheet, [])
        # 应该不抛出异常

    def test_insert_images_batch_single(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试批量插入单个图像"""
        image = Image.new("RGB", (50, 50), color="purple")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        buffer.seek(0)

        images_data = [{"image": buffer, "cell": "A1"}]

        with patch.object(worksheet._worksheet, "add_image"):
            excel_image.insert_images_batch(worksheet, images_data)

    def test_insert_images_batch_multiple(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试批量插入多个图像"""
        images_data = []
        for i in range(5):
            image = Image.new("RGB", (30, 30), color="orange")
            buffer = io.BytesIO()
            image.save(buffer, format="PNG")
            buffer.seek(0)
            images_data.append({"image": buffer, "cell": f"A{i + 1}"})

        with patch.object(worksheet._worksheet, "add_image"):
            excel_image.insert_images_batch(worksheet, images_data)

    def test_insert_images_batch_with_sizes(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试批量插入带尺寸的图像"""
        image = Image.new("RGB", (100, 100), color="cyan")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        buffer.seek(0)

        images_data = [{"image": buffer, "cell": "B2", "width": 50, "height": 50}]

        with patch.object(worksheet._worksheet, "add_image"):
            excel_image.insert_images_batch(worksheet, images_data)

    def test_insert_images_batch_partial_failure(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试批量插入部分失败"""
        image = Image.new("RGB", (40, 40), color="magenta")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        buffer.seek(0)

        images_data = [
            {"image": buffer, "cell": "A1"},
            {"image": "invalid_path.png", "cell": "B2"},  # 无效图像
            {"image": buffer, "cell": "C3"},
        ]

        with patch.object(worksheet._worksheet, "add_image"):
            # 应该记录警告但继续处理
            excel_image.insert_images_batch(worksheet, images_data)

    def test_batch_optimize_images_empty(self, excel_image: ExcelImage) -> None:
        """测试批量优化空列表"""
        result = excel_image.batch_optimize_images([])
        assert result == []

    def test_batch_optimize_images_single(self, excel_image: ExcelImage) -> None:
        """测试批量优化单个图像"""
        image = Image.new("RGB", (500, 500), color="brown")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        buffer.seek(0)

        result = excel_image.batch_optimize_images([buffer])
        assert len(result) == 1
        assert result[0] is not None

    def test_batch_optimize_images_multiple(self, excel_image: ExcelImage) -> None:
        """测试批量优化多个图像"""
        images = []
        for _ in range(3):
            image = Image.new("RGB", (800, 600), color="gray")
            buffer = io.BytesIO()
            image.save(buffer, format="PNG")
            buffer.seek(0)
            images.append(buffer)

        result = excel_image.batch_optimize_images(
            images, max_width=400, max_height=300
        )
        assert len(result) == 3
        assert all(img is not None for img in result)

    def test_batch_optimize_images_with_failures(self, excel_image: ExcelImage) -> None:
        """测试批量优化包含失败项"""
        image = Image.new("RGB", (100, 100), color="pink")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        buffer.seek(0)

        sources = [buffer, "invalid_path.png", buffer]

        result = excel_image.batch_optimize_images(sources)
        assert len(result) == 3
        assert result[0] is not None
        assert result[1] is None  # 失败项
        assert result[2] is not None


class TestImageInfo:
    """测试图像信息获取"""

    @pytest.fixture
    def excel_image(self) -> ExcelImage:
        """创建ExcelImage实例"""
        return ExcelImage()

    def test_get_image_info_valid(self, excel_image: ExcelImage) -> None:
        """测试获取有效图像信息"""
        image = Image.new("RGB", (200, 150), color="white")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        buffer.seek(0)

        info = excel_image.get_image_info(buffer)

        assert "width" in info
        assert "height" in info
        assert "format" in info
        assert "size_bytes" in info
        assert "size_mb" in info
        assert "is_valid_for_excel" in info
        assert info["width"] == 200
        assert info["height"] == 150

    def test_get_image_info_invalid(self, excel_image: ExcelImage) -> None:
        """测试获取无效图像信息"""
        info = excel_image.get_image_info("nonexistent.png")

        assert "error" in info
        assert "is_valid" in info
        assert info["is_valid"] is False


class TestUtilityMethods:
    """测试工具方法"""

    @pytest.fixture
    def workbook(self) -> Workbook:
        """创建测试工作簿"""
        return Workbook()

    @pytest.fixture
    def worksheet(self, workbook: Workbook) -> Worksheet:
        """创建测试工作表"""
        return workbook.active

    @pytest.fixture
    def excel_image(self) -> ExcelImage:
        """创建ExcelImage实例"""
        return ExcelImage()

    def test_parse_cell_reference_simple(self, excel_image: ExcelImage) -> None:
        """测试解析简单单元格引用"""
        col, row = excel_image._parse_cell_reference("A1")
        assert col == 1
        assert row == 1

    def test_parse_cell_reference_double_letter(self, excel_image: ExcelImage) -> None:
        """测试解析双字母列引用"""
        col, row = excel_image._parse_cell_reference("AA10")
        assert col == 27
        assert row == 10

    def test_parse_cell_reference_large_row(self, excel_image: ExcelImage) -> None:
        """测试解析大行号"""
        col, row = excel_image._parse_cell_reference("Z999")
        assert col == 26
        assert row == 999

    def test_get_cell_dimensions_default(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试获取默认单元格尺寸"""
        width, height = excel_image._get_cell_dimensions(worksheet, "A1")
        assert width > 0
        assert height > 0

    def test_get_cell_dimensions_custom(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试获取自定义单元格尺寸"""
        # 设置自定义列宽和行高
        worksheet._worksheet.column_dimensions["B"].width = 20
        worksheet._worksheet.row_dimensions[2].height = 30

        width, height = excel_image._get_cell_dimensions(worksheet, "B2")
        assert width == int(20 * 7.5)
        assert height == int(30 * 1.33)

    def test_create_image_from_cell_range(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试从单元格范围创建图像"""
        buffer = excel_image.create_image_from_cell_range(worksheet, "A1:D5")
        assert isinstance(buffer, io.BytesIO)
        assert buffer.tell() == 0  # Buffer应该已重置

    def test_create_image_from_cell_range_with_output(
        self, excel_image: ExcelImage, worksheet: Worksheet, tmp_path: Path
    ) -> None:
        """测试创建图像并保存到文件"""
        output_path = tmp_path / "range_image.png"
        buffer = excel_image.create_image_from_cell_range(
            worksheet, "B2:E10", output_path=str(output_path)
        )
        assert output_path.exists()
        assert isinstance(buffer, io.BytesIO)


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

    @pytest.fixture
    def workbook(self) -> Workbook:
        """创建测试工作簿"""
        return Workbook()

    @pytest.fixture
    def worksheet(self, workbook: Workbook) -> Worksheet:
        """创建测试工作表"""
        return workbook.active

    @pytest.fixture
    def excel_image(self) -> ExcelImage:
        """创建ExcelImage实例"""
        return ExcelImage()

    def test_insert_image_empty_bytesio(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试插入空的BytesIO"""
        with pytest.raises(ValidationError):
            excel_image.insert_image(worksheet, io.BytesIO(b""), "A1")

    def test_insert_image_none_source(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试插入None源"""
        with pytest.raises((ValidationError, TypeError)):
            excel_image.insert_image(worksheet, None, "A1")  # type: ignore

    def test_insert_image_zero_width(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试插入0宽度图像"""
        image = Image.new("RGB", (100, 100), color="red")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        buffer.seek(0)

        with patch.object(worksheet._worksheet, "add_image"):
            # 0宽度应该被处理器处理或抛出异常
            try:
                excel_image.insert_image(worksheet, buffer, "A1", width=0)
            except (ValidationError, ValueError):
                pass  # 预期行为

    def test_insert_image_negative_dimensions(
        self, excel_image: ExcelImage, worksheet: Worksheet
    ) -> None:
        """测试插入负数尺寸"""
        image = Image.new("RGB", (100, 100), color="blue")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG")
        buffer.seek(0)

        with pytest.raises((ValidationError, ValueError)):
            excel_image.insert_image(worksheet, buffer, "A1", width=-10, height=-20)

    def test_parse_cell_reference_invalid_format(self, excel_image: ExcelImage) -> None:
        """测试解析无效格式的单元格引用"""
        with pytest.raises((ValueError, IndexError)):
            excel_image._parse_cell_reference("INVALID")

    def test_parse_cell_reference_no_number(self, excel_image: ExcelImage) -> None:
        """测试解析没有数字的引用"""
        with pytest.raises(ValueError):
            excel_image._parse_cell_reference("ABC")

    def test_parse_cell_reference_no_letter(self, excel_image: ExcelImage) -> None:
        """测试解析没有字母的引用"""
        with pytest.raises((ValueError, IndexError)):
            excel_image._parse_cell_reference("123")
