use std::fmt::Write as _;
use std::path::PathBuf;

use sqpack_rs::verify::{PathManifestAddOutcome, PathMismatchKind};
use sqpack_rs::{SqPack, SqPackPathManifestBuilder, SqPackPathVerifier};

const CURATED_PATHS: &[&str] = &[
    "exd/root.exl",
    "exd/item.exh",
    "exd/quest.exh",
    "exd/action.exh",
    "exd/territorytype.exh",
    "exd/classjob.exh",
    "exd/world.exh",
];

#[allow(clippy::too_many_lines)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let install_root: PathBuf = if let Some(arg) = std::env::args().nth(1) {
        PathBuf::from(arg)
    } else {
        eprintln!(
            "usage: verify_install <install_root>\n\n\
             Pass the FFXIV game directory (the one containing sqpack/).",
        );
        std::process::exit(2);
    };

    if !install_root.join("sqpack").is_dir() {
        eprintln!(
            "No sqpack/ directory under {}; argv[1] must be the FFXIV game directory.",
            install_root.display(),
        );
        std::process::exit(1);
    }

    println!("install root: {}", install_root.display());
    println!();

    let sq = SqPack::open(&install_root)?;

    println!(
        "=== Step 1: building manifest from {} curated paths ===",
        CURATED_PATHS.len()
    );
    let mut builder = SqPackPathManifestBuilder::new(&sq);
    let mut added = 0usize;
    let mut skipped = 0usize;
    let mut errored = 0usize;

    for path in CURATED_PATHS {
        match builder.add(path) {
            Ok(PathManifestAddOutcome::Added(entry)) => {
                println!(
                    "  + {path}  ({} bytes, sha256={})",
                    entry.size,
                    hex(&entry.hash),
                );
                added += 1;
            }
            Ok(PathManifestAddOutcome::Skipped { kind, .. }) => {
                println!("  - {path}  skipped (kind={kind:?})");
                skipped += 1;
            }
            Ok(other) => {
                println!("  ? {path}  unknown outcome: {other:?}");
            }
            Err(e) => {
                println!("  ! {path}  error: {e}");
                errored += 1;
            }
        }
    }

    let mut manifest = builder.finish();
    println!();
    println!(
        "manifest built: {} entries (added {added}, skipped {skipped}, errored {errored})",
        manifest.len()
    );

    if manifest.is_empty() {
        eprintln!("No entries in the manifest — install doesn't carry any of the curated paths.");
        std::process::exit(2);
    }

    println!();
    println!("=== Step 2: round-trip verify against the same install ===");
    let mut verifier = SqPackPathVerifier::new(&sq);
    let report = verifier.verify(&manifest);

    println!(
        "report: verified={}, mismatches={}, skipped={}, cancelled={}, is_clean={}",
        report.verified_count,
        report.mismatches.len(),
        report.skipped_count,
        report.cancelled,
        report.is_clean(),
    );
    if !report.is_clean() {
        eprintln!("Round-trip verify FAILED — library produced inconsistent hashes.");
        for m in &report.mismatches {
            eprintln!("  {} [{:?}]: {:?}", m.game_path, m.category, m.kind);
        }
        std::process::exit(3);
    }

    println!();
    println!("=== Step 3: synthesize corruption by tampering with one manifest entry ===");
    let victim_path = manifest
        .entries
        .keys()
        .next()
        .expect("manifest has entries")
        .clone();
    let original_hash = manifest.entries[&victim_path].hash;
    manifest.entries.get_mut(&victim_path).unwrap().hash[0] ^= 0x01;
    println!("tampered: {victim_path}");
    println!(
        "  expected (post-tamper): {}",
        hex(&manifest.entries[&victim_path].hash)
    );
    println!("  actual (live install):  {}", hex(&original_hash));

    let mut verifier = SqPackPathVerifier::new(&sq);
    let report = verifier.verify(&manifest);

    println!();
    println!(
        "report: verified={}, mismatches={}, skipped={}, cancelled={}, is_clean={}",
        report.verified_count,
        report.mismatches.len(),
        report.skipped_count,
        report.cancelled,
        report.is_clean(),
    );

    println!();
    println!("by_category breakdown:");
    for (cat, mismatches) in report.by_category() {
        println!("  category {cat:?}: {} mismatches", mismatches.len());
        for m in mismatches {
            print_mismatch(m);
        }
    }

    if report.mismatches.len() != 1 {
        eprintln!(
            "Expected exactly 1 mismatch (the tampered entry); got {}.",
            report.mismatches.len()
        );
        std::process::exit(4);
    }
    let only = &report.mismatches[0];
    if only.game_path != victim_path {
        eprintln!(
            "PathMismatch path mismatch: expected {victim_path:?}, got {:?}",
            only.game_path
        );
        std::process::exit(5);
    }
    if !matches!(only.kind, PathMismatchKind::HashMismatch { .. }) {
        eprintln!("Expected HashMismatch variant; got {:?}", only.kind);
        std::process::exit(6);
    }

    println!();
    println!("OK — library produces a clean round-trip and identifies synthesized corruption.");
    Ok(())
}

fn hex(bytes: &[u8]) -> String {
    let mut s = String::with_capacity(bytes.len() * 2);
    for b in bytes {
        let _ = write!(s, "{b:02x}");
    }
    s
}

fn print_mismatch(m: &sqpack_rs::verify::PathMismatch) {
    match &m.kind {
        PathMismatchKind::HashMismatch { expected, actual } => {
            println!("    {} HashMismatch", m.game_path);
            println!("      expected: {}", hex(expected));
            println!("      actual:   {}", hex(actual));
        }
        PathMismatchKind::SizeMismatch { expected, actual } => {
            println!(
                "    {} SizeMismatch (expected={expected} actual={actual})",
                m.game_path
            );
        }
        PathMismatchKind::Missing => println!("    {} Missing", m.game_path),
        PathMismatchKind::Unverifiable { kind } => {
            println!("    {} Unverifiable (kind={kind:?})", m.game_path);
        }
        PathMismatchKind::ReadError { message, .. } => {
            println!("    {} ReadError: {message}", m.game_path);
        }
        other => println!("    {} {other:?}", m.game_path),
    }
}
