"""
Excel集成的图像处理工具
"""

import base64
import io
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Any

from PIL import Image as PILImage
from PIL import ImageDraw, ImageFont

from ..utils.exceptions import ValidationError, handle_file_errors
from ..utils.memory_manager import MemoryManager


class ImageFormat(Enum):
    """支持的图像格式"""

    PNG = "PNG"
    JPEG = "JPEG"
    BMP = "BMP"
    GIF = "GIF"
    WEBP = "WEBP"


@dataclass
class ImageProperties:
    """Excel集成的图像属性"""

    width: int
    height: int
    format: ImageFormat
    size_bytes: int
    dpi: tuple[int, int | None] | None = None
    mode: str = "RGB"


@dataclass
class CellImageOptions:
    """在单元格中嵌入图像的配置选项"""

    anchor_to_cell: bool = True
    move_with_cells: bool = True
    resize_with_cells: bool = True
    maintain_aspect_ratio: bool = True
    max_width: int | None = None
    max_height: int | None = None
    position: str = "center"  # 位置选项：center, top-left, top-right, bottom-left, bottom-right
    margin: int = 2
    # 锚点类型：oneCell（默认）或 twoCell
    anchor_type: str = "oneCell"  # "oneCell" 或 "twoCell"


class ImageProcessor:
    """
    Excel集成的图像处理工具
    """

    def __init__(self, memory_manager: MemoryManager | None = None) -> None:
        """
        初始化图像处理器

        参数:
            memory_manager: 内存管理器实例
        """
        self.memory_manager = memory_manager
        self._image_cache: dict[str, PILImage.Image] = {}

    @handle_file_errors(error_message="加载图像失败")
    def load_image(
        self, image_source: str | Path | bytes | io.BytesIO
    ) -> PILImage.Image:
        """
        从各种源加载图像

        参数:
            image_source: 图像源（文件路径、字节或BytesIO）

        返回:
            PIL图像对象

        异常:
            ValidationError: 如果无法加载图像
        """
        if isinstance(image_source, (str, Path)):
            image = PILImage.open(image_source)
        elif isinstance(image_source, bytes):
            image = PILImage.open(io.BytesIO(image_source))
        elif isinstance(image_source, io.BytesIO):
            image = PILImage.open(image_source)
        else:
            raise ValidationError(f"不支持的图像源类型: {type(image_source)}")

        # 跟踪内存使用
        if self.memory_manager:
            size = image.size[0] * image.size[1] * len(image.mode)
            self.memory_manager.allocate_memory(size)

        return image

    def get_image_properties(self, image: PILImage.Image) -> ImageProperties:
        """
        获取图像属性

        参数:
            image: PIL图像对象

        返回:
            图像属性
        """
        format_type = ImageFormat(image.format) if image.format else ImageFormat.PNG

        # 计算字节大小
        img_buffer = io.BytesIO()
        image.save(img_buffer, format=image.format or "PNG")
        size_bytes = len(img_buffer.getvalue())

        # 获取DPI（如果可用）
        dpi_raw = image.info.get("dpi") if "dpi" in image.info else None
        dpi: tuple[int, int | None] | None = (
            dpi_raw if isinstance(dpi_raw, tuple) else None
        )

        return ImageProperties(
            width=image.width,
            height=image.height,
            format=format_type,
            size_bytes=size_bytes,
            dpi=dpi,
            mode=image.mode,
        )

    def resize_image(
        self,
        image: PILImage.Image,
        width: int | None = None,
        height: int | None = None,
        maintain_aspect_ratio: bool = True,
        resample: int = PILImage.Resampling.LANCZOS,
    ) -> PILImage.Image:
        """
        调整图像大小（带各种选项）

        参数:
            image: PIL图像对象
            width: 目标宽度
            height: 目标高度
            maintain_aspect_ratio: 是否保持宽高比
            resample: 重采样滤镜

        返回:
            调整大小后的图像
        """
        if width is None and height is None:
            return image

        original_width, original_height = image.size

        if maintain_aspect_ratio:
            if width is None and height is not None:
                width = int(original_width * (height / original_height))
            elif height is None and width is not None:
                height = int(original_height * (width / original_width))
            elif width is not None and height is not None:
                # 计算宽高比
                width_ratio = width / original_width
                height_ratio = height / original_height

                # 使用较小的比例来保持宽高比
                if width_ratio < height_ratio:
                    height = int(original_height * width_ratio)
                else:
                    width = int(original_width * height_ratio)
            else:
                # 宽度和高度都为None，使用原始尺寸
                width = original_width
                height = original_height
        else:
            width = width or original_width
            height = height or original_height

        resized = image.resize((width, height), resample)

        # 更新内存跟踪
        if self.memory_manager:
            old_size = original_width * original_height * len(image.mode)
            new_size = width * height * len(image.mode)
            size_diff = new_size - old_size
            if size_diff > 0:
                self.memory_manager.allocate_memory(size_diff)
            elif size_diff < 0:
                self.memory_manager.deallocate_memory(-size_diff)

        return resized

    def crop_image(
        self, image: PILImage.Image, left: int, top: int, right: int, bottom: int
    ) -> PILImage.Image:
        """
        裁剪图像到指定区域

        参数:
            image: PIL图像对象
            left: 左坐标
            top: 顶坐标
            right: 右坐标
            bottom: 底坐标

        返回:
            裁剪后的图像
        """
        cropped = image.crop((left, top, right, bottom))

        # 更新内存跟踪
        if self.memory_manager:
            old_size = image.width * image.height * len(image.mode)
            new_size = cropped.width * cropped.height * len(cropped.mode)
            size_diff = new_size - old_size
            if size_diff < 0:
                self.memory_manager.deallocate_memory(-size_diff)

        return cropped

    def convert_format(
        self, image: PILImage.Image, target_format: ImageFormat, quality: int = 85
    ) -> io.BytesIO:
        """
        将图像转换为不同格式

        参数:
            image: PIL图像对象
            target_format: 目标格式
            quality: JPEG质量（1-100）

        返回:
            包含转换后图像的BytesIO缓冲区
        """
        output_buffer = io.BytesIO()

        # 如有必要则转换模式
        if target_format == ImageFormat.JPEG and image.mode in ("RGBA", "LA", "P"):
            # 为JPEG转换为RGB
            rgb_image = PILImage.new("RGB", image.size, (255, 255, 255))
            if image.mode == "P":
                image = image.convert("RGBA")
            rgb_image.paste(
                image, mask=image.split()[-1] if image.mode == "RGBA" else None
            )
            image = rgb_image

        # 使用适当的设置保存
        save_kwargs = {}
        if target_format == ImageFormat.JPEG:
            save_kwargs["quality"] = quality
            save_kwargs["optimize"] = True
        elif target_format == ImageFormat.PNG:
            save_kwargs["optimize"] = True

        image.save(output_buffer, format=target_format.value, **save_kwargs)
        output_buffer.seek(0)

        return output_buffer

    def optimize_for_excel(
        self,
        image: PILImage.Image,
        max_width: int = 800,
        max_height: int = 600,
        quality: int = 80,
    ) -> io.BytesIO:
        """
        优化图像以适配Excel插入

        参数:
            image: PIL图像对象
            max_width: 最大宽度
            max_height: 最大高度
            quality: 用于优化的JPEG质量

        返回:
            优化后的图像缓冲区
        """
        # 如果太大则调整大小
        props = self.get_image_properties(image)

        if props.width > max_width or props.height > max_height:
            image = self.resize_image(
                image, width=max_width, height=max_height, maintain_aspect_ratio=True
            )

        # 转换为PNG以确保Excel兼容性
        return self.convert_format(image, ImageFormat.PNG)

    def add_watermark(
        self,
        image: PILImage.Image,
        watermark_text: str,
        opacity: float = 0.3,
        position: str = "bottom-right",
    ) -> PILImage.Image:
        """
        为图像添加文本水印

        参数:
            image: PIL图像对象
            watermark_text: 水印文本
            opacity: 水印不透明度（0.0-1.0）
            position: 水印位置

        返回:
            带水印的图像
        """
        # 创建水印层
        watermark = PILImage.new("RGBA", image.size, (0, 0, 0, 0))
        draw = ImageDraw.Draw(watermark)

        # 根据图像大小计算字体大小
        font_size = max(12, min(image.width, image.height) // 20)

        font: Any
        try:
            font = ImageFont.truetype("arial.ttf", font_size)
        except OSError:
            font = ImageFont.load_default()

        # 获取文本大小
        bbox = draw.textbbox((0, 0), watermark_text, font=font)
        text_width = bbox[2] - bbox[0]
        text_height = bbox[3] - bbox[1]

        # 计算位置
        margin = 20
        if position == "bottom-right":
            x = image.width - text_width - margin
            y = image.height - text_height - margin
        elif position == "bottom-left":
            x = margin
            y = image.height - text_height - margin
        elif position == "top-right":
            x = image.width - text_width - margin
            y = margin
        elif position == "top-left":
            x = margin
            y = margin
        elif position == "center":
            x = (image.width - text_width) // 2
            y = (image.height - text_height) // 2
        else:
            x = image.width - text_width - margin
            y = image.height - text_height - margin

        # 绘制水印
        draw.text(
            (x, y), watermark_text, font=font, fill=(255, 255, 255, int(255 * opacity))
        )

        # 将水印合成到原始图像上
        watermarked = PILImage.alpha_composite(image.convert("RGBA"), watermark)

        return watermarked.convert(image.mode)

    def create_thumbnail(
        self,
        image: PILImage.Image,
        size: tuple[int, int] = (150, 150),
        crop: str = "center",
    ) -> PILImage.Image:
        """
        创建图像缩略图

        参数:
            image: PIL图像对象
            size: 缩略图大小
            crop: 裁剪方法（'center', 'top', 'bottom', 'left', 'right'）

        返回:
            缩略图图像
        """
        thumbnail = image.copy()

        if crop == "center":
            thumbnail = self._crop_center(thumbnail, size)
        else:
            thumbnail.thumbnail(size, PILImage.Resampling.LANCZOS)

        return thumbnail

    def _crop_center(
        self, image: PILImage.Image, size: tuple[int, int]
    ) -> PILImage.Image:
        """
        裁剪图像到中心

        参数:
            image: PIL图像对象
            size: 目标大小

        返回:
            裁剪后的图像
        """
        original_width, original_height = image.size
        target_width, target_height = size

        # 计算裁剪坐标
        left = (original_width - target_width) // 2
        top = (original_height - target_height) // 2
        right = left + target_width
        bottom = top + target_height

        return image.crop((left, top, right, bottom))

    def image_to_base64(
        self, image: PILImage.Image, format_type: ImageFormat = ImageFormat.PNG
    ) -> str:
        """
        将图像转换为base64字符串

        参数:
            image: PIL图像对象
            format_type: 图像格式

        返回:
            base64编码的图像
        """
        buffer = io.BytesIO()
        image.save(buffer, format=format_type.value)
        buffer.seek(0)

        return base64.b64encode(buffer.getvalue()).decode("utf-8")

    def base64_to_image(self, base64_string: str) -> PILImage.Image:
        """
        将base64字符串转换为图像

        参数:
            base64_string: base64编码的图像

        返回:
            PIL图像对象
        """
        image_data = base64.b64decode(base64_string)
        return PILImage.open(io.BytesIO(image_data))

    def cache_image(self, image_id: str, image: PILImage.Image) -> None:
        """
        缓存图像以供重用

        参数:
            image_id: 图像标识符
            image: PIL图像对象
        """
        self._image_cache[image_id] = image.copy()

    def get_cached_image(self, image_id: str) -> PILImage.Image | None:
        """
        获取缓存的图像

        参数:
            image_id: 图像标识符

        返回:
            缓存的图像或None
        """
        cached = self._image_cache.get(image_id)
        return cached.copy() if cached else None

    def clear_cache(self) -> None:
        """
        清除图像缓存
        """
        self._image_cache.clear()

        if self.memory_manager:
            self.memory_manager.optimize_memory()

    def validate_image_for_excel(
        self, image: PILImage.Image, max_size_mb: float = 10.0
    ) -> bool:
        """
        验证图像是否适合Excel插入

        参数:
            image: PIL图像对象
            max_size_mb: 最大大小（MB）

        返回:
            如果有效返回True，否则返回False
        """
        props = self.get_image_properties(image)

        # 检查大小
        max_size_bytes = max_size_mb * 1024 * 1024
        if props.size_bytes > max_size_bytes:
            return False

        # 检查尺寸
        if props.width > 16384 or props.height > 16384:  # Excel限制
            return False

        # 检查格式
        if props.format not in (
            ImageFormat.PNG,
            ImageFormat.JPEG,
            ImageFormat.BMP,
            ImageFormat.GIF,
        ):
            return False

        return True
