use image::{DynamicImage, Rgba};
use imageproc::drawing::draw_text_mut;

use crate::error::{PixstripError, Result};
use crate::types::Dimensions;

use super::{WatermarkConfig, WatermarkPosition};

pub fn calculate_position(
    position: WatermarkPosition,
    image_size: Dimensions,
    watermark_size: Dimensions,
    margin: u32,
) -> (u32, u32) {
    let iw = image_size.width;
    let ih = image_size.height;
    let ww = watermark_size.width;
    let wh = watermark_size.height;

    let center_x = iw.saturating_sub(ww) / 2;
    let center_y = ih.saturating_sub(wh) / 2;
    let right_x = iw.saturating_sub(ww).saturating_sub(margin);
    let bottom_y = ih.saturating_sub(wh).saturating_sub(margin);

    match position {
        WatermarkPosition::TopLeft => (margin, margin),
        WatermarkPosition::TopCenter => (center_x, margin),
        WatermarkPosition::TopRight => (right_x, margin),
        WatermarkPosition::MiddleLeft => (margin, center_y),
        WatermarkPosition::Center => (center_x, center_y),
        WatermarkPosition::MiddleRight => (right_x, center_y),
        WatermarkPosition::BottomLeft => (margin, bottom_y),
        WatermarkPosition::BottomCenter => (center_x, bottom_y),
        WatermarkPosition::BottomRight => (right_x, bottom_y),
    }
}

pub fn apply_watermark(
    img: DynamicImage,
    config: &WatermarkConfig,
) -> Result<DynamicImage> {
    match config {
        WatermarkConfig::Text {
            text,
            position,
            font_size,
            opacity,
            color,
            font_family,
            rotation,
            tiled,
            margin,
        } => {
            if *tiled {
                apply_tiled_text_watermark(img, text, *font_size, *opacity, *color, font_family.as_deref(), *rotation, *margin)
            } else {
                apply_text_watermark(img, text, *position, *font_size, *opacity, *color, font_family.as_deref(), *rotation, *margin)
            }
        }
        WatermarkConfig::Image {
            path,
            position,
            opacity,
            scale,
            rotation,
            tiled,
            margin,
        } => {
            if *tiled {
                apply_tiled_image_watermark(img, path, *opacity, *scale, *rotation, *margin)
            } else {
                apply_image_watermark(img, path, *position, *opacity, *scale, *rotation, *margin)
            }
        }
    }
}

/// Cache for font data to avoid repeated filesystem walks during preview updates
static FONT_CACHE: std::sync::Mutex<Option<FontCache>> = std::sync::Mutex::new(None);
static DEFAULT_FONT_CACHE: std::sync::OnceLock<Option<Vec<u8>>> = std::sync::OnceLock::new();

struct FontCache {
    entries: std::collections::HashMap<String, Vec<u8>>,
}

fn find_system_font(family: Option<&str>) -> Result<Vec<u8>> {
    // If a specific font family was requested, check the cache first
    if let Some(name) = family {
        if !name.is_empty() {
            let name_lower = name.to_lowercase();

            // Check cache
            if let Ok(cache) = FONT_CACHE.lock() {
                if let Some(ref c) = *cache {
                    if let Some(data) = c.entries.get(&name_lower) {
                        return Ok(data.clone());
                    }
                }
            }

            // Cache miss - search filesystem
            let search_dirs = [
                "/usr/share/fonts",
                "/usr/local/share/fonts",
            ];
            for dir in &search_dirs {
                if let Ok(entries) = walkdir(std::path::Path::new(dir)) {
                    for path in entries {
                        let file_name = path.file_name()
                            .and_then(|n| n.to_str())
                            .unwrap_or("")
                            .to_lowercase();
                        if file_name.contains(&name_lower)
                            && (file_name.ends_with(".ttf") || file_name.ends_with(".otf"))
                            && (file_name.contains("regular") || (!file_name.contains("bold") && !file_name.contains("italic")))
                        {
                            if let Ok(data) = std::fs::read(&path) {
                                // Store in cache
                                if let Ok(mut cache) = FONT_CACHE.lock() {
                                    let c = cache.get_or_insert_with(|| FontCache {
                                        entries: std::collections::HashMap::new(),
                                    });
                                    c.entries.insert(name_lower, data.clone());
                                }
                                return Ok(data);
                            }
                        }
                    }
                }
            }
        }
    }

    // Fall back to default system fonts (cached via OnceLock)
    let default = DEFAULT_FONT_CACHE.get_or_init(|| {
        let candidates = [
            "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
            "/usr/share/fonts/TTF/DejaVuSans.ttf",
            "/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf",
            "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
            "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf",
            "/usr/share/fonts/noto/NotoSans-Regular.ttf",
        ];
        for path in &candidates {
            if let Ok(data) = std::fs::read(path) {
                return Some(data);
            }
        }
        None
    });

    match default {
        Some(data) => Ok(data.clone()),
        None => Err(PixstripError::Processing {
            operation: "watermark".into(),
            reason: "No system font found for text watermark".into(),
        }),
    }
}

/// Recursively walk a directory and collect file paths (max depth 5)
fn walkdir(dir: &std::path::Path) -> std::io::Result<Vec<std::path::PathBuf>> {
    walkdir_depth(dir, 5)
}

fn walkdir_depth(dir: &std::path::Path, max_depth: u32) -> std::io::Result<Vec<std::path::PathBuf>> {
    const MAX_RESULTS: usize = 10_000;
    let mut results = Vec::new();
    walkdir_depth_inner(dir, max_depth, &mut results, MAX_RESULTS);
    Ok(results)
}

fn walkdir_depth_inner(dir: &std::path::Path, max_depth: u32, results: &mut Vec<std::path::PathBuf>, max: usize) {
    if max_depth == 0 || !dir.is_dir() || results.len() >= max {
        return;
    }
    let Ok(entries) = std::fs::read_dir(dir) else { return };
    for entry in entries {
        if results.len() >= max {
            break;
        }
        let Ok(entry) = entry else { continue };
        let path = entry.path();
        if path.is_dir() {
            walkdir_depth_inner(&path, max_depth - 1, results, max);
        } else {
            results.push(path);
        }
    }
}

/// Estimate text dimensions for a given string and font size.
/// Returns (width, height) in pixels.
fn estimate_text_dimensions(text: &str, font_size: f32) -> (u32, u32) {
    let w = ((text.chars().count().min(10_000) as f32 * font_size.min(1000.0) * 0.6) as u32)
        .saturating_add(4)
        .min(16384);
    let h = ((font_size.min(1000.0) * 1.4) as u32)
        .saturating_add(4)
        .min(4096);
    (w, h)
}

/// Render text onto a transparent RGBA buffer and return it as a DynamicImage
fn render_text_to_image(
    text: &str,
    font: &ab_glyph::FontArc,
    font_size: f32,
    color: [u8; 4],
    opacity: f32,
) -> image::RgbaImage {
    let scale = ab_glyph::PxScale::from(font_size);
    let (text_width, text_height) = estimate_text_dimensions(text, font_size);

    let alpha = (opacity * 255.0).clamp(0.0, 255.0) as u8;
    let draw_color = Rgba([color[0], color[1], color[2], alpha]);

    let mut buf = image::RgbaImage::new(text_width.max(1), text_height.max(1));
    draw_text_mut(&mut buf, draw_color, 2, 2, scale, font, text);
    buf
}

/// Expand canvas so rotated content fits without clipping, then rotate
fn rotate_on_expanded_canvas(img: &image::RgbaImage, radians: f32) -> DynamicImage {
    let w = img.width() as f32;
    let h = img.height() as f32;
    let cos = radians.abs().cos();
    let sin = radians.abs().sin();
    // Bounding box of rotated rectangle
    let new_w = (w * cos + h * sin).ceil() as u32 + 2;
    let new_h = (w * sin + h * cos).ceil() as u32 + 2;

    // Place original in center of expanded transparent canvas
    let mut expanded = image::RgbaImage::new(new_w, new_h);
    let ox = (new_w.saturating_sub(img.width())) / 2;
    let oy = (new_h.saturating_sub(img.height())) / 2;
    image::imageops::overlay(&mut expanded, img, ox as i64, oy as i64);

    imageproc::geometric_transformations::rotate_about_center(
        &expanded,
        radians,
        imageproc::geometric_transformations::Interpolation::Bilinear,
        Rgba([0, 0, 0, 0]),
    ).into()
}

/// Rotate an RGBA image by the given WatermarkRotation
fn rotate_watermark_image(
    img: DynamicImage,
    rotation: super::WatermarkRotation,
) -> DynamicImage {
    match rotation {
        super::WatermarkRotation::Degrees90 => img.rotate90(),
        super::WatermarkRotation::Degrees45 => {
            rotate_on_expanded_canvas(&img.to_rgba8(), std::f32::consts::FRAC_PI_4)
        }
        super::WatermarkRotation::DegreesNeg45 => {
            rotate_on_expanded_canvas(&img.to_rgba8(), -std::f32::consts::FRAC_PI_4)
        }
        super::WatermarkRotation::Custom(degrees) => {
            rotate_on_expanded_canvas(&img.to_rgba8(), degrees.to_radians())
        }
    }
}

fn apply_text_watermark(
    img: DynamicImage,
    text: &str,
    position: WatermarkPosition,
    font_size: f32,
    opacity: f32,
    color: [u8; 4],
    font_family: Option<&str>,
    rotation: Option<super::WatermarkRotation>,
    margin_px: u32,
) -> Result<DynamicImage> {
    if text.is_empty() {
        return Ok(img);
    }
    let font_data = find_system_font(font_family)?;
    let font = ab_glyph::FontArc::try_from_vec(font_data).map_err(|_| {
        PixstripError::Processing {
            operation: "watermark".into(),
            reason: "Failed to load font".into(),
        }
    })?;

    if let Some(rot) = rotation {
        // Render text to buffer, rotate, then overlay
        let text_buf = render_text_to_image(text, &font, font_size, color, opacity);
        let rotated = rotate_watermark_image(DynamicImage::ImageRgba8(text_buf), rot);

        let wm_dims = Dimensions {
            width: rotated.width(),
            height: rotated.height(),
        };
        let image_dims = Dimensions {
            width: img.width(),
            height: img.height(),
        };
        let margin = if margin_px > 0 { margin_px } else { (font_size * 0.5) as u32 };
        let (x, y) = calculate_position(position, image_dims, wm_dims, margin);

        let mut rgba = img.into_rgba8();
        image::imageops::overlay(&mut rgba, &rotated.to_rgba8(), x as i64, y as i64);
        Ok(DynamicImage::ImageRgba8(rgba))
    } else {
        // No rotation - draw text directly (faster)
        let scale = ab_glyph::PxScale::from(font_size);
        let (tw, th) = estimate_text_dimensions(text, font_size);
        let text_dims = Dimensions {
            width: tw,
            height: th,
        };
        let image_dims = Dimensions {
            width: img.width(),
            height: img.height(),
        };
        let margin = if margin_px > 0 { margin_px } else { (font_size * 0.5) as u32 };
        let (x, y) = calculate_position(position, image_dims, text_dims, margin);

        let alpha = (opacity * 255.0).clamp(0.0, 255.0) as u8;
        let draw_color = Rgba([color[0], color[1], color[2], alpha]);

        let mut rgba = img.into_rgba8();
        draw_text_mut(&mut rgba, draw_color, x as i32, y as i32, scale, &font, text);
        Ok(DynamicImage::ImageRgba8(rgba))
    }
}

fn apply_tiled_text_watermark(
    img: DynamicImage,
    text: &str,
    font_size: f32,
    opacity: f32,
    color: [u8; 4],
    font_family: Option<&str>,
    rotation: Option<super::WatermarkRotation>,
    margin: u32,
) -> Result<DynamicImage> {
    if text.is_empty() {
        return Ok(img);
    }
    let font_data = find_system_font(font_family)?;
    let font = ab_glyph::FontArc::try_from_vec(font_data).map_err(|_| {
        PixstripError::Processing {
            operation: "watermark".into(),
            reason: "Failed to load font".into(),
        }
    })?;

    let spacing = margin.max(20);
    let mut rgba = img.into_rgba8();
    let (iw, ih) = (rgba.width(), rgba.height());

    if let Some(rot) = rotation {
        // Render a single tile, rotate it, then tile across the image
        let tile_buf = render_text_to_image(text, &font, font_size, color, opacity);
        let rotated = rotate_watermark_image(DynamicImage::ImageRgba8(tile_buf), rot);
        let tile = rotated.to_rgba8();
        let tw = tile.width();
        let th = tile.height();

        let mut y: i64 = 0;
        while y < ih as i64 {
            let mut x: i64 = 0;
            while x < iw as i64 {
                image::imageops::overlay(&mut rgba, &tile, x, y);
                x += tw as i64 + spacing as i64;
            }
            y += th as i64 + spacing as i64;
        }
    } else {
        // No rotation - draw text directly (faster)
        let scale = ab_glyph::PxScale::from(font_size);
        let alpha = (opacity * 255.0).clamp(0.0, 255.0) as u8;
        let draw_color = Rgba([color[0], color[1], color[2], alpha]);

        let (text_width, text_height) = estimate_text_dimensions(text, font_size);

        let mut y: i64 = 0;
        while y < ih as i64 {
            let mut x: i64 = 0;
            while x < iw as i64 {
                draw_text_mut(&mut rgba, draw_color, x as i32, y as i32, scale, &font, text);
                x += text_width as i64 + spacing as i64;
            }
            y += text_height as i64 + spacing as i64;
        }
    }

    Ok(DynamicImage::ImageRgba8(rgba))
}

fn apply_tiled_image_watermark(
    img: DynamicImage,
    watermark_path: &std::path::Path,
    opacity: f32,
    scale: f32,
    rotation: Option<super::WatermarkRotation>,
    margin: u32,
) -> Result<DynamicImage> {
    let watermark = image::open(watermark_path).map_err(|e| PixstripError::Processing {
        operation: "watermark".into(),
        reason: format!("Failed to load watermark image: {}", e),
    })?;

    let safe_scale = if scale.is_finite() && scale > 0.0 { scale } else { 1.0 };
    let wm_width = ((watermark.width() as f32 * safe_scale) as u32).clamp(1, 16384);
    let wm_height = ((watermark.height() as f32 * safe_scale) as u32).clamp(1, 16384);

    let mut watermark = watermark.resize_exact(wm_width, wm_height, image::imageops::FilterType::Lanczos3);
    if let Some(rot) = rotation {
        watermark = rotate_watermark_image(watermark, rot);
    }
    let overlay = watermark.to_rgba8();
    let ow = overlay.width();
    let oh = overlay.height();
    let alpha_factor = opacity.clamp(0.0, 1.0);
    let spacing = margin.max(10);

    let mut base = img.into_rgba8();
    let (iw, ih) = (base.width(), base.height());

    let mut ty = 0u32;
    while ty < ih {
        let mut tx = 0u32;
        while tx < iw {
            for oy in 0..oh {
                for ox in 0..ow {
                    let px = tx + ox;
                    let py = ty + oy;
                    if px < iw && py < ih {
                        let wm_pixel = overlay.get_pixel(ox, oy);
                        let base_pixel = base.get_pixel(px, py);
                        let wm_alpha = (wm_pixel[3] as f32 / 255.0) * alpha_factor;
                        let base_alpha = base_pixel[3] as f32 / 255.0;
                        let out_alpha = wm_alpha + base_alpha * (1.0 - wm_alpha);
                        if out_alpha > 0.0 {
                            let r = ((wm_pixel[0] as f32 * wm_alpha + base_pixel[0] as f32 * base_alpha * (1.0 - wm_alpha)) / out_alpha) as u8;
                            let g = ((wm_pixel[1] as f32 * wm_alpha + base_pixel[1] as f32 * base_alpha * (1.0 - wm_alpha)) / out_alpha) as u8;
                            let b = ((wm_pixel[2] as f32 * wm_alpha + base_pixel[2] as f32 * base_alpha * (1.0 - wm_alpha)) / out_alpha) as u8;
                            let a = (out_alpha * 255.0) as u8;
                            base.put_pixel(px, py, Rgba([r, g, b, a]));
                        }
                    }
                }
            }
            tx += ow + spacing;
        }
        ty += oh + spacing;
    }

    Ok(DynamicImage::ImageRgba8(base))
}

fn apply_image_watermark(
    img: DynamicImage,
    watermark_path: &std::path::Path,
    position: WatermarkPosition,
    opacity: f32,
    scale: f32,
    rotation: Option<super::WatermarkRotation>,
    margin: u32,
) -> Result<DynamicImage> {
    let watermark = image::open(watermark_path).map_err(|e| PixstripError::Processing {
        operation: "watermark".into(),
        reason: format!("Failed to load watermark image: {}", e),
    })?;

    // Scale the watermark (capped to prevent OOM on extreme scale values)
    let safe_scale = if scale.is_finite() && scale > 0.0 { scale } else { 1.0 };
    let wm_width = ((watermark.width() as f32 * safe_scale) as u32).clamp(1, 16384);
    let wm_height = ((watermark.height() as f32 * safe_scale) as u32).clamp(1, 16384);

    let mut watermark = watermark.resize_exact(
        wm_width,
        wm_height,
        image::imageops::FilterType::Lanczos3,
    );

    if let Some(rot) = rotation {
        watermark = rotate_watermark_image(watermark, rot);
    }

    let image_dims = Dimensions {
        width: img.width(),
        height: img.height(),
    };
    let wm_dims = Dimensions {
        width: watermark.width(),
        height: watermark.height(),
    };

    let (x, y) = calculate_position(position, image_dims, wm_dims, margin);

    let mut base = img.into_rgba8();
    let overlay = watermark.to_rgba8();
    let ow = overlay.width();
    let oh = overlay.height();

    let alpha_factor = opacity.clamp(0.0, 1.0);

    for oy in 0..oh {
        for ox in 0..ow {
            let px = x + ox;
            let py = y + oy;

            if px < base.width() && py < base.height() {
                let wm_pixel = overlay.get_pixel(ox, oy);
                let base_pixel = base.get_pixel(px, py);

                let wm_alpha = (wm_pixel[3] as f32 / 255.0) * alpha_factor;
                let base_alpha = base_pixel[3] as f32 / 255.0;

                let out_alpha = wm_alpha + base_alpha * (1.0 - wm_alpha);

                if out_alpha > 0.0 {
                    let r = ((wm_pixel[0] as f32 * wm_alpha
                        + base_pixel[0] as f32 * base_alpha * (1.0 - wm_alpha))
                        / out_alpha) as u8;
                    let g = ((wm_pixel[1] as f32 * wm_alpha
                        + base_pixel[1] as f32 * base_alpha * (1.0 - wm_alpha))
                        / out_alpha) as u8;
                    let b = ((wm_pixel[2] as f32 * wm_alpha
                        + base_pixel[2] as f32 * base_alpha * (1.0 - wm_alpha))
                        / out_alpha) as u8;
                    let a = (out_alpha * 255.0) as u8;

                    base.put_pixel(px, py, Rgba([r, g, b, a]));
                }
            }
        }
    }

    Ok(DynamicImage::ImageRgba8(base))
}
