#![cfg(feature = "image")]

use base64::Engine;
use pdfmake_rust::{Document, DocumentNode, Fit, FontRegistry, ImageNode, ImageSource};

#[test]
fn document_accepts_image_node_with_path_and_width() {
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::path("tests/fixtures/sample.png").width(120.0)))
        .build();

    match &document.content[0] {
        DocumentNode::Image(image) => {
            assert_eq!(image.source, ImageSource::Path("tests/fixtures/sample.png".to_string()));
            assert_eq!(image.width, Some(120.0));
            assert_eq!(image.height, None);
            assert_eq!(image.fit, None);
        }
        other => panic!("expected image node, got {other:?}"),
    }
}

#[test]
fn image_node_accepts_fit_box() {
    let image = ImageNode::path("tests/fixtures/sample.jpg").fit(200.0, 100.0);
    assert_eq!(image.fit, Some(Fit { width: 200.0, height: 100.0 }));
}

use pdfmake_rust::{normalize_document, Alignment};

#[test]
fn normalize_preserves_image_block_fields() {
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::path("tests/fixtures/sample.png").width(120.0).alignment(Alignment::Center)))
        .build();

    let normalized = normalize_document(&document).unwrap();
    match &normalized.blocks[0] {
        pdfmake_rust::NormalizedBlock::Image(image) => {
            assert_eq!(image.width, Some(120.0));
            assert_eq!(image.alignment, Alignment::Center);
        }
        other => panic!("expected normalized image, got {other:?}"),
    }
}

use pdfmake_rust::{measure_document, PdfMakeError};

#[test]
fn measure_image_resolves_width_from_explicit_width_and_intrinsic_ratio() {
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::bytes(minimal_png_2x1()).width(100.0)))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    match &measured.blocks[0] {
        pdfmake_rust::MeasuredBlock::Image(image) => {
            assert_eq!(image.width, 100.0);
            assert_eq!(image.height, 50.0);
            assert_eq!(image.intrinsic_width, 2);
            assert_eq!(image.intrinsic_height, 1);
        }
        other => panic!("expected measured image, got {other:?}"),
    }
}

#[test]
fn measure_image_reports_unsupported_format() {
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::bytes(vec![1, 2, 3])))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let error = measure_document(&normalized, &FontRegistry::empty()).unwrap_err();
    match error {
        PdfMakeError::UnsupportedImageFormat => {}
        other => panic!("expected unsupported image format, got {other:?}"),
    }
}

fn minimal_png_2x1() -> Vec<u8> {
    vec![
        137, 80, 78, 71, 13, 10, 26, 10,
        0, 0, 0, 13, 73, 72, 68, 82,
        0, 0, 0, 2, 0, 0, 0, 1,
        8, 2, 0, 0, 0, 0, 0, 0, 0,
    ]
}

use pdfmake_rust::{layout_document, PdfMake};

#[test]
fn layout_image_uses_alignment_and_advances_y() {
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::bytes(minimal_png_2x1()).width(100.0).alignment(Alignment::Center)))
        .build();
    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    match &layout.pages[0].items[0] {
        pdfmake_rust::LayoutItem::Image(image) => {
            assert_eq!(image.width, 100.0);
            assert_eq!(image.height, 50.0);
            assert!(image.x > layout.page_margins.left);
        }
        other => panic!("expected layout image, got {other:?}"),
    }
}

#[test]
fn render_image_document_embeds_image_xobject() {
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::bytes(minimal_jpeg_1x1()).width(24.0)))
        .build();

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

    assert!(text.contains("/XObject"));
    assert!(text.contains("/Subtype /Image"));
    assert!(text.contains("/DCTDecode"));
    assert!(text.contains("/Im1"));
}

fn minimal_jpeg_1x1() -> Vec<u8> {
    vec![
        0xFF, 0xD8,
        0xFF, 0xC0, 0x00, 0x0B, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01,
        0xFF, 0xD9,
    ]
}

#[test]
fn image_node_accepts_size_constraints() {
    let img = ImageNode::path("test.png")
        .max_width(200.0)
        .max_height(100.0)
        .min_width(50.0)
        .min_height(30.0);
    assert_eq!(img.max_width, Some(200.0));
    assert_eq!(img.max_height, Some(100.0));
    assert_eq!(img.min_width, Some(50.0));
    assert_eq!(img.min_height, Some(30.0));
}

use pdfmake_rust::{ImageCover, VerticalAlignment};

#[test]
fn image_cover_renders() {
    let cover = ImageCover::new(100.0, 80.0)
        .align(Alignment::Center)
        .valign(VerticalAlignment::Middle);

    let document = Document::builder()
        .content(DocumentNode::image(
            ImageNode::bytes(minimal_png_2x1()).cover(cover),
        ))
        .build();

    let pdf = PdfMake::new().render(&document).unwrap();
    let text = String::from_utf8_lossy(&pdf);
    assert!(text.contains("/XObject"));
    assert!(text.contains("/Subtype /Image"));
}

#[test]
fn image_cover_sets_display_dimensions() {
    let cover = ImageCover::new(200.0, 150.0);

    let document = Document::builder()
        .content(DocumentNode::image(
            ImageNode::bytes(minimal_png_2x1()).cover(cover),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    match &measured.blocks[0] {
        pdfmake_rust::MeasuredBlock::Image(image) => {
            assert_eq!(image.width, 200.0);
            assert_eq!(image.height, 150.0);
            assert!(image.cover.is_some());
        }
        other => panic!("expected measured image, got {other:?}"),
    }
}

#[test]
fn image_cover_propagates_to_layout() {
    let cover = ImageCover::new(100.0, 100.0);

    let document = Document::builder()
        .content(DocumentNode::image(
            ImageNode::bytes(minimal_png_2x1()).cover(cover),
        ))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    let layout = layout_document(&measured).unwrap();

    match &layout.pages[0].items[0] {
        pdfmake_rust::LayoutItem::Image(image) => {
            assert!(image.cover.is_some());
            let c = image.cover.unwrap();
            assert_eq!(c.width, 100.0);
            assert_eq!(c.height, 100.0);
        }
        other => panic!("expected layout image, got {other:?}"),
    }
}

#[test]
fn image_opacity_renders() {
    let doc = Document::builder()
        .content(DocumentNode::image(
            ImageNode::bytes(minimal_jpeg_1x1())
                .width(100.0)
                .opacity(0.5)
        ))
        .build();
    let pdf = PdfMake::new().render(&doc).unwrap();
    let text = String::from_utf8_lossy(&pdf);
    assert!(pdf.len() > 100);
    assert!(text.contains("/ExtGState"));
    assert!(text.contains("/gs50"));
}

#[test]
fn image_no_opacity_has_no_extgstate() {
    let doc = Document::builder()
        .content(DocumentNode::image(
            ImageNode::bytes(minimal_jpeg_1x1())
                .width(100.0)
        ))
        .build();
    let pdf = PdfMake::new().render(&doc).unwrap();
    let text = String::from_utf8_lossy(&pdf);
    assert!(!text.contains("/ExtGState"));
}

#[test]
fn image_full_opacity_has_no_extgstate() {
    let doc = Document::builder()
        .content(DocumentNode::image(
            ImageNode::bytes(minimal_jpeg_1x1())
                .width(100.0)
                .opacity(1.0)
        ))
        .build();
    let pdf = PdfMake::new().render(&doc).unwrap();
    let text = String::from_utf8_lossy(&pdf);
    assert!(!text.contains("/ExtGState"));
}

#[test]
fn exif_orientation6_swaps_dimensions() {
    let jpeg = jpeg_with_exif_orientation_be(6);
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::bytes(jpeg)))
        .build();
    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    match &measured.blocks[0] {
        pdfmake_rust::MeasuredBlock::Image(image) => {
            assert_eq!(image.intrinsic_width, 1);
            assert_eq!(image.intrinsic_height, 2);
        }
        other => panic!("expected measured image, got {other:?}"),
    }
}

#[test]
fn exif_orientation1_preserves_dimensions() {
    let jpeg = jpeg_with_exif_orientation_le(1);
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::bytes(jpeg)))
        .build();
    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    match &measured.blocks[0] {
        pdfmake_rust::MeasuredBlock::Image(image) => {
            assert_eq!(image.intrinsic_width, 2);
            assert_eq!(image.intrinsic_height, 1);
        }
        other => panic!("expected measured image, got {other:?}"),
    }
}

#[test]
fn jpeg_without_exif_preserves_dimensions() {
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::bytes(minimal_jpeg_1x1())))
        .build();
    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();
    match &measured.blocks[0] {
        pdfmake_rust::MeasuredBlock::Image(image) => {
            assert_eq!(image.intrinsic_width, 1);
            assert_eq!(image.intrinsic_height, 1);
        }
        other => panic!("expected measured image, got {other:?}"),
    }
}

fn make_exif_app1_be(orientation: u16) -> Vec<u8> {
    let mut exif = vec![
        0xFF, 0xE1,
        0x00, 0x1E,
        b'E', b'x', b'i', b'f', 0x00, 0x00,
        0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08,
        0x00, 0x01,
        0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
        (orientation >> 8) as u8, orientation as u8, 0x00, 0x00,
    ];
    exif.extend_from_slice(&[
        0xFF, 0xC0, 0x00, 0x0B, 0x08, 0x00, 0x01, 0x00, 0x02, 0x01, 0x01,
    ]);
    exif.push(0xFF);
    exif.push(0xD9);
    exif
}

fn make_exif_app1_le(orientation: u16) -> Vec<u8> {
    let mut exif = vec![
        0xFF, 0xE1,
        0x00, 0x1E,
        b'E', b'x', b'i', b'f', 0x00, 0x00,
        0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00,
        0x01, 0x00,
        0x12, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
        orientation as u8, (orientation >> 8) as u8, 0x00, 0x00,
    ];
    exif.extend_from_slice(&[
        0xFF, 0xC0, 0x00, 0x0B, 0x08, 0x00, 0x01, 0x00, 0x02, 0x01, 0x01,
    ]);
    exif.push(0xFF);
    exif.push(0xD9);
    exif
}

fn jpeg_with_exif_orientation_be(orientation: u16) -> Vec<u8> {
    let mut jpeg = vec![0xFF, 0xD8];
    jpeg.extend_from_slice(&make_exif_app1_be(orientation));
    jpeg
}

fn jpeg_with_exif_orientation_le(orientation: u16) -> Vec<u8> {
    let mut jpeg = vec![0xFF, 0xD8];
    jpeg.extend_from_slice(&make_exif_app1_le(orientation));
    jpeg
}

#[test]
fn exif_tag274_with_wrong_type_ignored() {
    let mut jpeg = vec![0xFF, 0xD8];
    // EXIF with orientation tag but type=4 (LONG) instead of type=3 (SHORT)
    jpeg.extend_from_slice(&[
        0xFF, 0xE1,
        0x00, 0x1E,
        b'E', b'x', b'i', b'f', 0x00, 0x00,
        0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08,
        0x00, 0x01,
        0x01, 0x12, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01,
        0x00, 0x00, 0x00, 0x06,
    ]);
    // Add SOF0 so JPEG is valid (2x1)
    jpeg.extend_from_slice(&[
        0xFF, 0xC0, 0x00, 0x0B, 0x08, 0x00, 0x01, 0x00, 0x02, 0x01, 0x01,
    ]);
    jpeg.push(0xFF);
    jpeg.push(0xD9);

    let orientation = pdfmake_rust::image::get_exif_orientation(&jpeg);
    assert_eq!(orientation, None);
}

fn minimal_png_base64() -> String {
    base64::engine::general_purpose::STANDARD.encode(minimal_png_2x1())
}

#[test]
fn base64_raw_string_decodes_and_measures() {
    let b64 = minimal_png_base64();
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::base64(&b64).width(100.0)))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    match &measured.blocks[0] {
        pdfmake_rust::MeasuredBlock::Image(image) => {
            assert_eq!(image.width, 100.0);
            assert_eq!(image.height, 50.0);
            assert_eq!(image.intrinsic_width, 2);
            assert_eq!(image.intrinsic_height, 1);
        }
        other => panic!("expected measured image, got {other:?}"),
    }
}

#[test]
fn base64_data_uri_with_prefix_decodes() {
    let b64 = minimal_png_base64();
    let data_uri = format!("data:image/png;base64,{b64}");
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::base64(data_uri).width(100.0)))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let measured = measure_document(&normalized, &FontRegistry::empty()).unwrap();

    match &measured.blocks[0] {
        pdfmake_rust::MeasuredBlock::Image(image) => {
            assert_eq!(image.width, 100.0);
            assert_eq!(image.height, 50.0);
        }
        other => panic!("expected measured image, got {other:?}"),
    }
}

#[test]
fn base64_invalid_returns_error() {
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::base64("not-valid-base64!!!")))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let error = measure_document(&normalized, &FontRegistry::empty()).unwrap_err();
    match error {
        PdfMakeError::InvalidBase64(_) => {}
        other => panic!("expected InvalidBase64 error, got {other:?}"),
    }
}

#[test]
fn base64_data_uri_without_base64_marker_returns_error() {
    let b64 = minimal_png_base64();
    let data_uri = format!("data:image/png;charset=utf-8,{b64}");
    let document = Document::builder()
        .content(DocumentNode::image(ImageNode::base64(data_uri)))
        .build();

    let normalized = normalize_document(&document).unwrap();
    let error = measure_document(&normalized, &FontRegistry::empty()).unwrap_err();
    match error {
        PdfMakeError::InvalidBase64(msg) => {
            assert!(msg.contains("missing base64 marker"), "unexpected message: {msg}");
        }
        other => panic!("expected InvalidBase64 error, got {other:?}"),
    }
}