//! Real-disc test for the movie manifest → voice binding. Skipped without
//! `SYLPHEED_DISC`.

use std::path::PathBuf;

use sylpheed_formats::movie_manifest;
use sylpheed_formats::slb::VoiceLang;
use sylpheed_formats::PakArchive;

fn disc_root() -> Option<PathBuf> {
    let p = PathBuf::from(std::env::var("SYLPHEED_DISC").ok()?);
    p.join("dat").is_dir().then_some(p)
}

/// Read the manifest + `eng\sounds.tbl` out of `tables.pak`.
fn load_manifest_and_sounds(root: &PathBuf) -> (Vec<u8>, Vec<u8>) {
    let pak = PakArchive::open(root.join("dat/tables.pak")).unwrap();
    let manifest = pak
        .entries()
        .iter()
        .find_map(|e| pak.read(e).ok().filter(|b| movie_manifest::is_manifest(b)))
        .expect("movie manifest present in tables.pak");
    let sounds = pak
        .read_by_name("eng\\sounds.tbl")
        .expect("sounds.tbl present")
        .unwrap();
    (manifest, sounds)
}

#[test]
fn binds_and_resolves_movie_voice() {
    let Some(root) = disc_root() else {
        eprintln!("SKIP: set SYLPHEED_DISC");
        return;
    };
    let (manifest, sounds) = load_manifest_and_sounds(&root);
    let entries = movie_manifest::parse(&manifest);
    assert!(entries.len() > 90, "expected ~101 movies, got {}", entries.len());

    // Standard story movie → VOICE_<movie> in <lang>\Movie\.
    assert_eq!(
        movie_manifest::voice_token(&manifest, "S13A").as_deref(),
        Some("VOICE_S13A")
    );
    assert_eq!(
        movie_manifest::resolve_voice_entry(&manifest, &sounds, "S13A", VoiceLang::English)
            .as_deref(),
        Some("eng\\Movie\\VOICE_S13A.slb")
    );

    // A resupply movie bound to an in-mission radio clip → VOICE_D_* in \etc\.
    // (This is what a `VOICE_<movie>` guess would miss entirely.)
    assert_eq!(
        movie_manifest::voice_token(&manifest, "hokyu_LS_s02A").as_deref(),
        Some("VOICE_D_450")
    );
    assert_eq!(
        movie_manifest::resolve_voice_entry(
            &manifest,
            &sounds,
            "hokyu_LS_s02A",
            VoiceLang::English
        )
        .as_deref(),
        Some("eng\\etc\\VOICE_D_450.slb")
    );

    // A resupply movie the game leaves unbound in the manifest → no DIRECT
    // binding. (Extending to unbound movies by shared demo line was verified
    // WRONG against the running game, so we do NOT resolve these.)
    let e = entries
        .iter()
        .find(|e| e.movie == "hokyu_DS_s13A")
        .expect("hokyu_DS_s13A present in manifest");
    assert_eq!(e.voice_token, None, "hokyu_DS_s13A has no direct voice binding");
    assert_eq!(
        movie_manifest::resolve_voice_entry(&manifest, &sounds, "hokyu_DS_s13A", VoiceLang::English),
        None
    );

    // Every resolved voice entry must actually exist in sound.pak.
    let snd = std::fs::read(root.join("dat/sound.pak")).unwrap();
    let keys: std::collections::HashSet<u32> = PakArchive::parse_toc(&snd)
        .unwrap()
        .iter()
        .map(|e| e.name_hash)
        .collect();
    let mut resolved = 0;
    let mut missing = Vec::new();
    for e in &entries {
        if let Some(full) =
            movie_manifest::resolve_voice_entry(&manifest, &sounds, &e.movie, VoiceLang::English)
        {
            resolved += 1;
            if !keys.contains(&sylpheed_formats::hash::name_hash(&full)) {
                missing.push(full);
            }
        }
    }
    assert!(resolved > 80, "expected 80+ voiced movies, got {resolved}");
    assert!(missing.is_empty(), "resolved but absent in sound.pak: {missing:?}");
}
