"""Unified image transformation operation."""

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 ImageTransform(ArtifactCacheMixin, Operation):
    """Apply a selected image transformation action to input images."""

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

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

    @property
    def description(self) -> str:
        return "Resize, convert, rotate, compress, or strip EXIF in one operation"

    @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]:
        action = str(params["action"]).lower()
        output_dir = Path(tempfile.mkdtemp(prefix=f"cascade_img_{action}_"))
        outputs: list[Path] = []
        source_files = [file_path for file_path in files if file_path.is_file()]
        total = len(source_files)

        for index, file_path in enumerate(source_files, start=1):
            self.report_progress(index - 1, total, f"Processing {file_path.name}")

            with Image.open(file_path) as image:
                output_path = self._output_path(output_dir, file_path, action, params)
                self._apply_action(image, output_path, action, params)

                outputs.append(output_path)
                self.report_progress(index, total, f"Processed {file_path.name}")

        if total > 0:
            self.report_progress(total, total, "Image transform complete")

        return outputs

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

        normalized = action.lower()
        if normalized not in {"resize", "convert", "rotate", "compress", "strip_exif"}:
            return False, "action must be one of: resize, convert, rotate, compress, strip_exif"
        validators = {
            "resize": self._validate_resize,
            "convert": self._validate_convert,
            "rotate": self._validate_rotate,
            "compress": self._validate_compress,
            "strip_exif": self._validate_strip_exif,
        }
        return validators[normalized](params)

    def _apply_action(
        self,
        image: Image.Image,
        output_path: Path,
        action: str,
        params: dict[str, Any],
    ) -> None:
        """Apply selected transformation action and save output."""
        handlers = {
            "resize": self._save_resized,
            "convert": self._save_converted,
            "rotate": self._save_rotated,
            "compress": self._save_compressed,
            "strip_exif": self._save_stripped_exif,
        }
        handler = handlers.get(action)
        if handler is None:
            raise ValueError(f"Unsupported action: {action}")
        handler(image, output_path, params)

    def _save_resized(self, image: Image.Image, output_path: Path, params: dict[str, Any]) -> None:
        width = int(params["width"])
        height_value = params.get("height")
        target_height = int(height_value) if height_value is not None else None
        if target_height is None:
            ratio = width / image.width
            new_size = (width, max(1, int(image.height * ratio)))
        else:
            new_size = (width, target_height)
        transformed = image.resize(new_size)
        transformed.save(output_path)

    def _save_converted(
        self, image: Image.Image, output_path: Path, params: dict[str, Any]
    ) -> None:
        target_format = str(params["format"]).upper()
        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)

    def _save_rotated(self, image: Image.Image, output_path: Path, params: dict[str, Any]) -> None:
        angle = int(params["angle"])
        transformed = image.rotate(-angle, expand=True)
        transformed.save(output_path)

    def _save_compressed(
        self, image: Image.Image, output_path: Path, params: dict[str, Any]
    ) -> None:
        quality = int(params["quality"])
        save_kwargs: dict[str, Any] = {}
        file_format = (image.format or "").upper()
        save_image = image
        if file_format in {"JPEG", "JPG", "WEBP"}:
            if file_format in {"JPEG", "JPG"} and image.mode in {"RGBA", "LA"}:
                save_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
        save_image.save(output_path, **save_kwargs)

    def _save_stripped_exif(
        self, image: Image.Image, output_path: Path, params: dict[str, Any]
    ) -> None:
        del params
        image.save(output_path, exif=b"")

    def _validate_resize(self, params: dict[str, 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

    def _validate_convert(self, params: dict[str, 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"
        if target_format.upper() not in {"PNG", "JPEG", "JPG", "WEBP"}:
            return False, "format must be one of: PNG, JPEG, JPG, WEBP"
        return True, None

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

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

    def _validate_strip_exif(self, params: dict[str, Any]) -> tuple[bool, str | None]:
        del params
        return True, None

    def _output_path(
        self,
        output_dir: Path,
        file_path: Path,
        action: str,
        params: dict[str, Any],
    ) -> Path:
        """Build output path for a given transformation action."""
        if action == "convert":
            target_format = str(params["format"]).upper()
            extension = "jpg" if target_format in {"JPEG", "JPG"} else target_format.lower()
            desired_name = f"{file_path.stem}.{extension}"
            return self._unique_output_path(output_dir, desired_name)

        return self._unique_output_path(output_dir, file_path.name)

    def _unique_output_path(self, output_dir: Path, desired_name: str) -> Path:
        """Create a unique output path preserving the desired base name."""
        candidate = output_dir / desired_name
        if not candidate.exists():
            return candidate

        desired = Path(desired_name)
        index = 1
        while True:
            next_candidate = output_dir / f"{desired.stem}_{index}{desired.suffix}"
            if not next_candidate.exists():
                return next_candidate
            index += 1
