//! Integration test: run the XPR2 texture pipeline against REAL `.xpr` files
//! read directly from the retail disc image, reproducing exactly what the
//! viewer's texture-preview path does (`identify_format` → `X360Texture::
//! from_xpr2`). Skipped unless `SYLPHEED_ISO` points at the disc (or the
//! default dev path exists).
//!
//! Run: `cargo test -p sylpheed-formats --test texture_disc -- --ignored --nocapture`

use std::path::PathBuf;

use sylpheed_formats::texture::X360Texture;
use sylpheed_formats::vfs::identify_format;

fn iso_path() -> Option<PathBuf> {
    if let Ok(p) = std::env::var("SYLPHEED_ISO") {
        let p = PathBuf::from(p);
        if p.is_file() {
            return Some(p);
        }
    }
    let default = PathBuf::from(
        "/home/fabi/RE Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso",
    );
    default.is_file().then_some(default)
}

#[tokio::test]
#[ignore = "requires the retail ISO — set SYLPHEED_ISO"]
async fn xpr_pipeline_over_disc_sample() {
    let Some(iso) = iso_path() else {
        eprintln!("SKIP: ISO not found (set SYLPHEED_ISO)");
        return;
    };

    let mut reader = sylpheed_formats::xiso::open_iso(&iso).await.unwrap();
    let all = reader.list_all_files().await.unwrap();
    let mut xprs: Vec<String> = all
        .into_iter()
        .filter(|f| f.to_lowercase().ends_with(".xpr"))
        .collect();
    xprs.sort();
    println!("found {} .xpr files", xprs.len());

    // Sample across the set so we hit different texture sizes/formats.
    let sample: Vec<String> = xprs.iter().step_by((xprs.len() / 24).max(1)).cloned().collect();

    let mut ok = 0usize;
    let mut fail = 0usize;
    let mut by_format: std::collections::BTreeMap<String, usize> = Default::default();
    let mut nonxpr = 0usize;

    // Optionally dump the sampled .xpr bytes so the CLI can export them to PNG
    // for visual de-tiling validation: `DUMP_XPR_DIR=/tmp/xpr cargo test …`.
    let dump_dir = std::env::var("DUMP_XPR_DIR").ok();
    if let Some(d) = &dump_dir {
        std::fs::create_dir_all(d).unwrap();
    }

    for path in &sample {
        let bytes = reader.read_file(path).await.unwrap();
        if let Some(d) = &dump_dir {
            let base = path.rsplit('/').next().unwrap();
            std::fs::write(format!("{d}/{base}"), &bytes).unwrap();
        }
        let fmt = identify_format(&bytes);
        let magic: String = bytes
            .iter()
            .take(4)
            .map(|b| if b.is_ascii_graphic() { *b as char } else { '.' })
            .collect();

        if fmt != sylpheed_formats::vfs::FileFormat::Xpr2Texture {
            nonxpr += 1;
            println!("  [{path}] NOT XPR2 (magic {magic:?}, {} bytes)", bytes.len());
            continue;
        }

        match X360Texture::from_xpr2(&bytes) {
            Ok(t) => {
                ok += 1;
                *by_format.entry(format!("{:?}", t.format)).or_default() += 1;
                // Sanity: does the decoded data length match the descriptor
                // dimensions (what the GPU upload will require)?
                let bs = t.format.block_size() as u32;
                let bw = ((t.width + bs - 1) / bs).max(1) as usize;
                let bh = ((t.height + bs - 1) / bs).max(1) as usize;
                let need = bw * bh * t.format.bytes_per_block();
                let size_ok = if need == t.data.len() { "ok" } else { "MISMATCH" };
                println!(
                    "  [{path}] {:?} {}x{} mips={} data={} need={} {size_ok}",
                    t.format, t.width, t.height, t.mip_levels, t.data.len(), need
                );
            }
            Err(e) => {
                fail += 1;
                println!("  [{path}] from_xpr2 FAILED: {e}");
                dump_xpr2_structure(&bytes);
            }
        }
    }

    println!("\nSUMMARY: ok={ok} fail={fail} non-xpr2={nonxpr}  formats={by_format:?}");
}

/// Dump the XPR2 header + resource directory the way `from_xpr2` reads it,
/// so we can see why a file's TX2D scan comes up empty.
fn dump_xpr2_structure(bytes: &[u8]) {
    let be = |o: usize| u32::from_be_bytes(bytes[o..o + 4].try_into().unwrap());
    let magic: String = bytes[..4].iter().map(|b| *b as char).collect();
    println!(
        "      hdr magic={magic:?} header_size=0x{:X} data_size=0x{:X} num_resources={}",
        be(0x04),
        be(0x08),
        be(0x0C)
    );
    let n = be(0x0C).min(16); // guard against garbage counts
    for i in 0..n {
        let base = 0x10 + i as usize * 16;
        if base + 16 > bytes.len() {
            break;
        }
        let tag: String = bytes[base..base + 4]
            .iter()
            .map(|b| if b.is_ascii_graphic() { *b as char } else { '.' })
            .collect();
        println!(
            "      res[{i}] tag={tag:?} data_off=0x{:X} desc_size=0x{:X} name_off=0x{:X}",
            be(base + 4),
            be(base + 8),
            be(base + 12)
        );
    }
    // First 48 bytes hex for orientation.
    let hx: String = bytes
        .iter()
        .take(48)
        .map(|b| format!("{b:02X} "))
        .collect();
    println!("      hex[0..48] {hx}");
}
