use flate2::read::ZlibDecoder;
use pdfmake_rust::pdf_writer::PdfWriter;
use pdfmake_rust::pdf_stream::flate_stream_dictionary;
use pdfmake_rust::CompressionLevel;
use pdfmake_rust::{Document, DocumentNode, PdfMake, Style};
use std::io::Read;

#[cfg(test)]
pub fn decompress_pdf_stream(pdf: &[u8]) -> Option<Vec<u8>> {
    let pdf_str = String::from_utf8_lossy(pdf);
    
    // Find /Contents reference - this points to the content stream object
    if let Some(contents_pos) = pdf_str.find("/Contents") {
        let after_contents = &pdf_str[contents_pos..];
        if let Some(r_pos) = after_contents.find('R') {
            let obj_ref = &after_contents[..r_pos];
            if let Some(obj_num) = obj_ref.split_whitespace().last() {
                let obj_ref_str = format!("{} 0 obj", obj_num);
                if let Some(obj_pos) = pdf_str.find(&obj_ref_str) {
                    let after_obj = &pdf[obj_pos..];
                    let stream_start = after_obj.windows(7).position(|w| w == b"stream\n")?;
                    let stream_data_start = stream_start + 7;
                    let endstream_pos = after_obj[stream_data_start..].windows(9).position(|w| w == b"endstream")?;
                    let compressed = &after_obj[stream_data_start..stream_data_start + endstream_pos];

                    let mut decoder = ZlibDecoder::new(compressed);
                    let mut decompressed = Vec::new();
                    decoder.read_to_end(&mut decompressed).ok()?;
                    return Some(decompressed);
                }
            }
        }
    }
    
    // Fallback: find the smallest non-font stream
    let mut smallest_stream: Option<(usize, Vec<u8>)> = None;
    let mut search_pos = 0usize;
    while let Some(pos) = pdf[search_pos..].windows(7).position(|w| w == b"stream\n") {
        let abs_pos = search_pos + pos;
        let after_stream = &pdf[abs_pos + 7..];
        if let Some(end_pos) = after_stream.windows(9).position(|w| w == b"endstream") {
            let stream_size = end_pos;
            if stream_size < 1_000_000 {
                let should_replace = match smallest_stream {
                    None => true,
                    Some((prev_size, _)) => stream_size < prev_size,
                };
                if should_replace {
                    let compressed = &after_stream[..end_pos];
                    let mut decoder = ZlibDecoder::new(compressed);
                    let mut decompressed = Vec::new();
                    if decoder.read_to_end(&mut decompressed).is_ok() && !decompressed.is_empty() {
                        smallest_stream = Some((stream_size, decompressed));
                    }
                }
            }
        }
        search_pos = abs_pos + 7;
        if search_pos >= pdf.len() {
            break;
        }
    }
    
    smallest_stream.map(|(_, d)| d)
}

#[test]
fn flate_stream_dictionary_records_compressed_length_and_filter() {
    let data = b"BT /F1 12 Tf 10 10 Td (hello hello hello) Tj ET\n";
    let (dictionary, compressed) = flate_stream_dictionary(data, CompressionLevel::Default).unwrap();
    let dictionary = String::from_utf8(dictionary).unwrap();

    assert!(dictionary.contains("/Filter /FlateDecode"));
    assert!(dictionary.contains(&format!("/Length {}", compressed.len())));
    assert_ne!(compressed, data);
}

#[test]
fn pdf_writer_preserves_binary_stream_bytes_and_xref() {
    let mut writer = PdfWriter::new();
    writer.add_object(b"<< /Type /Catalog >>".to_vec());
    writer.add_object_with_stream(b"<< /Length 4 >>".to_vec(), vec![0, b'A', 0xFF, b'Z']);

    let pdf = writer.finish(1, None, None, None);

    assert!(pdf.starts_with(b"%PDF-1.4\n"));
    assert!(pdf.windows(4).any(|window| window == [0, b'A', 0xFF, b'Z']));
    assert!(String::from_utf8_lossy(&pdf).contains("xref\n0 3"));
    assert!(String::from_utf8_lossy(&pdf).contains("/Root 1 0 R"));
}

fn cjk_font_path() -> Option<String> {
    std::env::var("PDFMAKE_RUST_TEST_CJK_FONT").ok()
}

#[test]
fn registered_font_is_not_asciihex_encoded_when_fixture_is_available() {
    let Some(path) = cjk_font_path() else { return; };
    let document = Document::builder()
        .font("cjk", path)
        .default_style(Style::default().font_family("cjk"))
        .content(DocumentNode::text("你好"))
        .build();

    let bytes = PdfMake::new().render(&document).unwrap();
    let pdf = String::from_utf8_lossy(&bytes);

    assert!(pdf.contains("/FontFile2"));
    assert!(!pdf.contains("/ASCIIHexDecode"));
    assert!(pdf.contains("/Filter /FlateDecode"));
}

use pdfmake_rust::font_subset::{FontEmbedding, FontSubsetMode};
use pdfmake_rust::GlyphMapping;
use std::collections::BTreeSet;

#[test]
fn font_embedding_falls_back_to_full_font_for_invalid_font_bytes() {
    let glyphs = BTreeSet::from([GlyphMapping { glyph_id: 1, unicode: '你' }]);
    let embedding = FontEmbedding::prepare("bad", &vec![1, 2, 3, 4], &glyphs).unwrap();

    assert_eq!(embedding.mode, FontSubsetMode::FullFontFallback);
    assert_eq!(embedding.font_data, vec![1, 2, 3, 4]);
    assert_eq!(embedding.cid_for_char('你'), Some(1));
}

#[test]
fn font_embedding_subsets_registered_font_when_fixture_is_available() {
    let Some(path) = cjk_font_path() else { return; };
    let mut sources = std::collections::BTreeMap::new();
    sources.insert("cjk".to_string(), pdfmake_rust::FontSource::path(path));
    let registry = pdfmake_rust::FontRegistry::load(&sources).unwrap();
    let loaded = registry.get("cjk").unwrap();
    let glyphs = registry.glyphs_for_text("cjk", "你好").unwrap().into_iter().collect();

    let embedding = FontEmbedding::prepare("cjk", &loaded.data, &glyphs).unwrap();

    assert_eq!(embedding.mode, FontSubsetMode::Subset);
    assert!(embedding.font_data.len() < loaded.data.len());
    assert!(embedding.cid_for_char('你').is_some());
    assert!(embedding.cid_for_char('好').is_some());
}

#[test]
fn font_embedding_widths_are_scaled_to_pdf_text_space_when_fixture_is_available() {
    let Some(path) = cjk_font_path() else { return; };
    let mut sources = std::collections::BTreeMap::new();
    sources.insert("cjk".to_string(), pdfmake_rust::FontSource::path(path));
    let registry = pdfmake_rust::FontRegistry::load(&sources).unwrap();
    let loaded = registry.get("cjk").unwrap();
    let glyphs = registry.glyphs_for_text("cjk", "你好").unwrap().into_iter().collect();

    let embedding = FontEmbedding::prepare("cjk", &loaded.data, &glyphs).unwrap();

    assert_eq!(embedding.mode, FontSubsetMode::Subset);
    let first_glyph = embedding.glyphs.first().unwrap();
    let subset_face = ttf_parser::Face::parse(&embedding.font_data, 0).unwrap();
    let expected_width = ((subset_face.glyph_hor_advance(ttf_parser::GlyphId(first_glyph.embedded_cid)).unwrap() as f32)
        * 1000.0
        / subset_face.units_per_em() as f32)
        .round() as u16;

    assert_eq!(first_glyph.width, expected_width);
    assert!(first_glyph.width < subset_face.glyph_hor_advance(ttf_parser::GlyphId(first_glyph.embedded_cid)).unwrap());
}

#[test]
fn rendered_cjk_pdf_is_smaller_than_original_font_when_fixture_is_available() {
    let Some(path) = cjk_font_path() else { return; };
    let original_font_size = std::fs::metadata(&path).unwrap().len() as usize;
    let document = Document::builder()
        .font("cjk", path)
        .default_style(Style::default().font_family("cjk"))
        .content(DocumentNode::text("你好，PDF体积优化"))
        .build();

    let bytes = PdfMake::new().render(&document).unwrap();

    assert!(bytes.len() < original_font_size, 
        "pdf size {} should be below original font size {}", 
        bytes.len(), original_font_size);
}
