"""
Centralized image processing utilities for pyPhotoAlbum.

This module consolidates common image operations to avoid code duplication
across models.py, pdf_exporter.py, and async_backend.py.
"""

from typing import Tuple
from PIL import Image

# =============================================================================
# Image Processing Utilities
# =============================================================================


def apply_pil_rotation(image: Image.Image, pil_rotation_90: int) -> Image.Image:
    """
    Apply 90-degree rotation increments to a PIL image.

    Args:
        image: PIL Image to rotate
        pil_rotation_90: Number of 90-degree rotations (0, 1, 2, or 3)

    Returns:
        Rotated PIL Image (or original if no rotation needed)
    """
    if pil_rotation_90 <= 0:
        return image

    angle = pil_rotation_90 * 90
    if angle == 90:
        return image.transpose(Image.Transpose.ROTATE_270)  # CCW 90 = rotate right
    elif angle == 180:
        return image.transpose(Image.Transpose.ROTATE_180)
    elif angle == 270:
        return image.transpose(Image.Transpose.ROTATE_90)  # CCW 270 = rotate left

    return image


def convert_to_rgba(image: Image.Image) -> Image.Image:
    """
    Convert image to RGBA mode if not already.

    Args:
        image: PIL Image in any mode

    Returns:
        PIL Image in RGBA mode
    """
    if image.mode != "RGBA":
        return image.convert("RGBA")
    return image


def calculate_center_crop_coords(
    img_width: int,
    img_height: int,
    target_width: float,
    target_height: float,
    crop_info: Tuple[float, float, float, float] = (0, 0, 1, 1),
) -> Tuple[float, float, float, float]:
    """
    Calculate texture/crop coordinates for center-crop fitting an image to a target aspect ratio.

    This implements the center-crop algorithm used for fitting images into frames
    while preserving aspect ratio. The image is scaled to cover the target area,
    then the excess is cropped equally from both sides.

    Args:
        img_width: Source image width in pixels
        img_height: Source image height in pixels
        target_width: Target frame width (any unit, only ratio matters)
        target_height: Target frame height (any unit, only ratio matters)
        crop_info: Additional crop range as (x_min, y_min, x_max, y_max) in 0-1 range
                   Default (0, 0, 1, 1) means no additional cropping

    Returns:
        Tuple of (tx_min, ty_min, tx_max, ty_max) texture coordinates in 0-1 range
    """
    crop_x_min, crop_y_min, crop_x_max, crop_y_max = crop_info

    img_aspect = img_width / img_height
    target_aspect = target_width / target_height

    # Calculate base texture coordinates for center crop
    if img_aspect > target_aspect:
        # Image is wider than target - crop horizontally
        scale = target_aspect / img_aspect
        tx_offset = (1.0 - scale) / 2.0
        tx_min_base = tx_offset
        tx_max_base = 1.0 - tx_offset
        ty_min_base = 0.0
        ty_max_base = 1.0
    else:
        # Image is taller than target - crop vertically
        scale = img_aspect / target_aspect
        ty_offset = (1.0 - scale) / 2.0
        tx_min_base = 0.0
        tx_max_base = 1.0
        ty_min_base = ty_offset
        ty_max_base = 1.0 - ty_offset

    # Apply additional crop from crop_info (for spanning elements, user crops, etc.)
    tx_range = tx_max_base - tx_min_base
    ty_range = ty_max_base - ty_min_base

    tx_min = tx_min_base + crop_x_min * tx_range
    tx_max = tx_min_base + crop_x_max * tx_range
    ty_min = ty_min_base + crop_y_min * ty_range
    ty_max = ty_min_base + crop_y_max * ty_range

    return (tx_min, ty_min, tx_max, ty_max)


def crop_image_to_coords(image: Image.Image, coords: Tuple[float, float, float, float]) -> Image.Image:
    """
    Crop an image using normalized texture coordinates.

    Args:
        image: PIL Image to crop
        coords: Tuple of (tx_min, ty_min, tx_max, ty_max) in 0-1 range

    Returns:
        Cropped PIL Image
    """
    tx_min, ty_min, tx_max, ty_max = coords
    img_width, img_height = image.size

    crop_left_px = int(tx_min * img_width)
    crop_right_px = int(tx_max * img_width)
    crop_top_px = int(ty_min * img_height)
    crop_bottom_px = int(ty_max * img_height)

    return image.crop((crop_left_px, crop_top_px, crop_right_px, crop_bottom_px))


def resize_to_fit(
    image: Image.Image, max_size: int, resample: Image.Resampling = Image.Resampling.LANCZOS
) -> Image.Image:
    """
    Resize image to fit within max_size while preserving aspect ratio.

    Args:
        image: PIL Image to resize
        max_size: Maximum dimension (width or height)
        resample: Resampling filter (default LANCZOS for quality)

    Returns:
        Resized PIL Image, or original if already smaller
    """
    if image.width <= max_size and image.height <= max_size:
        return image

    scale = min(max_size / image.width, max_size / image.height)
    new_width = int(image.width * scale)
    new_height = int(image.height * scale)

    return image.resize((new_width, new_height), resample)


# =============================================================================
# Image Styling Utilities
# =============================================================================


def apply_rounded_corners(
    image: Image.Image,
    radius_percent: float,
    antialias: bool = True,
) -> Image.Image:
    """
    Apply rounded corners to an image.

    Args:
        image: PIL Image (should be RGBA)
        radius_percent: Corner radius as percentage of shorter side (0-50)
        antialias: If True, use supersampling for smooth antialiased edges

    Returns:
        PIL Image with rounded corners (transparent outside corners)
    """
    from PIL import ImageDraw

    if radius_percent <= 0:
        return image

    # Ensure RGBA mode for transparency
    if image.mode != "RGBA":
        image = image.convert("RGBA")

    width, height = image.size
    shorter_side = min(width, height)

    # Clamp radius to 0-50%
    radius_percent = max(0, min(50, radius_percent))
    radius = int(shorter_side * radius_percent / 100)

    if radius <= 0:
        return image

    # Use supersampling for antialiasing
    if antialias:
        # Create mask at higher resolution (4x), then downscale for smooth edges
        supersample_factor = 4
        ss_width = width * supersample_factor
        ss_height = height * supersample_factor
        ss_radius = radius * supersample_factor

        mask_large = Image.new("L", (ss_width, ss_height), 0)
        draw = ImageDraw.Draw(mask_large)
        draw.rounded_rectangle([0, 0, ss_width - 1, ss_height - 1], radius=ss_radius, fill=255)

        # Downscale with LANCZOS for smooth antialiased edges
        mask = mask_large.resize((width, height), Image.Resampling.LANCZOS)
    else:
        # Original non-antialiased path
        mask = Image.new("L", (width, height), 0)
        draw = ImageDraw.Draw(mask)
        draw.rounded_rectangle([0, 0, width - 1, height - 1], radius=radius, fill=255)

    # Apply mask to alpha channel
    result = image.copy()
    if result.mode == "RGBA":
        # Composite with existing alpha
        r, g, b, a = result.split()
        # Combine existing alpha with our mask
        from PIL import ImageChops

        new_alpha = ImageChops.multiply(a, mask)
        result = Image.merge("RGBA", (r, g, b, new_alpha))
    else:
        result.putalpha(mask)

    return result


def apply_drop_shadow(
    image: Image.Image,
    offset: Tuple[float, float] = (2.0, 2.0),
    blur_radius: float = 3.0,
    shadow_color: Tuple[int, int, int, int] = (0, 0, 0, 128),
    expand: bool = True,
) -> Image.Image:
    """
    Apply a drop shadow effect to an image.

    Args:
        image: PIL Image (should be RGBA with transparency for best results)
        offset: Shadow offset in pixels (x, y)
        blur_radius: Shadow blur radius in pixels
        shadow_color: Shadow color as RGBA tuple (0-255)
        expand: If True, expand canvas to fit shadow; if False, shadow may be clipped

    Returns:
        PIL Image with drop shadow
    """
    from PIL import ImageFilter

    # Ensure RGBA
    if image.mode != "RGBA":
        image = image.convert("RGBA")

    offset_x, offset_y = int(offset[0]), int(offset[1])
    blur_radius = max(0, int(blur_radius))

    # Calculate canvas expansion needed
    if expand:
        # Account for blur spread and offset
        padding = blur_radius * 2 + max(abs(offset_x), abs(offset_y))
        new_width = image.width + padding * 2
        new_height = image.height + padding * 2
        img_x = padding
        img_y = padding
    else:
        new_width = image.width
        new_height = image.height
        padding = 0
        img_x = 0
        img_y = 0

    # Create shadow layer from alpha channel
    _, _, _, alpha = image.split()

    # Create shadow image (same shape as alpha, filled with shadow color)
    shadow = Image.new("RGBA", (image.width, image.height), shadow_color[:3] + (0,))
    shadow.putalpha(alpha)

    # Apply blur to shadow
    if blur_radius > 0:
        shadow = shadow.filter(ImageFilter.GaussianBlur(blur_radius))

    # Adjust shadow alpha based on shadow_color alpha
    if shadow_color[3] < 255:
        r, g, b, a = shadow.split()
        # Scale alpha by shadow_color alpha
        a = a.point(lambda x: int(x * shadow_color[3] / 255))
        shadow = Image.merge("RGBA", (r, g, b, a))

    # Create result canvas
    result = Image.new("RGBA", (new_width, new_height), (0, 0, 0, 0))

    # Paste shadow (offset from image position)
    shadow_x = img_x + offset_x
    shadow_y = img_y + offset_y
    result.paste(shadow, (shadow_x, shadow_y), shadow)

    # Paste original image on top
    result.paste(image, (img_x, img_y), image)

    return result


def create_border_image(
    width: int,
    height: int,
    border_width: int,
    border_color: Tuple[int, int, int] = (0, 0, 0),
    corner_radius: int = 0,
) -> Image.Image:
    """
    Create an image with just a border (transparent center).

    Args:
        width: Image width in pixels
        height: Image height in pixels
        border_width: Border width in pixels
        border_color: Border color as RGB tuple (0-255)
        corner_radius: Corner radius in pixels (0 for square corners)

    Returns:
        PIL Image with border only (RGBA with transparent center)
    """
    from PIL import ImageDraw

    if border_width <= 0:
        return Image.new("RGBA", (width, height), (0, 0, 0, 0))

    result = Image.new("RGBA", (width, height), (0, 0, 0, 0))
    draw = ImageDraw.Draw(result)

    # Draw outer rounded rectangle
    outer_color = border_color + (255,)  # Add full alpha
    if corner_radius > 0:
        draw.rounded_rectangle(
            [0, 0, width - 1, height - 1],
            radius=corner_radius,
            fill=outer_color,
        )
        # Draw inner transparent area
        inner_radius = max(0, corner_radius - border_width)
        draw.rounded_rectangle(
            [border_width, border_width, width - 1 - border_width, height - 1 - border_width],
            radius=inner_radius,
            fill=(0, 0, 0, 0),
        )
    else:
        draw.rectangle([0, 0, width - 1, height - 1], fill=outer_color)
        draw.rectangle(
            [border_width, border_width, width - 1 - border_width, height - 1 - border_width],
            fill=(0, 0, 0, 0),
        )

    return result


def apply_style_to_image(
    image: Image.Image,
    corner_radius: float = 0.0,
    border_width: float = 0.0,
    border_color: Tuple[int, int, int] = (0, 0, 0),
    shadow_enabled: bool = False,
    shadow_offset: Tuple[float, float] = (2.0, 2.0),
    shadow_blur: float = 3.0,
    shadow_color: Tuple[int, int, int, int] = (0, 0, 0, 128),
    dpi: float = 96.0,
) -> Image.Image:
    """
    Apply all styling effects to an image in the correct order.

    Args:
        image: Source PIL Image
        corner_radius: Corner radius as percentage (0-50)
        border_width: Border width in mm
        border_color: Border color as RGB (0-255)
        shadow_enabled: Whether to apply drop shadow
        shadow_offset: Shadow offset in mm (x, y)
        shadow_blur: Shadow blur in mm
        shadow_color: Shadow color as RGBA (0-255)
        dpi: DPI for converting mm to pixels

    Returns:
        Styled PIL Image
    """
    # Ensure RGBA
    result = convert_to_rgba(image)

    # Convert mm to pixels
    mm_to_px = dpi / 25.4
    border_width_px = int(border_width * mm_to_px)
    shadow_offset_px = (shadow_offset[0] * mm_to_px, shadow_offset[1] * mm_to_px)
    shadow_blur_px = shadow_blur * mm_to_px

    # 1. Apply rounded corners first
    if corner_radius > 0:
        result = apply_rounded_corners(result, corner_radius)

    # 2. Apply border (composite border image on top)
    if border_width_px > 0:
        shorter_side = min(result.width, result.height)
        corner_radius_px = int(shorter_side * min(50, corner_radius) / 100) if corner_radius > 0 else 0

        border_img = create_border_image(
            result.width,
            result.height,
            border_width_px,
            border_color,
            corner_radius_px,
        )
        result = Image.alpha_composite(result, border_img)

    # 3. Apply shadow last (expands canvas)
    if shadow_enabled:
        result = apply_drop_shadow(
            result,
            offset=shadow_offset_px,
            blur_radius=shadow_blur_px,
            shadow_color=shadow_color,
            expand=True,
        )

    return result
