from io import BytesIO

from PIL import Image, ImageOps, UnidentifiedImageError

from zbt.core.errors import AppError
from zbt.domains.service_request.preprocessing import ProcessedMaterial


class PillowMaterialPreprocessor:
    """解码、校验方向并压缩为统一 JPEG，模型不会直接接收原始文件。"""

    def __init__(
        self,
        *,
        max_edge: int = 2048,
        max_pixels: int = 25_000_000,
    ) -> None:
        self._max_edge = max_edge
        self._max_pixels = max_pixels

    def process(self, *, content: bytes, media_type: str) -> ProcessedMaterial:
        del media_type
        try:
            with Image.open(BytesIO(content)) as source:
                source.verify()
            with Image.open(BytesIO(content)) as source:
                image = ImageOps.exif_transpose(source)
                width, height = image.size
                if width <= 0 or height <= 0 or width * height > self._max_pixels:
                    raise AppError(
                        "MATERIAL_IMAGE_DIMENSIONS_INVALID",
                        "图片尺寸无效或像素数量过大",
                        422,
                    )
                image.thumbnail((self._max_edge, self._max_edge))
                normalized = image.convert("RGB")
                target = BytesIO()
                normalized.save(
                    target,
                    format="JPEG",
                    quality=90,
                    optimize=True,
                )
                normalized_width, normalized_height = normalized.size
        except AppError:
            raise
        except (UnidentifiedImageError, OSError, SyntaxError, ValueError) as error:
            raise AppError(
                "MATERIAL_IMAGE_INVALID",
                "上传内容不是有效的JPG、PNG或WebP图片",
                422,
            ) from error
        return ProcessedMaterial(
            content=target.getvalue(),
            media_type="image/jpeg",
            width=normalized_width,
            height=normalized_height,
        )
