use image::{DynamicImage, Rgba, RgbaImage};

use crate::error::Result;
use super::AdjustmentsConfig;

pub fn apply_adjustments(
    mut img: DynamicImage,
    config: &AdjustmentsConfig,
) -> Result<DynamicImage> {
    // Crop to aspect ratio (before other adjustments)
    if let Some((w_ratio, h_ratio)) = config.crop_aspect_ratio {
        img = crop_to_aspect_ratio(img, w_ratio, h_ratio);
    }

    // Trim whitespace
    if config.trim_whitespace {
        img = trim_whitespace(img);
    }

    // Brightness
    if config.brightness != 0 {
        img = img.brighten(config.brightness);
    }

    // Contrast
    if config.contrast != 0 {
        img = img.adjust_contrast(config.contrast as f32);
    }

    // Saturation
    if config.saturation != 0 {
        img = adjust_saturation(img, config.saturation);
    }

    // Grayscale
    if config.grayscale {
        img = img.grayscale();
    }

    // Sepia
    if config.sepia {
        img = apply_sepia(img);
    }

    // Sharpen
    if config.sharpen {
        img = img.unsharpen(1.0, 5);
    }

    // Canvas padding
    if config.canvas_padding > 0 {
        img = add_canvas_padding(img, config.canvas_padding);
    }

    Ok(img)
}

fn crop_to_aspect_ratio(img: DynamicImage, w_ratio: f64, h_ratio: f64) -> DynamicImage {
    let (iw, ih) = (img.width(), img.height());
    if !w_ratio.is_finite() || !h_ratio.is_finite()
        || w_ratio <= 0.0 || h_ratio <= 0.0
        || iw == 0 || ih == 0
    {
        return img;
    }
    let target_ratio = w_ratio / h_ratio;
    let current_ratio = iw as f64 / ih as f64;

    let (crop_w, crop_h) = if current_ratio > target_ratio {
        // Image is wider than target, crop width
        let new_w = (ih as f64 * target_ratio).round().max(1.0) as u32;
        (new_w.min(iw).max(1), ih)
    } else {
        // Image is taller than target, crop height
        let new_h = (iw as f64 / target_ratio).round().max(1.0) as u32;
        (iw, new_h.min(ih).max(1))
    };

    let x = iw.saturating_sub(crop_w) / 2;
    let y = ih.saturating_sub(crop_h) / 2;

    img.crop_imm(x, y, crop_w, crop_h)
}

fn trim_whitespace(img: DynamicImage) -> DynamicImage {
    let rgba = img.to_rgba8();
    let (w, h) = (rgba.width(), rgba.height());

    if w == 0 || h == 0 {
        return img;
    }

    // Get corner pixel as reference background color
    let bg = *rgba.get_pixel(0, 0);
    let threshold = 30u32;

    let is_bg = |p: &Rgba<u8>| -> bool {
        let dr = (p[0] as i32 - bg[0] as i32).unsigned_abs();
        let dg = (p[1] as i32 - bg[1] as i32).unsigned_abs();
        let db = (p[2] as i32 - bg[2] as i32).unsigned_abs();
        dr + dg + db < threshold
    };

    let mut top = 0u32;
    let mut bottom = h - 1;
    let mut left = 0u32;
    let mut right = w - 1;

    // Find top
    'top: for y in 0..h {
        for x in 0..w {
            if !is_bg(rgba.get_pixel(x, y)) {
                top = y;
                break 'top;
            }
        }
    }

    // Find bottom
    'bottom: for y in (0..h).rev() {
        for x in 0..w {
            if !is_bg(rgba.get_pixel(x, y)) {
                bottom = y;
                break 'bottom;
            }
        }
    }

    // Find left
    'left: for x in 0..w {
        for y in top..=bottom {
            if !is_bg(rgba.get_pixel(x, y)) {
                left = x;
                break 'left;
            }
        }
    }

    // Find right
    'right: for x in (0..w).rev() {
        for y in top..=bottom {
            if !is_bg(rgba.get_pixel(x, y)) {
                right = x;
                break 'right;
            }
        }
    }

    let crop_w = right.saturating_sub(left).saturating_add(1);
    let crop_h = bottom.saturating_sub(top).saturating_add(1);

    if crop_w == 0 || crop_h == 0 || (crop_w == w && crop_h == h) {
        return img;
    }

    img.crop_imm(left, top, crop_w, crop_h)
}

fn adjust_saturation(img: DynamicImage, amount: i32) -> DynamicImage {
    let amount = amount.clamp(-100, 100);
    let mut rgba = img.into_rgba8();
    let factor = 1.0 + (amount as f64 / 100.0);

    for pixel in rgba.pixels_mut() {
        let r = pixel[0] as f64;
        let g = pixel[1] as f64;
        let b = pixel[2] as f64;

        let gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;

        pixel[0] = (gray + (r - gray) * factor).clamp(0.0, 255.0) as u8;
        pixel[1] = (gray + (g - gray) * factor).clamp(0.0, 255.0) as u8;
        pixel[2] = (gray + (b - gray) * factor).clamp(0.0, 255.0) as u8;
    }

    DynamicImage::ImageRgba8(rgba)
}

fn apply_sepia(img: DynamicImage) -> DynamicImage {
    let mut rgba = img.into_rgba8();

    for pixel in rgba.pixels_mut() {
        let r = pixel[0] as f64;
        let g = pixel[1] as f64;
        let b = pixel[2] as f64;

        pixel[0] = (0.393 * r + 0.769 * g + 0.189 * b).clamp(0.0, 255.0) as u8;
        pixel[1] = (0.349 * r + 0.686 * g + 0.168 * b).clamp(0.0, 255.0) as u8;
        pixel[2] = (0.272 * r + 0.534 * g + 0.131 * b).clamp(0.0, 255.0) as u8;
    }

    DynamicImage::ImageRgba8(rgba)
}

fn add_canvas_padding(img: DynamicImage, padding: u32) -> DynamicImage {
    // Cap padding to prevent unreasonable memory allocation
    let padding = padding.min(10_000);
    let (w, h) = (img.width(), img.height());
    let new_w = w.saturating_add(padding.saturating_mul(2)).min(65_535);
    let new_h = h.saturating_add(padding.saturating_mul(2)).min(65_535);

    let mut canvas = RgbaImage::from_pixel(new_w, new_h, Rgba([255, 255, 255, 255]));

    image::imageops::overlay(&mut canvas, &img.to_rgba8(), padding as i64, padding as i64);

    DynamicImage::ImageRgba8(canvas)
}
