use std::path::Path;

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

use super::MetadataConfig;

pub fn strip_metadata(
    input: &Path,
    output: &Path,
    config: &MetadataConfig,
) -> Result<()> {
    match config {
        MetadataConfig::KeepAll => {
            std::fs::copy(input, output).map_err(PixstripError::Io)?;
        }
        MetadataConfig::StripAll => {
            strip_all_exif(input, output)?;
        }
        MetadataConfig::Privacy | MetadataConfig::Custom { .. } => {
            // Copy file first, then selectively strip using little_exif
            std::fs::copy(input, output).map_err(PixstripError::Io)?;
            strip_selective_exif(output, config);
        }
    }
    Ok(())
}

fn strip_selective_exif(path: &Path, config: &MetadataConfig) {
    use little_exif::exif_tag::ExifTag;
    use little_exif::metadata::Metadata;

    let Ok(source_meta) = Metadata::new_from_path(path) else {
        return;
    };

    let mut strip_ids: Vec<u16> = Vec::new();

    if config.should_strip_gps() {
        strip_ids.push(ExifTag::GPSInfo(Vec::new()).as_u16());
    }
    if config.should_strip_camera() {
        strip_ids.push(ExifTag::Make(String::new()).as_u16());
        strip_ids.push(ExifTag::Model(String::new()).as_u16());
        strip_ids.push(ExifTag::LensModel(String::new()).as_u16());
        strip_ids.push(ExifTag::LensMake(String::new()).as_u16());
        strip_ids.push(ExifTag::SerialNumber(String::new()).as_u16());
        strip_ids.push(ExifTag::LensSerialNumber(String::new()).as_u16());
        strip_ids.push(ExifTag::LensInfo(Vec::new()).as_u16());
    }
    if config.should_strip_software() {
        strip_ids.push(ExifTag::Software(String::new()).as_u16());
        strip_ids.push(ExifTag::MakerNote(Vec::new()).as_u16());
    }
    if config.should_strip_timestamps() {
        strip_ids.push(ExifTag::ModifyDate(String::new()).as_u16());
        strip_ids.push(ExifTag::DateTimeOriginal(String::new()).as_u16());
        strip_ids.push(ExifTag::CreateDate(String::new()).as_u16());
        strip_ids.push(ExifTag::SubSecTime(String::new()).as_u16());
        strip_ids.push(ExifTag::SubSecTimeOriginal(String::new()).as_u16());
        strip_ids.push(ExifTag::SubSecTimeDigitized(String::new()).as_u16());
        strip_ids.push(ExifTag::OffsetTime(String::new()).as_u16());
        strip_ids.push(ExifTag::OffsetTimeOriginal(String::new()).as_u16());
        strip_ids.push(ExifTag::OffsetTimeDigitized(String::new()).as_u16());
    }
    if config.should_strip_copyright() {
        strip_ids.push(ExifTag::Copyright(String::new()).as_u16());
        strip_ids.push(ExifTag::Artist(String::new()).as_u16());
        strip_ids.push(ExifTag::OwnerName(String::new()).as_u16());
    }

    let mut new_meta = Metadata::new();
    for tag in source_meta.data() {
        if !strip_ids.contains(&tag.as_u16()) {
            new_meta.set_tag(tag.clone());
        }
    }

    let _ = new_meta.write_to_file(path);
}

fn strip_all_exif(input: &Path, output: &Path) -> Result<()> {
    let data = std::fs::read(input).map_err(PixstripError::Io)?;
    let cleaned = if data.len() >= 8 && &data[..8] == b"\x89PNG\r\n\x1a\n" {
        remove_metadata_from_png(&data)
    } else {
        remove_exif_from_jpeg(&data)
    };
    std::fs::write(output, cleaned).map_err(PixstripError::Io)?;
    Ok(())
}

fn remove_exif_from_jpeg(data: &[u8]) -> Vec<u8> {
    // Only process JPEG files (starts with FF D8)
    if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
        return data.to_vec();
    }

    let mut result = Vec::with_capacity(data.len());
    result.push(0xFF);
    result.push(0xD8);

    let mut i = 2;
    while i + 1 < data.len() {
        if data[i] != 0xFF {
            result.extend_from_slice(&data[i..]);
            break;
        }

        let marker = data[i + 1];

        // APP1 (0xE1) contains EXIF - skip it
        if marker == 0xE1 && i + 3 < data.len() {
            let len = ((data[i + 2] as usize) << 8) | (data[i + 3] as usize);
            let skip = 2 + len;
            if i + skip <= data.len() {
                i += skip;
            } else {
                break;
            }
            continue;
        }

        // SOS (0xDA) - start of scan, copy everything from here
        if marker == 0xDA {
            result.extend_from_slice(&data[i..]);
            break;
        }

        // Other markers - keep them
        if i + 3 < data.len() {
            let len = ((data[i + 2] as usize) << 8) | (data[i + 3] as usize);
            let end = i + 2 + len;
            if end <= data.len() {
                result.extend_from_slice(&data[i..end]);
                i = end;
            } else {
                result.extend_from_slice(&data[i..]);
                break;
            }
        } else {
            result.extend_from_slice(&data[i..]);
            break;
        }
    }

    result
}

fn remove_metadata_from_png(data: &[u8]) -> Vec<u8> {
    // PNG: 8-byte signature + chunks (4 len + 4 type + data + 4 CRC)
    // Keep only rendering-essential chunks, strip textual/EXIF metadata.
    const KEEP_CHUNKS: &[&[u8; 4]] = &[
        b"IHDR", b"PLTE", b"IDAT", b"IEND",
        b"tRNS", b"gAMA", b"cHRM", b"sRGB", b"iCCP", b"sBIT",
        b"pHYs", b"bKGD", b"hIST", b"sPLT",
        b"acTL", b"fcTL", b"fdAT",
    ];

    if data.len() < 8 {
        return data.to_vec();
    }

    let mut result = Vec::with_capacity(data.len());
    result.extend_from_slice(&data[..8]); // PNG signature

    let mut pos = 8;
    while pos + 12 <= data.len() {
        let chunk_len = u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
        let chunk_type = &data[pos + 4..pos + 8];
        let total = 12 + chunk_len; // 4 len + 4 type + data + 4 CRC

        if pos + total > data.len() {
            break;
        }

        let keep = KEEP_CHUNKS.iter().any(|k| *k == chunk_type);
        if keep {
            result.extend_from_slice(&data[pos..pos + total]);
        }

        pos += total;
    }

    result
}
