use base64::Engine;
use image::imageops::FilterType;
use image::ImageFormat;
use std::io::Cursor;

/// Read width and height from a PNG file's IHDR chunk (bytes 16-23).
/// Returns (0, 0) if the buffer is too short or not a valid PNG.
pub fn read_png_dimensions(bytes: &[u8]) -> (u32, u32) {
    if bytes.len() < 24 {
        return (0, 0);
    }
    // PNG signature check
    if &bytes[0..4] != b"\x89PNG" {
        return (0, 0);
    }
    let w = u32::from_be_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]);
    let h = u32::from_be_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]);
    (w, h)
}

/// Resize a PNG image if its width exceeds `max_width`.
/// Resize so neither dimension exceeds `max_edge`, preserving aspect ratio.
/// Returns the (possibly resized) PNG bytes and final (w, h). Caps BOTH width
/// and height — a tall narrow screenshot is constrained by height, not just width.
pub fn resize_png(bytes: Vec<u8>, max_edge: u32) -> Result<(Vec<u8>, u32, u32), String> {
    let (orig_w, orig_h) = read_png_dimensions(&bytes);

    if orig_w == 0 || (orig_w <= max_edge && orig_h <= max_edge) {
        return Ok((bytes, orig_w, orig_h));
    }

    let img = image::load_from_memory_with_format(&bytes, ImageFormat::Png)
        .map_err(|e| format!("Failed to decode PNG: {e}"))?;

    // Scale by whichever edge is over the limit (the larger ratio wins).
    let scale = (max_edge as f64 / orig_w as f64).min(max_edge as f64 / orig_h as f64);
    let new_w = (orig_w as f64 * scale).round().max(1.0) as u32;
    let new_h = (orig_h as f64 * scale).round().max(1.0) as u32;

    let resized = img.resize_exact(new_w, new_h, FilterType::Lanczos3);

    let mut buf = Cursor::new(Vec::new());
    resized
        .write_to(&mut buf, ImageFormat::Png)
        .map_err(|e| format!("Failed to encode resized PNG: {e}"))?;

    Ok((buf.into_inner(), new_w, new_h))
}

/// PNG metadata from the IHDR chunk.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PngMeta {
    pub bit_depth: u8,
    pub color_type: String,
    pub channels: u8,
    pub raw_bytes: u64,
    pub file_bytes: u64,
    pub compression_ratio: f64,
    pub compression_pct: u8,
}

/// Read extended PNG metadata from IHDR chunk.
pub fn read_png_meta(bytes: &[u8]) -> Option<PngMeta> {
    if bytes.len() < 26 || &bytes[0..4] != b"\x89PNG" {
        return None;
    }
    let w = u32::from_be_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]);
    let h = u32::from_be_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]);
    let bit_depth = bytes[24];
    let color_type_byte = bytes[25];

    let (color_type, channels) = match color_type_byte {
        0 => ("Grayscale", 1u8),
        2 => ("RGB", 3),
        3 => ("Indexed", 1),
        4 => ("Grayscale+Alpha", 2),
        6 => ("RGBA", 4),
        _ => ("Unknown", 1),
    };

    let bytes_per_pixel = (channels as u64) * (bit_depth as u64) / 8;
    let raw = (w as u64) * (h as u64) * bytes_per_pixel.max(1);
    let file = bytes.len() as u64;
    let ratio = if file > 0 { raw as f64 / file as f64 } else { 1.0 };
    let pct = if raw > 0 { ((1.0 - (file as f64 / raw as f64)) * 100.0).max(0.0) as u8 } else { 0 };

    Some(PngMeta {
        bit_depth,
        color_type: color_type.to_string(),
        channels,
        raw_bytes: raw,
        file_bytes: file,
        compression_ratio: (ratio * 10.0).round() / 10.0,
        compression_pct: pct,
    })
}

/// Parse a data URL like `data:image/png;base64,iVBOR...` into (mime_type, raw_bytes).
pub fn parse_data_url(data_url: &str) -> Result<(String, Vec<u8>), String> {
    let rest = data_url
        .strip_prefix("data:")
        .ok_or("Not a data URL")?;

    let (meta, b64) = rest
        .split_once(",")
        .ok_or("No comma in data URL")?;

    let mime = meta
        .split(';')
        .next()
        .unwrap_or("application/octet-stream")
        .to_string();

    let bytes = base64::engine::general_purpose::STANDARD
        .decode(b64)
        .map_err(|e| format!("Base64 decode error: {e}"))?;

    Ok((mime, bytes))
}