use flate2::read::ZlibDecoder;
use pdfmake_rust::{Document, DocumentNode, InlineFragment, Style, TableCell, TableNode, TableWidth, TextNode};
use pdfmake_rust::{FontRegistry, FontSource, GlyphMapping, PdfMakeError};
use pdfmake_rust::build_to_unicode_cmap;
use pdfmake_rust::PdfMake;
use std::collections::{BTreeMap, BTreeSet};
use std::io::Read;

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") {
        // Parse the object number after /Contents
        // Format: /Contents N 0 R
        let after_contents = &pdf_str[contents_pos..];
        if let Some(r_pos) = after_contents.find('R') {
            let obj_ref = &after_contents[..r_pos];
            // Extract object number
            if let Some(obj_num) = obj_ref.split_whitespace().last() {
                let obj_ref_str = format!("{} 0 obj", obj_num);
                // Find this object in the PDF
                if let Some(obj_pos) = pdf_str.find(&obj_ref_str) {
                    // Find stream after this object
                    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;
            // Skip very large streams (> 1MB) as they're likely font data
            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 document_builder_registers_font_family_path() {
    let document = Document::builder()
        .font("noto_sc", "fonts/NotoSansSC-Regular.otf")
        .content(DocumentNode::text("你好"))
        .build();

    assert_eq!(document.fonts["noto_sc"].path, "fonts/NotoSansSC-Regular.otf");
}

#[test]
fn style_builder_sets_font_family() {
    let style = Style::new().font_family("noto_sc");
    assert_eq!(style.font_family.as_deref(), Some("noto_sc"));
}

#[test]
fn font_registry_reports_missing_font_file() {
    let mut fonts = BTreeMap::new();
    fonts.insert("missing".to_string(), FontSource::path("fonts/does-not-exist.ttf"));
    let error = FontRegistry::load(&fonts).unwrap_err();
    match error {
        PdfMakeError::FontRead { family, path, .. } => {
            assert_eq!(family, "missing");
            assert_eq!(path, "fonts/does-not-exist.ttf");
        }
        other => panic!("expected FontRead, got {other:?}"),
    }
}

#[test]
fn empty_font_registry_does_not_contain_families() {
    let registry = FontRegistry::empty();
    assert!(!registry.contains("noto_sc"));
}

#[test]
fn to_unicode_cmap_maps_glyph_ids_to_unicode() {
    let glyphs = BTreeSet::from([
        GlyphMapping { glyph_id: 1, unicode: '你' },
        GlyphMapping { glyph_id: 2, unicode: '好' },
    ]);
    let cmap = build_to_unicode_cmap("noto_sc", &glyphs);
    assert!(cmap.contains("/CMapType 2 def"));
    assert!(cmap.contains("<0001> <4F60>"));
    assert!(cmap.contains("<0002> <597D>"));
}

#[test]
fn render_errors_when_selected_font_file_is_missing() {
    let document = Document::builder()
        .font("missing", "fonts/does-not-exist.ttf")
        .default_style(Style::default().font_family("missing"))
        .content(DocumentNode::text("你好"))
        .build();

    let error = PdfMake::new().render(&document).unwrap_err();
    match error {
        PdfMakeError::FontRead { family, .. } => assert_eq!(family, "missing"),
        other => panic!("expected FontRead, got {other:?}"),
    }
}

#[test]
fn render_errors_when_font_family_is_not_registered() {
    let document = Document::builder()
        .default_style(Style::default().font_family("missing"))
        .content(DocumentNode::text("你好"))
        .build();

    let error = PdfMake::new().render(&document).unwrap_err();
    match error {
        PdfMakeError::UnknownFontFamily(family) => assert_eq!(family, "missing"),
        other => panic!("expected UnknownFontFamily, got {other:?}"),
    }
}

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

#[test]
fn render_chinese_text_embeds_type0_font_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("/Subtype /Type0"));
    assert!(pdf.contains("/Encoding /Identity-H"));
    assert!(pdf.contains("/ToUnicode"));
    assert!(pdf.contains("/FontFile2"));
    assert!(pdf.contains("> Tj"));
}

#[test]
fn render_chinese_table_cell_uses_registered_font_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::Table(
            TableNode::new(vec![vec![TableCell::text("中文表格")]])
                .widths(vec![TableWidth::Fixed(120.0)]),
        ))
        .build();

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

    assert!(pdf.contains("/Subtype /Type0"));
    assert!(pdf.contains("/ToUnicode"));

    let decompressed = decompress_pdf_stream(&bytes).unwrap_or_default();
    let content = String::from_utf8_lossy(&decompressed);
    assert!(content.contains(" l S\n"));
}

#[test]
fn render_chinese_inline_runs_keep_registered_font_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(TextNode::with_inlines(vec![
            InlineFragment::new("普通"),
            InlineFragment::new("加粗").style(Style::new().bold(true)),
        ])))
        .build();

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

    assert!(pdf.contains("/Subtype /Type0"));
    assert!(pdf.contains("/ToUnicode"));

    let decompressed = decompress_pdf_stream(&bytes).unwrap_or_default();
    let content = String::from_utf8_lossy(&decompressed);
    assert!(content.contains("> Tj"));
}