"""Image transform operations."""

from __future__ import annotations

import tempfile
from pathlib import Path
from typing import Any

from PIL import Image

from cascade_fm.core.cache.cache_artifact import ArtifactCacheMixin
from cascade_fm.operations.base import Operation


class ResizeImage(ArtifactCacheMixin, Operation):
    """Resize image files."""

    @property
    def name(self) -> str:
        return "resize_image"

    @property
    def label(self) -> str:
        return "Resize Image"

    @property
    def description(self) -> str:
        return "Resize images to target width/height"

    @property
    def accepts(self) -> list[str]:
        return ["image/*"]

    @property
    def supports_incremental_cache(self) -> bool:
        return True

    def _execute_impl(self, files: list[Path], **params: Any) -> list[Path]:
        width = int(params["width"])
        height_value = params.get("height")
        target_height = int(height_value) if height_value is not None else None

        output_dir = Path(tempfile.mkdtemp(prefix="cascade_resize_"))
        outputs: list[Path] = []

        for file_path in files:
            if not file_path.is_file():
                continue

            with Image.open(file_path) as image:
                if target_height is None:
                    ratio = width / image.width
                    new_size = (width, max(1, int(image.height * ratio)))
                else:
                    new_size = (width, target_height)

                resized = image.resize(new_size)
                output_path = output_dir / f"{file_path.stem}_resized{file_path.suffix}"
                resized.save(output_path)
                outputs.append(output_path)

        return outputs

    def validate_params(self, **params: Any) -> tuple[bool, str | None]:
        width = params.get("width")
        height = params.get("height")

        if not isinstance(width, int) or width <= 0:
            return False, "width must be a positive integer"

        if height is not None and (not isinstance(height, int) or height <= 0):
            return False, "height must be a positive integer when provided"

        return True, None


class ConvertImage(ArtifactCacheMixin, Operation):
    """Convert image files to a target format."""

    @property
    def name(self) -> str:
        return "convert_image"

    @property
    def label(self) -> str:
        return "Convert Image"

    @property
    def description(self) -> str:
        return "Convert images to target format"

    @property
    def accepts(self) -> list[str]:
        return ["image/*"]

    @property
    def supports_incremental_cache(self) -> bool:
        return True

    def _execute_impl(self, files: list[Path], **params: Any) -> list[Path]:
        target_format = str(params["format"]).upper()
        extension = self._format_to_extension(target_format)

        output_dir = Path(tempfile.mkdtemp(prefix="cascade_convert_"))
        outputs: list[Path] = []

        for file_path in files:
            if not file_path.is_file():
                continue

            output_path = output_dir / f"{file_path.stem}_converted.{extension}"
            with Image.open(file_path) as image:
                save_image = image
                if target_format in {"JPEG", "JPG"} and image.mode in {"RGBA", "LA"}:
                    save_image = image.convert("RGB")
                save_image.save(output_path, format=target_format)
                outputs.append(output_path)

        return outputs

    def validate_params(self, **params: Any) -> tuple[bool, str | None]:
        target_format = params.get("format")
        if not isinstance(target_format, str) or not target_format.strip():
            return False, "format must be a non-empty string"

        normalized = target_format.upper()
        if normalized not in {"PNG", "JPEG", "JPG", "WEBP"}:
            return False, "format must be one of: PNG, JPEG, JPG, WEBP"

        return True, None

    def _format_to_extension(self, image_format: str) -> str:
        if image_format in {"JPEG", "JPG"}:
            return "jpg"
        return image_format.lower()


class RotateImage(ArtifactCacheMixin, Operation):
    """Rotate image files by fixed angle."""

    @property
    def name(self) -> str:
        return "rotate_image"

    @property
    def label(self) -> str:
        return "Rotate Image"

    @property
    def description(self) -> str:
        return "Rotate images by 90/180/270 degrees"

    @property
    def accepts(self) -> list[str]:
        return ["image/*"]

    @property
    def supports_incremental_cache(self) -> bool:
        return True

    def _execute_impl(self, files: list[Path], **params: Any) -> list[Path]:
        angle = int(params["angle"])

        output_dir = Path(tempfile.mkdtemp(prefix="cascade_rotate_"))
        outputs: list[Path] = []

        for file_path in files:
            if not file_path.is_file():
                continue

            with Image.open(file_path) as image:
                rotated = image.rotate(-angle, expand=True)
                output_path = output_dir / f"{file_path.stem}_rotated{file_path.suffix}"
                rotated.save(output_path)
                outputs.append(output_path)

        return outputs

    def validate_params(self, **params: Any) -> tuple[bool, str | None]:
        angle = params.get("angle")
        if not isinstance(angle, int):
            return False, "angle must be an integer"
        if angle not in {90, 180, 270}:
            return False, "angle must be one of: 90, 180, 270"
        return True, None


class StripExif(ArtifactCacheMixin, Operation):
    """Strip EXIF metadata from image files."""

    @property
    def name(self) -> str:
        return "strip_exif"

    @property
    def label(self) -> str:
        return "Strip EXIF"

    @property
    def description(self) -> str:
        return "Remove EXIF metadata while preserving image pixels"

    @property
    def accepts(self) -> list[str]:
        return ["image/*"]

    @property
    def supports_incremental_cache(self) -> bool:
        return True

    def _can_execute_without_params(self) -> bool:
        return True

    def _execute_impl(self, files: list[Path], **params: Any) -> list[Path]:
        output_dir = Path(tempfile.mkdtemp(prefix="cascade_strip_exif_"))
        outputs: list[Path] = []

        for file_path in files:
            if not file_path.is_file():
                continue

            with Image.open(file_path) as image:
                output_path = output_dir / f"{file_path.stem}_clean{file_path.suffix}"
                image.save(output_path, exif=b"")
                outputs.append(output_path)

        return outputs


class CompressImage(ArtifactCacheMixin, Operation):
    """Compress image files using format-specific quality settings."""

    @property
    def name(self) -> str:
        return "compress_image"

    @property
    def label(self) -> str:
        return "Compress Image"

    @property
    def description(self) -> str:
        return "Compress images with adjustable quality"

    @property
    def accepts(self) -> list[str]:
        return ["image/*"]

    @property
    def supports_incremental_cache(self) -> bool:
        return True

    def _execute_impl(self, files: list[Path], **params: Any) -> list[Path]:
        quality = int(params["quality"])

        output_dir = Path(tempfile.mkdtemp(prefix="cascade_compress_"))
        outputs: list[Path] = []

        for file_path in files:
            if not file_path.is_file():
                continue

            with Image.open(file_path) as image:
                output_path = output_dir / f"{file_path.stem}_compressed{file_path.suffix}"
                save_kwargs: dict[str, Any] = {}

                file_format = (image.format or "").upper()
                if file_format in {"JPEG", "JPG", "WEBP"}:
                    if file_format in {"JPEG", "JPG"} and image.mode in {"RGBA", "LA"}:
                        image = image.convert("RGB")
                    save_kwargs["quality"] = quality
                elif file_format == "PNG":
                    compression_level = min(9, max(0, round((100 - quality) / 11.11)))
                    save_kwargs["compress_level"] = compression_level

                image.save(output_path, **save_kwargs)
                outputs.append(output_path)

        return outputs

    def validate_params(self, **params: Any) -> tuple[bool, str | None]:
        quality = params.get("quality")
        if not isinstance(quality, int):
            return False, "quality must be an integer"
        if quality < 1 or quality > 100:
            return False, "quality must be between 1 and 100"
        return True, None
