mod common;

use std::io::{BufReader, Seek, SeekFrom};

use binrw::BinRead as _;
use sqpack_rs::dat::FileInfo;
use sqpack_rs::header::SqPackFileType;
use sqpack_rs::{DatFile, Error, FileKind, Index};

const SMALL_INDEX: &str = "000000.win32.index";
const SMALL_DAT0: &str = "000000.win32.dat0";

const TINY_DAT0: &str = "120000.win32.dat0";

#[test]
#[ignore = "requires real FFXIV .dat file; run with `cargo test -- --ignored`"]
fn opens_tiny_real_dat0() {
    let Some(dir) = common::maybe_data_dir() else {
        eprintln!("skipping: no sqpack data available (set SQPACK_TEST_DATA_DIR)");
        return;
    };
    let path = dir.join(TINY_DAT0);
    let dat = DatFile::open(&path).expect("DatFile::open should succeed on tiny .dat0");
    let header = dat.header().expect(".dat0 carries a SqPack header");
    assert_eq!(header.typed_file_type(), SqPackFileType::Dat);
    assert_eq!(header.size, 1024);
    assert_eq!(dat.source_path(), Some(path.as_path()));
}

#[test]
#[ignore = "requires real FFXIV .dat file; run with `cargo test -- --ignored`"]
fn reads_first_standard_file_from_real_dat0() {
    let Some(dir) = common::maybe_data_dir() else {
        eprintln!("skipping: no sqpack data available (set SQPACK_TEST_DATA_DIR)");
        return;
    };
    let idx_path = dir.join(SMALL_INDEX);
    let dat_path = dir.join(SMALL_DAT0);
    let idx = Index::open(&idx_path).expect("index open");
    let mut dat = DatFile::open(&dat_path).expect("dat open");

    let entry = idx
        .entries()
        .iter()
        .find(|e| !e.is_synonym() && e.data_file_id() == 0)
        .expect("at least one non-synonym entry in .dat0");

    let mut chosen_entry = None;
    for e in idx
        .entries()
        .iter()
        .filter(|e| !e.is_synonym() && e.data_file_id() == 0)
    {
        match dat.read_file(e.byte_offset()) {
            Ok(bytes) => {
                chosen_entry = Some((e, bytes));
                break;
            }
            Err(Error::UnsupportedFileKind { .. }) => {}
            Err(other) => panic!("unexpected error at hash {:#x}: {other}", e.hash),
        }
    }

    let (e, bytes) = chosen_entry.unwrap_or_else(|| {
        panic!(
            "no Standard payload in dat0 (first entry hash {:#x})",
            entry.hash
        )
    });
    eprintln!(
        "extracted hash={:#018x} offset={:#x} -> {} bytes",
        e.hash,
        e.byte_offset(),
        bytes.len()
    );
}

#[test]
#[ignore = "requires real FFXIV .dat file; run with `cargo test -- --ignored`"]
fn sweeps_many_entries_from_real_dat0() {
    let Some(dir) = common::maybe_data_dir() else {
        eprintln!("skipping: no sqpack data available (set SQPACK_TEST_DATA_DIR)");
        return;
    };
    let idx_path = dir.join(SMALL_INDEX);
    let dat_path = dir.join(SMALL_DAT0);
    let idx = Index::open(&idx_path).expect("index open");
    let mut dat = DatFile::open(&dat_path).expect("dat open");

    let target = 20usize;
    let mut ok = 0usize;
    let mut model = 0usize;
    let mut texture = 0usize;
    let mut other_unsupported = 0usize;
    let mut empty = 0usize;
    let mut visited = 0usize;

    for e in idx
        .entries()
        .iter()
        .filter(|e| !e.is_synonym() && e.data_file_id() == 0)
    {
        if visited >= 200 {
            break;
        }
        visited += 1;
        match dat.read_file(e.byte_offset()) {
            Ok(bytes) => {
                if bytes.is_empty() {
                    empty += 1;
                } else {
                    ok += 1;
                    let raw_file = std::fs::File::open(&dat_path).expect("raw file open");
                    let mut raw_reader = BufReader::new(raw_file);
                    raw_reader
                        .seek(SeekFrom::Start(e.byte_offset()))
                        .expect("seek to entry");
                    let file_info = FileInfo::read(&mut raw_reader).expect("FileInfo parse");
                    let bytes_len_u32 =
                        u32::try_from(bytes.len()).expect("payload larger than u32::MAX");
                    assert_eq!(
                        bytes_len_u32,
                        file_info.raw_file_size(),
                        "bytes.len() != raw_file_size for hash={:#018x} offset={:#x}",
                        e.hash,
                        e.byte_offset()
                    );
                }
                if ok >= target {
                    break;
                }
            }
            Err(Error::UnsupportedFileKind { kind }) => match kind {
                FileKind::Model => model += 1,
                FileKind::Texture => texture += 1,
                _ => other_unsupported += 1,
            },
            Err(other) => panic!("unexpected error at hash {:#x}: {other}", e.hash),
        }
    }

    eprintln!(
        "sweep: visited={visited} ok={ok} empty={empty} model={model} texture={texture} other_unsupported={other_unsupported}"
    );
    assert!(
        ok > 0,
        "expected to extract at least one Standard file from {SMALL_DAT0}"
    );
}

#[test]
#[ignore = "requires real FFXIV .dat file; run with `cargo test -- --ignored`"]
fn dat_header_is_well_formed_on_real_file() {
    let Some(dir) = common::maybe_data_dir() else {
        eprintln!("skipping: no sqpack data available (set SQPACK_TEST_DATA_DIR)");
        return;
    };
    let path = dir.join(SMALL_DAT0);
    let dat = DatFile::open(&path).expect("open");
    let header = dat.header().expect(".dat0 carries a SqPack header");
    assert_eq!(header.magic, sqpack_rs::header::SQPACK_MAGIC);
    assert_eq!(header.typed_file_type(), SqPackFileType::Dat);
    assert_eq!(header.size, 1024);
}
