use image::{DynamicImage, ImageFormat, ImageReader, imageops::FilterType};
use std::io::Cursor;

pub const THUMB_SIZE_FEED: u32 = 200;
pub const THUMB_SIZE_FULL: u32 = 1000;

#[derive(Debug, Clone)]
pub struct ProcessedImage {
    pub data: Vec<u8>,
    pub mime_type: String,
    pub width: u32,
    pub height: u32,
}

#[derive(Debug, Clone)]
pub struct ImageProcessingResult {
    pub original: ProcessedImage,
    pub thumbnail_feed: Option<ProcessedImage>,
    pub thumbnail_full: Option<ProcessedImage>,
}

#[derive(Debug, thiserror::Error)]
pub enum ImageError {
    #[error("Failed to decode image: {0}")]
    DecodeError(String),
    #[error("Failed to encode image: {0}")]
    EncodeError(String),
    #[error("Unsupported image format: {0}")]
    UnsupportedFormat(String),
    #[error("Image too large: {width}x{height} exceeds maximum {max_dimension}")]
    TooLarge {
        width: u32,
        height: u32,
        max_dimension: u32,
    },
    #[error("File too large: {size} bytes exceeds maximum {max_size} bytes")]
    FileTooLarge { size: usize, max_size: usize },
}

pub const DEFAULT_MAX_FILE_SIZE: usize = 10 * 1024 * 1024;

pub struct ImageProcessor {
    max_dimension: u32,
    max_file_size: usize,
    output_format: OutputFormat,
    generate_thumbnails: bool,
}

#[derive(Debug, Clone, Copy)]
pub enum OutputFormat {
    WebP,
    Jpeg,
    Png,
    Original,
}

impl Default for ImageProcessor {
    fn default() -> Self {
        Self {
            max_dimension: 4096,
            max_file_size: DEFAULT_MAX_FILE_SIZE,
            output_format: OutputFormat::WebP,
            generate_thumbnails: true,
        }
    }
}

impl ImageProcessor {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_max_dimension(mut self, max: u32) -> Self {
        self.max_dimension = max;
        self
    }

    pub fn with_max_file_size(mut self, max: usize) -> Self {
        self.max_file_size = max;
        self
    }

    pub fn with_output_format(mut self, format: OutputFormat) -> Self {
        self.output_format = format;
        self
    }

    pub fn with_thumbnails(mut self, generate: bool) -> Self {
        self.generate_thumbnails = generate;
        self
    }

    pub fn process(
        &self,
        data: &[u8],
        mime_type: &str,
    ) -> Result<ImageProcessingResult, ImageError> {
        if data.len() > self.max_file_size {
            return Err(ImageError::FileTooLarge {
                size: data.len(),
                max_size: self.max_file_size,
            });
        }
        let format = self.detect_format(mime_type, data)?;
        let img = self.decode_image(data, format)?;
        if img.width() > self.max_dimension || img.height() > self.max_dimension {
            return Err(ImageError::TooLarge {
                width: img.width(),
                height: img.height(),
                max_dimension: self.max_dimension,
            });
        }
        let original = self.encode_image(&img)?;
        let thumbnail_feed = if self.generate_thumbnails
            && (img.width() > THUMB_SIZE_FEED || img.height() > THUMB_SIZE_FEED)
        {
            Some(self.generate_thumbnail(&img, THUMB_SIZE_FEED)?)
        } else {
            None
        };
        let thumbnail_full = if self.generate_thumbnails
            && (img.width() > THUMB_SIZE_FULL || img.height() > THUMB_SIZE_FULL)
        {
            Some(self.generate_thumbnail(&img, THUMB_SIZE_FULL)?)
        } else {
            None
        };
        Ok(ImageProcessingResult {
            original,
            thumbnail_feed,
            thumbnail_full,
        })
    }

    fn detect_format(&self, mime_type: &str, data: &[u8]) -> Result<ImageFormat, ImageError> {
        match mime_type.to_lowercase().as_str() {
            "image/jpeg" | "image/jpg" => Ok(ImageFormat::Jpeg),
            "image/png" => Ok(ImageFormat::Png),
            "image/gif" => Ok(ImageFormat::Gif),
            "image/webp" => Ok(ImageFormat::WebP),
            _ => {
                if let Ok(format) = image::guess_format(data) {
                    Ok(format)
                } else {
                    Err(ImageError::UnsupportedFormat(mime_type.to_string()))
                }
            }
        }
    }

    fn decode_image(&self, data: &[u8], format: ImageFormat) -> Result<DynamicImage, ImageError> {
        let cursor = Cursor::new(data);
        let reader = ImageReader::with_format(cursor, format);
        reader
            .decode()
            .map_err(|e| ImageError::DecodeError(e.to_string()))
    }

    fn encode_image(&self, img: &DynamicImage) -> Result<ProcessedImage, ImageError> {
        let (data, mime_type) = match self.output_format {
            OutputFormat::WebP => {
                let mut buf = Vec::new();
                img.write_to(&mut Cursor::new(&mut buf), ImageFormat::WebP)
                    .map_err(|e| ImageError::EncodeError(e.to_string()))?;
                (buf, "image/webp".to_string())
            }
            OutputFormat::Jpeg => {
                let mut buf = Vec::new();
                img.write_to(&mut Cursor::new(&mut buf), ImageFormat::Jpeg)
                    .map_err(|e| ImageError::EncodeError(e.to_string()))?;
                (buf, "image/jpeg".to_string())
            }
            OutputFormat::Png => {
                let mut buf = Vec::new();
                img.write_to(&mut Cursor::new(&mut buf), ImageFormat::Png)
                    .map_err(|e| ImageError::EncodeError(e.to_string()))?;
                (buf, "image/png".to_string())
            }
            OutputFormat::Original => {
                let mut buf = Vec::new();
                img.write_to(&mut Cursor::new(&mut buf), ImageFormat::Png)
                    .map_err(|e| ImageError::EncodeError(e.to_string()))?;
                (buf, "image/png".to_string())
            }
        };
        Ok(ProcessedImage {
            data,
            mime_type,
            width: img.width(),
            height: img.height(),
        })
    }

    fn generate_thumbnail(
        &self,
        img: &DynamicImage,
        max_size: u32,
    ) -> Result<ProcessedImage, ImageError> {
        let (orig_width, orig_height) = (img.width(), img.height());
        let safe_f64_to_u32 =
            |v: f64| -> u32 { u32::try_from(v.round() as u64).unwrap_or(u32::MAX) };
        let (new_width, new_height) = if orig_width > orig_height {
            let ratio = f64::from(max_size) / f64::from(orig_width);
            let scaled = safe_f64_to_u32((f64::from(orig_height) * ratio).max(1.0));
            (max_size, scaled.min(max_size))
        } else {
            let ratio = f64::from(max_size) / f64::from(orig_height);
            let scaled = safe_f64_to_u32((f64::from(orig_width) * ratio).max(1.0));
            (scaled.min(max_size), max_size)
        };
        let thumb = img.resize(new_width, new_height, FilterType::Lanczos3);
        self.encode_image(&thumb)
    }

    pub fn is_supported_mime_type(mime_type: &str) -> bool {
        matches!(
            mime_type.to_lowercase().as_str(),
            "image/jpeg" | "image/jpg" | "image/png" | "image/gif" | "image/webp"
        )
    }

    pub fn strip_exif(data: &[u8]) -> Result<Vec<u8>, ImageError> {
        let format =
            image::guess_format(data).map_err(|e| ImageError::DecodeError(e.to_string()))?;
        let cursor = Cursor::new(data);
        let img = ImageReader::with_format(cursor, format)
            .decode()
            .map_err(|e| ImageError::DecodeError(e.to_string()))?;
        let mut buf = Vec::new();
        img.write_to(&mut Cursor::new(&mut buf), format)
            .map_err(|e| ImageError::EncodeError(e.to_string()))?;
        Ok(buf)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn create_test_image(width: u32, height: u32) -> Vec<u8> {
        let img = DynamicImage::new_rgb8(width, height);
        let mut buf = Vec::new();
        img.write_to(&mut Cursor::new(&mut buf), ImageFormat::Png)
            .unwrap();
        buf
    }

    #[test]
    fn test_process_small_image() {
        let processor = ImageProcessor::new();
        let data = create_test_image(100, 100);
        let result = processor.process(&data, "image/png").unwrap();
        assert!(result.thumbnail_feed.is_none());
        assert!(result.thumbnail_full.is_none());
    }

    #[test]
    fn test_process_large_image_generates_thumbnails() {
        let processor = ImageProcessor::new();
        let data = create_test_image(2000, 1500);
        let result = processor.process(&data, "image/png").unwrap();
        assert!(result.thumbnail_feed.is_some());
        assert!(result.thumbnail_full.is_some());
        let feed_thumb = result.thumbnail_feed.unwrap();
        assert!(feed_thumb.width <= THUMB_SIZE_FEED);
        assert!(feed_thumb.height <= THUMB_SIZE_FEED);
        let full_thumb = result.thumbnail_full.unwrap();
        assert!(full_thumb.width <= THUMB_SIZE_FULL);
        assert!(full_thumb.height <= THUMB_SIZE_FULL);
    }

    #[test]
    fn test_webp_conversion() {
        let processor = ImageProcessor::new().with_output_format(OutputFormat::WebP);
        let data = create_test_image(500, 500);
        let result = processor.process(&data, "image/png").unwrap();
        assert_eq!(result.original.mime_type, "image/webp");
    }

    #[test]
    fn test_reject_too_large() {
        let processor = ImageProcessor::new().with_max_dimension(1000);
        let data = create_test_image(2000, 2000);
        let result = processor.process(&data, "image/png");
        assert!(matches!(result, Err(ImageError::TooLarge { .. })));
    }

    #[test]
    fn test_is_supported_mime_type() {
        assert!(ImageProcessor::is_supported_mime_type("image/jpeg"));
        assert!(ImageProcessor::is_supported_mime_type("image/png"));
        assert!(ImageProcessor::is_supported_mime_type("image/gif"));
        assert!(ImageProcessor::is_supported_mime_type("image/webp"));
        assert!(!ImageProcessor::is_supported_mime_type("image/bmp"));
        assert!(!ImageProcessor::is_supported_mime_type("text/plain"));
    }
}
