mod common;

use common::image_compare;
use common::labelary_client;
use common::render_helpers;

/// Maximum allowed pixel-difference percentage for carrier label tests.
const LABEL_TOLERANCE: f64 = 15.0;
/// Tolerance for unit/synthetic tests — compared against Labelary reference at 813×1626.
const UNIT_TOLERANCE: f64 = 8.0;

fn testdata_dir() -> std::path::PathBuf {
    render_helpers::testdata_dir()
}

/// Target canvas size — matches `default_options()` renderer output (813×1626 px).
const CANVAS_W: u32 = 813;
const CANVAS_H: u32 = 1626;

/// Auto-generate a missing golden reference PNG for a ZPL test.
///
/// Fetches from the Labelary API using `default_options()` dimensions (101.625 mm ×
/// 203.25 mm). If Labelary returns a PNG at a different size (e.g. 812×1624 due to
/// server-side floating-point rounding) it is padded to 813×1626 with white so the
/// reference always matches the canvas our renderer produces. Falls back to the
/// renderer when Labelary is unreachable (offline / CI without network).
fn auto_bootstrap_zpl(content: &str, path: &std::path::Path, name: &str) {
    let opts = render_helpers::default_options();
    let width_in = opts.label_width_mm / 25.4;
    let height_in = opts.label_height_mm / 25.4;

    let png = if let Some(fetched) =
        labelary_client::labelary_render(content, opts.dpmm as u8, width_in, height_in)
    {
        let normalized = labelary_client::pad_png_to_size(&fetched, CANVAS_W, CANVAS_H);
        eprintln!(
            "[bootstrap] '{}': fetched from Labelary, normalized to {}×{}",
            name, CANVAS_W, CANVAS_H
        );
        normalized
    } else {
        eprintln!(
            "[bootstrap] '{}': Labelary unavailable — using renderer baseline ({}×{})",
            name, CANVAS_W, CANVAS_H
        );
        render_helpers::render_zpl_to_png(content, opts)
    };

    std::fs::create_dir_all(path.parent().unwrap()).ok();
    std::fs::write(path, &png).expect("write auto-generated golden PNG");
}

/// Auto-generate a missing golden reference PNG for an EPL test.
///
/// EPL is not supported by the Labelary API, so the renderer baseline is always used.
fn auto_bootstrap_epl(content: &str, path: &std::path::Path, name: &str) {
    eprintln!(
        "[bootstrap] '{}': using renderer baseline for EPL (813×1626)",
        name
    );
    let opts = render_helpers::default_options();
    let png = render_helpers::render_epl_to_png(content, opts);
    std::fs::create_dir_all(path.parent().unwrap()).ok();
    std::fs::write(path, &png).expect("write auto-generated golden PNG");
}

/// Run a golden-file comparison for a ZPL test case.
fn golden_zpl(name: &str) {
    golden_zpl_with_tolerance(name, LABEL_TOLERANCE);
}

fn golden_zpl_with_tolerance(name: &str, tolerance: f64) {
    let dir = testdata_dir();
    // Try labels/ first, then unit/, then root
    let (input, is_unit) = if dir.join("labels").join(format!("{}.zpl", name)).exists() {
        (dir.join("labels").join(format!("{}.zpl", name)), false)
    } else if dir.join("unit").join(format!("{}.zpl", name)).exists() {
        (dir.join("unit").join(format!("{}.zpl", name)), true)
    } else {
        (dir.join(format!("{}.zpl", name)), false)
    };
    let expected = input.with_extension("png");

    if !input.exists() {
        eprintln!("SKIP {}: missing ZPL input", name);
        return;
    }

    // Auto-generate the reference PNG if it doesn't exist yet.
    if !expected.exists() {
        let content = std::fs::read_to_string(&input).expect("read input");
        auto_bootstrap_zpl(&content, &expected, name);
    }

    let options = render_helpers::default_options();
    let effective_tolerance = if is_unit { UNIT_TOLERANCE } else { tolerance };
    let content = std::fs::read_to_string(&input).expect("read input");
    let actual_png = render_helpers::render_zpl_to_png(&content, options);
    let expected_png = std::fs::read(&expected).expect("read golden");
    let result = image_compare::compare_images(&actual_png, &expected_png, effective_tolerance);

    if result.diff_percent > effective_tolerance {
        if let Some(ref diff_img) = result.diff_image {
            image_compare::save_diff_image(name, diff_img);
        }
    }

    // Optionally update golden file
    if std::env::var("LABELIZE_UPDATE_GOLDEN").is_ok() && result.diff_percent > 0.0 {
        std::fs::write(&expected, &actual_png).expect("update golden file");
        return;
    }

    assert!(
        result.diff_percent <= effective_tolerance,
        "ZPL golden test '{}' FAILED: {:.2}% pixel diff (tolerance: {:.2}%), dims: actual={:?}, expected={:?}",
        name,
        result.diff_percent,
        tolerance,
        result.actual_dims,
        result.expected_dims,
    );
}

/// Run a golden-file comparison for an EPL test case.
fn golden_epl(name: &str) {
    golden_epl_with_tolerance(name, LABEL_TOLERANCE);
}

fn golden_epl_with_tolerance(name: &str, tolerance: f64) {
    let dir = testdata_dir();
    let input = dir.join(format!("{}.epl", name));
    let expected = dir.join(format!("{}.png", name));

    if !input.exists() {
        eprintln!("SKIP {}: missing EPL input", name);
        return;
    }

    // Auto-generate the reference PNG if it doesn't exist yet.
    if !expected.exists() {
        let content = std::fs::read_to_string(&input).expect("read input");
        auto_bootstrap_epl(&content, &expected, name);
    }

    let content = std::fs::read_to_string(&input).expect("read input");
    let actual_png = render_helpers::render_epl_to_png(&content, render_helpers::default_options());
    let expected_png = std::fs::read(&expected).expect("read golden");
    let result = image_compare::compare_images(&actual_png, &expected_png, tolerance);

    if result.diff_percent > tolerance {
        if let Some(ref diff_img) = result.diff_image {
            image_compare::save_diff_image(name, diff_img);
        }
    }

    if std::env::var("LABELIZE_UPDATE_GOLDEN").is_ok() && result.diff_percent > 0.0 {
        std::fs::write(&expected, &actual_png).expect("update golden file");
        return;
    }

    assert!(
        result.diff_percent <= tolerance,
        "EPL golden test '{}' FAILED: {:.2}% pixel diff (tolerance: {:.2}%), dims: actual={:?}, expected={:?}",
        name,
        result.diff_percent,
        tolerance,
        result.actual_dims,
        result.expected_dims,
    );
}

// ── ZPL golden tests ──────────────────────────────────────────────
// Tolerances are per-label ceilings (current diff + headroom).
// See docs/DIFF_THRESHOLDS.md for rationale.

#[test]
fn golden_amazon() {
    golden_zpl_with_tolerance("amazon", 3.5);
}
#[test]
fn golden_aztec_ec() {
    golden_zpl_with_tolerance("aztec_ec", 7.5);
}
#[test]
fn golden_barcode128_default_width() {
    golden_zpl_with_tolerance("barcode128_default_width", 2.0);
}
#[test]
fn golden_barcode128_line() {
    golden_zpl_with_tolerance("barcode128_line", 2.0);
}
#[test]
fn golden_barcode128_line_above() {
    golden_zpl_with_tolerance("barcode128_line_above", 2.0);
}
#[test]
fn golden_barcode128_mode_a() {
    golden_zpl_with_tolerance("barcode128_mode_a", 2.0);
}
#[test]
fn golden_barcode128_mode_d() {
    golden_zpl_with_tolerance("barcode128_mode_d", 2.0);
}
#[test]
fn golden_barcode128_mode_n() {
    golden_zpl_with_tolerance("barcode128_mode_n", 2.0);
}
#[test]
fn golden_barcode128_mode_n_cba_sets() {
    golden_zpl_with_tolerance("barcode128_mode_n_cba_sets", 2.0);
}
#[test]
fn golden_barcode128_mode_u() {
    golden_zpl_with_tolerance("barcode128_mode_u", 2.0);
}
#[test]
fn golden_barcode128_rotated() {
    golden_zpl_with_tolerance("barcode128_rotated", 2.0);
}
#[test]
fn golden_bstc() {
    golden_zpl_with_tolerance("bstc", 1.0);
}
#[test]
fn golden_dbs() {
    golden_zpl_with_tolerance("dbs", 5.0);
}
#[test]
fn golden_dhlecommercetr() {
    golden_zpl_with_tolerance("dhlecommercetr", 4.5);
}
#[test]
fn golden_dhlpaket() {
    golden_zpl_with_tolerance("dhlpaket", 3.5);
}
#[test]
fn golden_dhlparceluk() {
    golden_zpl_with_tolerance("dhlparceluk", 5.5);
}
#[test]
fn golden_dpdpl() {
    golden_zpl_with_tolerance("dpdpl", 7.5);
}
#[test]
fn golden_ean13() {
    golden_zpl_with_tolerance("ean13", 3.0);
}
#[test]
fn golden_cp850_hex_chars() {
    golden_zpl("cp850_hex_chars");
}
#[test]
fn golden_encodings_013() {
    golden_zpl_with_tolerance("encodings_013", 2.5);
}
#[test]
fn golden_fedex() {
    golden_zpl_with_tolerance("fedex", 7.0);
}
#[test]
fn golden_font_p() {
    golden_zpl("font_p");
}
#[test]
fn golden_font_q() {
    golden_zpl("font_q");
}
#[test]
fn golden_font_s() {
    golden_zpl("font_s");
}
#[test]
fn golden_gd_thin_r() {
    golden_zpl_with_tolerance("gd_thin_r", 1.0);
}
#[test]
fn golden_gd_thin_l() {
    golden_zpl_with_tolerance("gd_thin_l", 1.0);
}
#[test]
fn golden_gd_thick() {
    golden_zpl_with_tolerance("gd_thick", 1.0);
}
#[test]
fn golden_gd_default_params() {
    golden_zpl_with_tolerance("gd_default_params", 1.0);
}
#[test]
fn golden_gb_0_height() {
    golden_zpl_with_tolerance("gb_0_height", 1.0);
}
#[test]
fn golden_gb_0_width() {
    golden_zpl_with_tolerance("gb_0_width", 1.0);
}
#[test]
fn golden_gb_normal() {
    golden_zpl_with_tolerance("gb_normal", 1.0);
}
#[test]
fn golden_gb_rounded() {
    golden_zpl_with_tolerance("gb_rounded", 1.0);
}
#[test]
fn golden_glscz() {
    golden_zpl_with_tolerance("glscz", 3.5);
}
#[test]
fn golden_glsdk_return() {
    golden_zpl_with_tolerance("glsdk_return", 5.5);
}
#[test]
fn golden_gs() {
    golden_zpl_with_tolerance("gs", 2.0);
}
#[test]
fn golden_icapaket() {
    golden_zpl_with_tolerance("icapaket", 5.5);
}
#[test]
fn golden_jcpenney() {
    golden_zpl_with_tolerance("jcpenney", 6.0);
}
#[test]
fn golden_kmart() {
    golden_zpl_with_tolerance("kmart", 8.0);
}
#[test]
fn golden_labelary() {
    golden_zpl_with_tolerance("labelary", 4.5);
}
#[test]
fn golden_pnldpd() {
    golden_zpl_with_tolerance("pnldpd", 11.5);
}
#[test]
fn golden_pocztex() {
    golden_zpl_with_tolerance("pocztex", 4.5);
}
#[test]
fn golden_porterbuddy() {
    golden_zpl_with_tolerance("porterbuddy", 7.0);
}
#[test]
fn golden_posten() {
    golden_zpl_with_tolerance("posten", 3.0);
}
#[test]
fn golden_qr_code_ft_manual() {
    golden_zpl_with_tolerance("qr_code_ft_manual", 1.0);
}
#[test]
fn golden_qr_code_offset() {
    golden_zpl_with_tolerance("qr_code_offset", 1.0);
}
#[test]
fn golden_return_qrcode() {
    golden_zpl_with_tolerance("return_qrcode", 4.0);
}
#[test]
fn golden_reverse_qr() {
    golden_zpl_with_tolerance("reverse_qr", 1.5);
}
#[test]
fn golden_reverse() {
    golden_zpl_with_tolerance("reverse", 1.5);
}
#[test]
fn golden_swisspost() {
    golden_zpl_with_tolerance("swisspost", 2.5);
}
#[test]
fn golden_templating() {
    golden_zpl_with_tolerance("templating", 2.5);
}
#[test]
fn golden_text_fallback_default() {
    golden_zpl_with_tolerance("text_fallback_default", 5.0);
}
#[test]
fn golden_text_fo_b() {
    golden_zpl_with_tolerance("text_fo_b", 1.0);
}
#[test]
fn golden_text_fo_i() {
    golden_zpl_with_tolerance("text_fo_i", 1.0);
}
#[test]
fn golden_text_fo_n() {
    golden_zpl_with_tolerance("text_fo_n", 1.0);
}
#[test]
fn golden_text_fo_r() {
    golden_zpl_with_tolerance("text_fo_r", 1.0);
}
#[test]
fn golden_text_ft_auto_pos() {
    golden_zpl_with_tolerance("text_ft_auto_pos", 2.5);
}
#[test]
fn golden_text_ft_b() {
    golden_zpl_with_tolerance("text_ft_b", 1.0);
}
#[test]
fn golden_text_ft_i() {
    golden_zpl_with_tolerance("text_ft_i", 1.0);
}
#[test]
fn golden_text_ft_n() {
    golden_zpl_with_tolerance("text_ft_n", 1.0);
}
#[test]
fn golden_text_ft_r() {
    golden_zpl_with_tolerance("text_ft_r", 1.0);
}
#[test]
fn golden_text_multiline() {
    golden_zpl_with_tolerance("text_multiline", 1.5);
}
#[test]
fn golden_ups_surepost() {
    golden_zpl_with_tolerance("ups_surepost", 10.0);
}
#[test]
fn golden_ups() {
    golden_zpl_with_tolerance("ups", 8.0);
}
#[test]
fn golden_usps() {
    golden_zpl_with_tolerance("usps", 5.0);
}

// ── New Carrier Labels (March 2026) ────────────────────────────────

#[test]
fn golden_tnt_express() {
    golden_zpl_with_tolerance("tnt_express", 5.0);
}
#[test]
fn golden_royalmail() {
    golden_zpl_with_tolerance("royalmail", 4.5);
}
#[test]
fn golden_canadapost() {
    golden_zpl_with_tolerance("canadapost", 5.0);
}
#[test]
fn golden_auspost() {
    golden_zpl_with_tolerance("auspost", 5.0);
}
#[test]
fn golden_colissimo() {
    golden_zpl_with_tolerance("colissimo", 4.5);
}
#[test]
fn golden_postnl() {
    golden_zpl_with_tolerance("postnl", 5.0);
}
#[test]
fn golden_bpost() {
    golden_zpl_with_tolerance("bpost", 4.5);
}
#[test]
fn golden_correos() {
    golden_zpl_with_tolerance("correos", 5.0);
}
#[test]
fn golden_dbschenker() {
    golden_zpl_with_tolerance("dbschenker", 5.5);
}
#[test]
fn golden_evri() {
    golden_zpl_with_tolerance("evri", 4.5);
}
#[test]
fn golden_dpdde() {
    golden_zpl_with_tolerance("dpdde", 4.5);
}
#[test]
fn golden_ontrac() {
    golden_zpl_with_tolerance("ontrac", 4.5);
}
#[test]
fn golden_seur() {
    golden_zpl_with_tolerance("seur", 4.5);
}
#[test]
fn golden_purolator() {
    golden_zpl_with_tolerance("purolator", 4.0);
}
#[test]
fn golden_inpost() {
    golden_zpl_with_tolerance("inpost", 5.5);
}
#[test]
fn golden_yodel() {
    golden_zpl_with_tolerance("yodel", 4.5);
}
#[test]
fn golden_pdf417_basic() {
    golden_zpl_with_tolerance("pdf417_basic", 1.0);
}

// ── Italian carrier golden tests ─────────────────────────────────
// Anonymized real-world labels from Italian e-commerce shipping.
// Labelary API used as reference renderer for expected PNGs.

#[test]
fn golden_dhlparcelit() {
    // DHL Parcel Italy: ^A0I dominant, ~DG/^XG stored graphics (DHL logo),
    // Code128 barcodes, ^FH hex encoding
    golden_zpl_with_tolerance("dhlparcelit", 7.0);
}

#[test]
fn golden_brtit() {
    // BRT (Bartolini) Italy: ^POI orientation, ~DG000.GRF logo,
    // ^A0B rotated text, ^FR reverse video, Code128
    golden_zpl_with_tolerance("brtit", 3.0);
}

#[test]
fn golden_posteit() {
    // Poste Italiane: DataMatrix ^BX, ^GFA Z64 compressed logo,
    // ^BCB bottom-up barcode, ^FH hex in all fields
    golden_zpl_with_tolerance("posteit", 7.5);
}

#[test]
fn golden_amazonshipping() {
    // Amazon Shipping (MXP5): ^BXN/B/I/R DataMatrix in all 4 orientations,
    // ^FR field reverse, ^GFA inline graphics, ^FH hex in all fields
    golden_zpl_with_tolerance("amazonshipping", 4.0);
}

// ── Additional unit golden tests ──────────────────────────────────

#[test]
fn golden_ups_maxicode() {
    golden_zpl_with_tolerance("ups_maxicode", 5.0);
}
#[test]
fn golden_aztec_ec_1_ec23() {
    golden_zpl_with_tolerance("aztec_ec_1_ec23", 7.5);
}
#[test]
fn golden_aztec_ec_2_ec45() {
    golden_zpl_with_tolerance("aztec_ec_2_ec45", 7.5);
}
#[test]
fn golden_aztec_ec_3_ec70() {
    golden_zpl_with_tolerance("aztec_ec_3_ec70", 7.5);
}
#[test]
fn golden_aztec_ec_4_ec95() {
    golden_zpl_with_tolerance("aztec_ec_4_ec95", 7.5);
}
#[test]
fn golden_dhlparceluk_dhl_text() {
    golden_zpl_with_tolerance("dhlparceluk_dhl_text", 5.5);
}
#[test]
fn golden_dhlparceluk_ver() {
    golden_zpl_with_tolerance("dhlparceluk_ver", 5.5);
}
#[test]
fn golden_postnl_qr() {
    golden_zpl_with_tolerance("postnl_qr", 5.0);
}
#[test]
fn golden_edi_triangle() {
    golden_zpl_with_tolerance("edi_triangle", 2.0);
}
#[test]
fn golden_qr_ft_by100() {
    golden_zpl_with_tolerance("qr_ft_by100", 1.0);
}
#[test]
fn golden_qr_ft_600() {
    golden_zpl_with_tolerance("qr_ft_600", 1.0);
}
#[test]
fn golden_qr_ft_test() {
    golden_zpl_with_tolerance("qr_ft_test", 1.0);
}

// ── EPL golden tests ──────────────────────────────────────────────

#[test]
fn golden_dpduk_epl() {
    golden_epl_with_tolerance("dpduk", 6.5);
}
