mod common;

use sqpack_rs::tex::{TextureFormat, decompress_to_rgba8};
use sqpack_rs::{DatFile, Error, FileKind, Index, SqPack, SqPackPayload};

const UI_LOADING_PATH: &str = "ui/loadingimage/logohdffxiv.tex";

const SKIN_TEXTURE_PATH: &str = "chara/common/texture/skin_m.tex";

fn maybe_open_sqpack() -> Option<SqPack> {
    let data_dir = common::maybe_data_dir()?;
    let install_root = common::install_root_from_data_dir(&data_dir)?;
    match SqPack::open(&install_root) {
        Ok(sq) => Some(sq),
        Err(e) => {
            eprintln!("skipping: SqPack::open failed: {e}");
            None
        }
    }
}

fn maybe_texture(sq: &SqPack, game_path: &str) -> Option<Vec<u8>> {
    match sq.read_file_typed(game_path) {
        Ok(SqPackPayload::Texture(t)) => Some(t.bytes().to_vec()),
        Ok(other) => {
            eprintln!(
                "skipping {game_path}: expected Texture payload, got {:?}",
                other.kind()
            );
            None
        }
        Err(Error::PathNotFound { .. }) => {
            eprintln!("skipping {game_path}: path not found on this install");
            None
        }
        Err(e) => panic!("unexpected error reading {game_path}: {e}"),
    }
}

#[test]
#[ignore = "requires real FFXIV install; run with `cargo test -- --ignored`"]
fn ui_texture_parses_and_has_valid_dimensions() {
    let Some(sq) = maybe_open_sqpack() else {
        eprintln!("skipping: no sqpack data available (set SQPACK_TEST_DATA_DIR)");
        return;
    };
    let Some(bytes) = maybe_texture(&sq, UI_LOADING_PATH) else {
        return;
    };

    let tex = sqpack_rs::tex::Tex::parse(&bytes)
        .unwrap_or_else(|e| panic!("Tex::parse failed on {UI_LOADING_PATH}: {e}"));

    let hdr = tex.header();
    assert!(hdr.width > 0, "width must be > 0, got {}", hdr.width);
    assert!(hdr.height > 0, "height must be > 0, got {}", hdr.height);
    assert!(
        tex.mip_count() >= 1,
        "mip_count must be >= 1, got {}",
        tex.mip_count()
    );

    let surf = tex
        .surface_bytes(0, 0)
        .expect("surface_bytes(0,0) must succeed");
    assert!(!surf.is_empty(), "surface_bytes(0,0) must be non-empty");

    eprintln!(
        "{UI_LOADING_PATH}: {}x{} {:?} mips={} slices_per_mip={} surface0={}B",
        hdr.width,
        hdr.height,
        hdr.format,
        tex.mip_count(),
        tex.slices_per_mip(),
        surf.len(),
    );
}

#[test]
#[ignore = "requires real FFXIV install; run with `cargo test -- --ignored`"]
fn ui_texture_iter_surfaces_count_and_dimension_invariants() {
    let Some(sq) = maybe_open_sqpack() else {
        eprintln!("skipping: no sqpack data available (set SQPACK_TEST_DATA_DIR)");
        return;
    };
    let Some(bytes) = maybe_texture(&sq, UI_LOADING_PATH) else {
        return;
    };

    let tex =
        sqpack_rs::tex::Tex::parse(&bytes).unwrap_or_else(|e| panic!("Tex::parse failed: {e}"));

    let surfaces = tex
        .surfaces()
        .expect("iter_surfaces must not error on a real texture");

    let expected_count = tex.mip_count() * (tex.slices_per_mip() as usize);
    assert_eq!(
        surfaces.len(),
        expected_count,
        "surface count must equal mip_count * slices_per_mip"
    );

    let mut prev_w = u32::MAX;
    let mut prev_h = u32::MAX;
    let mut prev_mip = usize::MAX;
    for surf in &surfaces {
        if surf.mip != prev_mip {
            assert!(
                surf.width <= prev_w,
                "mip {} width {} > mip {} width {} (non-monotonic)",
                surf.mip,
                surf.width,
                prev_mip,
                prev_w
            );
            assert!(
                surf.height <= prev_h,
                "mip {} height {} > mip {} height {} (non-monotonic)",
                surf.mip,
                surf.height,
                prev_mip,
                prev_h
            );
            prev_w = surf.width;
            prev_h = surf.height;
            prev_mip = surf.mip;
        }
    }

    eprintln!(
        "{UI_LOADING_PATH}: {} surfaces OK (mips={} slices_per_mip={})",
        surfaces.len(),
        tex.mip_count(),
        tex.slices_per_mip(),
    );
}

#[test]
#[ignore = "requires real FFXIV install; run with `cargo test -- --ignored`"]
fn skin_texture_bc_decompress_output_length_correct() {
    let Some(sq) = maybe_open_sqpack() else {
        eprintln!("skipping: no sqpack data available (set SQPACK_TEST_DATA_DIR)");
        return;
    };
    let Some(bytes) = maybe_texture(&sq, SKIN_TEXTURE_PATH) else {
        return;
    };

    let tex = sqpack_rs::tex::Tex::parse(&bytes)
        .unwrap_or_else(|e| panic!("Tex::parse failed on {SKIN_TEXTURE_PATH}: {e}"));

    let hdr = tex.header();
    let fmt = hdr.format;

    let is_supported_bc = matches!(
        fmt,
        TextureFormat::BC1
            | TextureFormat::BC2
            | TextureFormat::BC3
            | TextureFormat::BC4
            | TextureFormat::BC5
    );
    if !is_supported_bc {
        eprintln!("skipping decompress step: {SKIN_TEXTURE_PATH} is {fmt:?}, not BC1/2/3/4/5");
        assert!(hdr.width > 0);
        assert!(hdr.height > 0);
        return;
    }

    let surf = tex
        .surface_bytes(0, 0)
        .expect("surface_bytes(0,0) must succeed");

    let w = tex.width_of_mip(0);
    let h = tex.height_of_mip(0);

    let rgba = decompress_to_rgba8(fmt, w, h, surf)
        .unwrap_or_else(|e| panic!("decompress_to_rgba8 failed on {SKIN_TEXTURE_PATH}: {e}"));

    let expected_len = (w as usize) * (h as usize) * 4;
    assert_eq!(
        rgba.len(),
        expected_len,
        "RGBA8 output must be width*height*4 bytes"
    );

    eprintln!(
        "{SKIN_TEXTURE_PATH}: {w}x{h} {fmt:?} decompress OK → {} bytes",
        rgba.len()
    );
}

#[test]
#[ignore = "requires real FFXIV install; run with `cargo test -- --ignored`"]
fn scanned_textures_parse_and_have_positive_dimensions() {
    let Some(data_dir) = common::maybe_data_dir() else {
        eprintln!("skipping: no sqpack data available (set SQPACK_TEST_DATA_DIR)");
        return;
    };
    let Some(install_root) = common::install_root_from_data_dir(&data_dir) else {
        eprintln!(
            "skipping: could not derive install root from {}",
            data_dir.display()
        );
        return;
    };

    let sq = SqPack::open(&install_root).expect("SqPack::open");

    let dat_path = data_dir.join("000000.win32.dat0");
    let idx_path = data_dir.join("000000.win32.index");

    if !dat_path.exists() || !idx_path.exists() {
        eprintln!("skipping: 000000.win32.dat0 or .index not found");
        return;
    }

    let idx = Index::open(&idx_path).expect("Index::open");
    let mut dat = DatFile::open(&dat_path).expect("DatFile::open");

    let mut texture_count = 0usize;
    let mut checked = 0usize;
    let target = 3usize;

    'outer: for entry in idx
        .entries()
        .iter()
        .filter(|e| !e.is_synonym() && e.data_file_id() == 0)
        .take(500)
    {
        match dat.read_file_typed(entry.byte_offset()) {
            Ok(SqPackPayload::Texture(t)) => {
                let tex_bytes = t.bytes();
                match sqpack_rs::tex::Tex::parse(tex_bytes) {
                    Ok(tex) => {
                        let hdr = tex.header();
                        assert!(
                            hdr.width > 0,
                            "texture at hash {:#018x}: width must be > 0",
                            entry.hash
                        );
                        assert!(
                            hdr.height > 0,
                            "texture at hash {:#018x}: height must be > 0",
                            entry.hash
                        );
                        assert!(
                            tex.mip_count() >= 1,
                            "texture at hash {:#018x}: mip_count must be >= 1",
                            entry.hash
                        );
                        let surf = tex.surface_bytes(0, 0).unwrap_or_else(|e| {
                            panic!(
                                "surface_bytes(0,0) failed for hash {:#018x}: {e}",
                                entry.hash
                            )
                        });
                        assert!(!surf.is_empty(), "surface_bytes(0,0) must be non-empty");

                        texture_count += 1;
                        checked += 1;
                        eprintln!(
                            "  texture {checked}: hash={:#018x} {}x{} {:?} mips={}",
                            entry.hash,
                            hdr.width,
                            hdr.height,
                            hdr.format,
                            tex.mip_count(),
                        );
                        if checked >= target {
                            break 'outer;
                        }
                    }
                    Err(e) => {
                        panic!("Tex::parse failed for hash {:#018x}: {e}", entry.hash);
                    }
                }
            }
            Ok(_other) => {}
            Err(Error::UnsupportedFileKind {
                kind: FileKind::Unknown(_),
            }) => {}
            Err(e) => {
                eprintln!("  skipping hash {:#018x}: {e}", entry.hash);
            }
        }
    }

    eprintln!(
        "scanned_textures: found {texture_count} texture(s) in first 500 entries of 000000.win32"
    );

    let _ = sq;
}

#[test]
#[ignore = "requires real FFXIV install; run with `cargo test -- --ignored`"]
fn unsupported_format_decompress_returns_format_not_decompressible() {
    let Some(data_dir) = common::maybe_data_dir() else {
        eprintln!("skipping: no sqpack data available (set SQPACK_TEST_DATA_DIR)");
        return;
    };
    let Some(install_root) = common::install_root_from_data_dir(&data_dir) else {
        eprintln!(
            "skipping: could not derive install root from {}",
            data_dir.display()
        );
        return;
    };

    let sq = SqPack::open(&install_root).expect("SqPack::open");

    let dat_path = data_dir.join("000000.win32.dat0");
    let idx_path = data_dir.join("000000.win32.index");

    if !dat_path.exists() || !idx_path.exists() {
        eprintln!("skipping: 000000.win32.dat0 or .index not found");
        return;
    }

    let idx = Index::open(&idx_path).expect("Index::open");
    let mut dat = DatFile::open(&dat_path).expect("DatFile::open");

    let mut found_unsupported = false;
    for entry in idx
        .entries()
        .iter()
        .filter(|e| !e.is_synonym() && e.data_file_id() == 0)
        .take(500)
    {
        if let Ok(SqPackPayload::Texture(t)) = dat.read_file_typed(entry.byte_offset()) {
            if let Ok(tex) = sqpack_rs::tex::Tex::parse(t.bytes()) {
                let fmt = tex.header().format;
                let supported = matches!(
                    fmt,
                    TextureFormat::BC1
                        | TextureFormat::BC2
                        | TextureFormat::BC3
                        | TextureFormat::BC4
                        | TextureFormat::BC5
                );
                if !supported {
                    let w = tex.width_of_mip(0);
                    let h = tex.height_of_mip(0);
                    if let Ok(surf) = tex.surface_bytes(0, 0) {
                        let err = decompress_to_rgba8(fmt, w, h, surf)
                            .expect_err("unsupported format must error");
                        assert!(
                            matches!(err, Error::FormatNotDecompressibleToRgba { .. }),
                            "expected FormatNotDecompressibleToRgba for {fmt:?}, got {err:?}"
                        );
                        eprintln!(
                            "unsupported format rejection confirmed: hash={:#018x} fmt={fmt:?}",
                            entry.hash
                        );
                        found_unsupported = true;
                        break;
                    }
                }
            }
        }
    }

    if !found_unsupported {
        eprintln!(
            "no unsupported-format texture found in first 500 entries \
             (all may be BC1..BC5 on this install); skipping assertion"
        );
    }

    let _ = sq;
}
