"""Image processing service using Pillow."""
import os
import logging

from PIL import Image

logger = logging.getLogger(__name__)


class ImageProcessingError(Exception):
    """Custom exception for image processing failures."""
    pass


# Supported format mappings
FORMAT_MAP = {
    "jpg": "JPEG",
    "jpeg": "JPEG",
    "png": "PNG",
    "webp": "WEBP",
}


def convert_image(
    input_path: str,
    output_path: str,
    output_format: str,
    quality: int = 85,
) -> dict:
    """
    Convert an image to a different format.

    Args:
        input_path: Path to the input image
        output_path: Path for the output image
        output_format: Target format ("jpg", "png", "webp")
        quality: Output quality 1-100 (for lossy formats)

    Returns:
        dict with original_size, converted_size, dimensions

    Raises:
        ImageProcessingError: If conversion fails
    """
    output_format = output_format.lower()
    if output_format not in FORMAT_MAP:
        raise ImageProcessingError(
            f"Unsupported output format: {output_format}. "
            f"Supported: {', '.join(FORMAT_MAP.keys())}"
        )

    pil_format = FORMAT_MAP[output_format]
    os.makedirs(os.path.dirname(output_path), exist_ok=True)

    try:
        original_size = os.path.getsize(input_path)

        # Open and re-encode (strips any malicious payloads)
        with Image.open(input_path) as img:
            # Convert RGBA to RGB for JPEG (JPEG doesn't support alpha)
            if pil_format == "JPEG" and img.mode in ("RGBA", "P", "LA"):
                background = Image.new("RGB", img.size, (255, 255, 255))
                if img.mode == "P":
                    img = img.convert("RGBA")
                background.paste(img, mask=img.split()[-1] if "A" in img.mode else None)
                img = background

            width, height = img.size

            # Save with quality setting
            save_kwargs = {}
            if pil_format in ("JPEG", "WEBP"):
                save_kwargs["quality"] = max(1, min(100, quality))
                save_kwargs["optimize"] = True
            elif pil_format == "PNG":
                save_kwargs["optimize"] = True

            img.save(output_path, format=pil_format, **save_kwargs)

        converted_size = os.path.getsize(output_path)

        logger.info(
            f"Image conversion: {input_path} → {output_format} "
            f"({original_size} → {converted_size})"
        )

        return {
            "original_size": original_size,
            "converted_size": converted_size,
            "width": width,
            "height": height,
            "format": output_format,
        }

    except (IOError, OSError, Image.DecompressionBombError) as e:
        raise ImageProcessingError(f"Image processing failed: {str(e)}")


def resize_image(
    input_path: str,
    output_path: str,
    width: int | None = None,
    height: int | None = None,
    quality: int = 85,
) -> dict:
    """
    Resize an image while maintaining aspect ratio.

    Args:
        input_path: Path to the input image
        output_path: Path for the resized image
        width: Target width (None to auto-calculate from height)
        height: Target height (None to auto-calculate from width)
        quality: Output quality 1-100

    Returns:
        dict with original and new dimensions

    Raises:
        ImageProcessingError: If resize fails
    """
    if width is None and height is None:
        raise ImageProcessingError("At least one of width or height must be specified.")

    os.makedirs(os.path.dirname(output_path), exist_ok=True)

    try:
        with Image.open(input_path) as img:
            orig_width, orig_height = img.size

            # Calculate missing dimension to maintain aspect ratio
            if width and not height:
                ratio = width / orig_width
                height = int(orig_height * ratio)
            elif height and not width:
                ratio = height / orig_height
                width = int(orig_width * ratio)

            # Resize using high-quality resampling
            assert width is not None and height is not None
            resized = img.resize((width, height), Image.Resampling.LANCZOS)

            # Detect format from output extension
            ext = os.path.splitext(output_path)[1].lower().strip(".")
            pil_format = FORMAT_MAP.get(ext, "PNG")

            save_kwargs: dict[str, int | bool] = {"optimize": True}
            if pil_format in ("JPEG", "WEBP"):
                save_kwargs["quality"] = quality
                # Handle RGBA for JPEG
                if resized.mode in ("RGBA", "P", "LA"):
                    background = Image.new("RGB", resized.size, (255, 255, 255))
                    if resized.mode == "P":
                        resized = resized.convert("RGBA")
                    background.paste(
                        resized, mask=resized.split()[-1] if "A" in resized.mode else None
                    )
                    resized = background

            resized.save(output_path, format=pil_format, **save_kwargs)

        return {
            "original_width": orig_width,
            "original_height": orig_height,
            "new_width": width,
            "new_height": height,
        }

    except (IOError, OSError, Image.DecompressionBombError) as e:
        raise ImageProcessingError(f"Image resize failed: {str(e)}")


# ─── Allowed color modes for SVG tracing ─────────────────────
ALLOWED_COLOR_MODES = ("color", "binary")


def convert_image_to_svg(
    input_path: str,
    output_path: str,
    color_mode: str = "color",
) -> dict:
    """
    Convert a raster image to SVG using vtracer.

    Args:
        input_path: Path to the input image (PNG, JPG, WebP)
        output_path: Path for the output SVG file
        color_mode: "color" for full-colour trace, "binary" for black & white

    Returns:
        dict with original_size, converted_size, width, height

    Raises:
        ImageProcessingError: If conversion fails
    """
    import vtracer

    if color_mode not in ALLOWED_COLOR_MODES:
        color_mode = "color"

    os.makedirs(os.path.dirname(output_path), exist_ok=True)

    try:
        original_size = os.path.getsize(input_path)

        # Read dimensions via Pillow (also validates image)
        with Image.open(input_path) as img:
            width, height = img.size

        vtracer.convert_image_to_svg_py(
            input_path,
            output_path,
            colormode=color_mode,
        )

        converted_size = os.path.getsize(output_path)

        logger.info(
            f"Image→SVG conversion: {input_path} "
            f"({original_size} → {converted_size})"
        )

        return {
            "original_size": original_size,
            "converted_size": converted_size,
            "width": width,
            "height": height,
            "format": "svg",
        }

    except (IOError, OSError, Image.DecompressionBombError) as e:
        raise ImageProcessingError(f"Image to SVG conversion failed: {str(e)}")
    except Exception as e:
        raise ImageProcessingError(f"Image to SVG conversion failed: {str(e)}")
