//! Integration tests for archmeld CLI.
//!
//! These tests exercise all subcommands end-to-end using the fixture
//! archives in `test-fixtures/`.

#![allow(clippy::missing_panics_doc)]

use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;

fn cmd() -> Command {
    assert_cmd::cargo::cargo_bin_cmd!("archmeld")
}

// ─── Format detection (info) ───────────────────────────────────────

#[test]
fn info_zip() {
    cmd()
        .args(["info", "test-fixtures/sample.zip"])
        .assert()
        .success()
        .stdout(predicate::str::contains("ZIP"));
}

#[test]
fn info_tar_gz() {
    cmd()
        .args(["info", "test-fixtures/sample.tar.gz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("TAR.GZ"));
}

#[test]
fn info_tar_bz2() {
    cmd()
        .args(["info", "test-fixtures/sample.tar.bz2"])
        .assert()
        .success()
        .stdout(predicate::str::contains("TAR.BZ2"));
}

#[test]
fn info_tar_xz() {
    cmd()
        .args(["info", "test-fixtures/sample.tar.xz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("TAR.XZ"));
}

#[test]
fn info_tar_zst() {
    cmd()
        .args(["info", "test-fixtures/sample.tar.zst"])
        .assert()
        .success()
        .stdout(predicate::str::contains("TAR.ZSTD"));
}

#[test]
fn info_gz() {
    cmd()
        .args(["info", "test-fixtures/sample.txt.gz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("GZIP"));
}

#[test]
fn info_bz2() {
    cmd()
        .args(["info", "test-fixtures/sample.txt.bz2"])
        .assert()
        .success()
        .stdout(predicate::str::contains("BZIP2"));
}

#[test]
fn info_xz() {
    cmd()
        .args(["info", "test-fixtures/sample.txt.xz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("XZ"));
}

#[test]
fn info_lz4() {
    cmd()
        .args(["info", "test-fixtures/sample.txt.lz4"])
        .assert()
        .success()
        .stdout(predicate::str::contains("LZ4"));
}

#[test]
fn info_zstd() {
    cmd()
        .args(["info", "test-fixtures/sample.txt.zst"])
        .assert()
        .success()
        .stdout(predicate::str::contains("ZSTD"));
}

#[test]
fn info_json() {
    cmd()
        .args(["info", "--json", "test-fixtures/sample.zip"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"format\":"));
}

#[test]
fn info_sit() {
    cmd()
        .args(["info", "test-fixtures/sample.sit"])
        .assert()
        .success()
        .stdout(predicate::str::contains("StuffIt"));
}

#[test]
fn info_cpt() {
    cmd()
        .args(["info", "test-fixtures/sample.cpt"])
        .assert()
        .success()
        .stdout(predicate::str::contains("Compact Pro"));
}

// ─── Extraction ────────────────────────────────────────────────────

#[test]
fn extract_zip() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.zip",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success()
        .stderr(predicate::str::contains("Extracted"));

    assert!(tmp.path().join("test_sample.txt").exists());
    assert!(tmp.path().join("test_sample2.txt").exists());
}

#[test]
fn extract_tar() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.tar",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();

    assert!(tmp.path().join("test_dir").exists());
}

#[test]
fn extract_tar_gz() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.tar.gz",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();

    assert!(tmp.path().join("test_dir").is_dir());
}

#[test]
fn extract_tar_bz2() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.tar.bz2",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();

    assert!(tmp.path().join("test_dir").is_dir());
}

#[test]
fn extract_tar_xz() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.tar.xz",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();

    assert!(tmp.path().join("test_dir").is_dir());
}

#[test]
fn extract_tar_zst() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.tar.zst",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();

    assert!(tmp.path().join("test_dir").is_dir());
}

#[test]
fn extract_gz_single() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.txt.gz",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();

    assert!(tmp.path().join("decompressed").exists());
    let content = fs::read_to_string(tmp.path().join("decompressed")).unwrap();
    assert!(content.contains("Hello from archmeld"));
}

#[test]
fn extract_bz2_single() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.txt.bz2",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();
}

#[test]
fn extract_xz_single() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.txt.xz",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();
}

#[test]
fn extract_lz4_single() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.txt.lz4",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();
}

#[test]
fn extract_zstd_single() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.txt.zst",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();
}

#[test]
fn extract_with_size_limit() {
    let tmp = TempDir::new().unwrap();
    // Use an absurdly small limit to test the safety check on small files
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.zip",
            "-o",
            tmp.path().to_str().unwrap(),
            "--max-file-size",
            "1",
            "--max-total-size",
            "1",
        ])
        .assert()
        .success(); // Files are small enough to fit in 1 MiB
}

// ─── Listing ───────────────────────────────────────────────────────

#[test]
fn list_zip() {
    cmd()
        .args(["list", "test-fixtures/sample.zip"])
        .assert()
        .success()
        .stdout(predicate::str::contains("test_sample.txt"));
}

#[test]
fn list_tar_gz() {
    cmd()
        .args(["list", "test-fixtures/sample.tar.gz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("test_dir"));
}

#[test]
fn list_json() {
    cmd()
        .args(["list", "--json", "test-fixtures/sample.zip"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"path\":"));
}

// ─── Gzip inspection ──────────────────────────────────────────────

#[test]
fn gz_inspect_basic() {
    cmd()
        .args(["gz-inspect", "test-fixtures/sample.txt.gz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("deflate"))
        .stdout(predicate::str::contains("SHA-256"));
}

#[test]
fn gz_inspect_verify() {
    cmd()
        .args(["gz-inspect", "--verify", "test-fixtures/sample.txt.gz"])
        .assert()
        .success()
        .stderr(predicate::str::contains("PASSED"));
}

#[test]
fn gz_inspect_json() {
    cmd()
        .args(["gz-inspect", "--json", "test-fixtures/sample.txt.gz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"compression_method\""));
}

// ─── LZ4 operations ──────────────────────────────────────────────

#[test]
fn lz4_compress_decompress_roundtrip() {
    let tmp = TempDir::new().unwrap();
    let compressed = tmp.path().join("out.lz4");
    let decompressed = tmp.path().join("out.txt");

    // Compress
    cmd()
        .args([
            "lz4",
            "compress",
            "test-fixtures/sample.txt.gz", // any file works
            "-o",
            compressed.to_str().unwrap(),
        ])
        .assert()
        .success();

    assert!(compressed.exists());

    // Decompress
    cmd()
        .args([
            "lz4",
            "decompress",
            compressed.to_str().unwrap(),
            "-o",
            decompressed.to_str().unwrap(),
        ])
        .assert()
        .success();

    // Verify roundtrip
    let original = fs::read("test-fixtures/sample.txt.gz").unwrap();
    let result = fs::read(&decompressed).unwrap();
    assert_eq!(original, result);
}

#[test]
fn lz4_inspect() {
    cmd()
        .args(["lz4", "inspect", "test-fixtures/sample.txt.lz4"])
        .assert()
        .success()
        .stdout(predicate::str::contains("Block"));
}

#[test]
fn lz4_inspect_json() {
    cmd()
        .args(["lz4", "inspect", "--json", "test-fixtures/sample.txt.lz4"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"block_independent\""));
}

// ─── StuffIt inspection ──────────────────────────────────────────

#[test]
fn sit_inspect_basic() {
    cmd()
        .args(["sit-inspect", "test-fixtures/sample.sit"])
        .assert()
        .success()
        .stdout(predicate::str::contains("StuffIt"));
}

#[test]
fn sit_inspect_json() {
    cmd()
        .args(["sit-inspect", "--json", "test-fixtures/sample.sit"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"signature\""));
}

// ─── Compact Pro inspection ──────────────────────────────────────

#[test]
fn cpt_inspect_basic() {
    cmd()
        .args(["cpt-inspect", "test-fixtures/sample.cpt"])
        .assert()
        .success()
        .stdout(predicate::str::contains("Compact Pro"));
}

#[test]
fn cpt_inspect_json() {
    cmd()
        .args(["cpt-inspect", "--json", "test-fixtures/sample.cpt"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"header_crc32\""));
}

#[test]
fn cpt_inspect_verify() {
    cmd()
        .args(["cpt-inspect", "--verify", "test-fixtures/sample.cpt"])
        .assert()
        .success();
}

// ─── Verify ──────────────────────────────────────────────────────

#[test]
fn verify_gz() {
    cmd()
        .args(["verify", "test-fixtures/sample.txt.gz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("PASS"))
        .stdout(predicate::str::contains("SHA-256"));
}

#[test]
fn verify_zip() {
    cmd()
        .args(["verify", "test-fixtures/sample.zip"])
        .assert()
        .success()
        .stdout(predicate::str::contains("PASS"));
}

#[test]
fn verify_tar() {
    cmd()
        .args(["verify", "test-fixtures/sample.tar.gz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("PASS"));
}

#[test]
fn verify_lz4() {
    cmd()
        .args(["verify", "test-fixtures/sample.txt.lz4"])
        .assert()
        .success()
        .stdout(predicate::str::contains("PASS"));
}

#[test]
fn verify_sit() {
    cmd()
        .args(["verify", "test-fixtures/sample.sit"])
        .assert()
        .success()
        .stdout(predicate::str::contains("PASS"));
}

#[test]
fn verify_cpt() {
    cmd()
        .args(["verify", "test-fixtures/sample.cpt"])
        .assert()
        .success()
        .stdout(predicate::str::contains("PASS"));
}

// ─── Error handling ──────────────────────────────────────────────

#[test]
fn error_missing_file() {
    cmd()
        .args(["info", "nonexistent.zip"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("Error"));
}

#[test]
fn error_invalid_gz_inspect() {
    // Try to gz-inspect a ZIP file
    cmd()
        .args(["gz-inspect", "test-fixtures/sample.zip"])
        .assert()
        .failure();
}

// ─── CLI basics ──────────────────────────────────────────────────

#[test]
fn version_flag() {
    cmd()
        .arg("--version")
        .assert()
        .success()
        .stdout(predicate::str::contains("archmeld"));
}

#[test]
fn help_flag() {
    cmd()
        .arg("--help")
        .assert()
        .success()
        .stdout(predicate::str::contains("archive"));
}

#[test]
fn subcommand_help() {
    cmd()
        .args(["extract", "--help"])
        .assert()
        .success()
        .stdout(predicate::str::contains("FILE"));
}

// ─── Format override ────────────────────────────────────────────

#[test]
fn extract_with_format_override() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.txt.gz",
            "-o",
            tmp.path().to_str().unwrap(),
            "-f",
            "gz",
        ])
        .assert()
        .success();
}

// ─── Validate (dry-run decompression check) ─────────────────────

#[test]
fn validate_zip() {
    cmd()
        .args(["validate", "test-fixtures/sample.zip"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"))
        .stdout(predicate::str::contains("Entries:"));
}

#[test]
fn validate_tar_gz() {
    cmd()
        .args(["validate", "test-fixtures/sample.tar.gz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

#[test]
fn validate_gz_single() {
    cmd()
        .args(["validate", "test-fixtures/sample.txt.gz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

#[test]
fn validate_bz2_single() {
    cmd()
        .args(["validate", "test-fixtures/sample.txt.bz2"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

#[test]
fn validate_xz_single() {
    cmd()
        .args(["validate", "test-fixtures/sample.txt.xz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

#[test]
fn validate_lz4_single() {
    cmd()
        .args(["validate", "test-fixtures/sample.txt.lz4"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

#[test]
fn validate_zstd_single() {
    cmd()
        .args(["validate", "test-fixtures/sample.txt.zst"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

#[test]
fn validate_json_output() {
    cmd()
        .args(["validate", "--json", "test-fixtures/sample.zip"])
        .assert()
        .success()
        .stdout(predicate::str::contains("\"is_valid\": true"))
        .stdout(predicate::str::contains("\"entry_count\""));
}

#[test]
fn validate_with_format_override() {
    cmd()
        .args(["validate", "-f", "gz", "test-fixtures/sample.txt.gz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

#[test]
fn validate_corrupted_data() {
    let tmp = TempDir::new().unwrap();
    let bad_file = tmp.path().join("bad.gz");
    fs::write(&bad_file, b"\x1f\x8b\x08\x00CORRUPTED").unwrap();
    cmd()
        .args(["validate", bad_file.to_str().unwrap()])
        .assert()
        .failure()
        .stderr(predicate::str::contains("INVALID"));
}

#[test]
fn validate_nonexistent_file() {
    cmd()
        .args(["validate", "nonexistent.zip"])
        .assert()
        .failure()
        .stderr(predicate::str::contains("Error"));
}

#[test]
fn validate_unknown_format() {
    let tmp = TempDir::new().unwrap();
    let unknown = tmp.path().join("unknown.bin");
    fs::write(&unknown, b"not an archive").unwrap();
    cmd()
        .args(["validate", unknown.to_str().unwrap()])
        .assert()
        .failure()
        .stderr(predicate::str::contains("Could not determine"));
}

// ─── Additional edge-case tests ─────────────────────────────────

#[test]
fn info_json_is_valid_json() {
    let output = cmd()
        .args(["info", "--json", "test-fixtures/sample.zip"])
        .output()
        .unwrap();
    assert!(output.status.success());
    let parsed: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
    assert!(parsed.get("format").is_some());
    assert!(parsed.get("mime_type").is_some());
}

#[test]
fn list_json_is_valid_json() {
    let output = cmd()
        .args(["list", "--json", "test-fixtures/sample.zip"])
        .output()
        .unwrap();
    assert!(output.status.success());
    let parsed: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
    assert!(parsed.is_array());
}

#[test]
fn extract_creates_subdirectory_structure() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "test-fixtures/sample.tar.gz",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();

    // The tar has a test_dir/ with files inside
    assert!(tmp.path().join("test_dir").is_dir());
}

#[test]
fn validate_tar_bz2() {
    cmd()
        .args(["validate", "test-fixtures/sample.tar.bz2"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

#[test]
fn validate_tar_xz() {
    cmd()
        .args(["validate", "test-fixtures/sample.tar.xz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

#[test]
fn validate_tar_zst() {
    cmd()
        .args(["validate", "test-fixtures/sample.tar.zst"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

// ─── New format detection (example/ files) ──────────────────────

#[test]
fn info_rar_v5() {
    cmd()
        .args(["info", "example/7za433_rar.rar"])
        .assert()
        .success()
        .stdout(predicate::str::contains("RAR"));
}

#[test]
fn info_rar_v4() {
    cmd()
        .args(["info", "example/7za433_rar4.rar"])
        .assert()
        .success()
        .stdout(predicate::str::contains("RAR"));
}

#[test]
fn info_lzma_standalone() {
    cmd()
        .args(["info", "example/7za.exe.lzma"])
        .assert()
        .success()
        .stdout(predicate::str::contains("LZMA"));
}

#[test]
fn info_lzip() {
    cmd()
        .args(["info", "example/7za.exe.lz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("LZIP"));
}

#[test]
fn info_arc() {
    cmd()
        .args(["info", "example/LZ_COMP2.ARC"])
        .assert()
        .success()
        .stdout(predicate::str::contains("ARC"));
}

#[test]
fn info_example_tar() {
    cmd()
        .args(["info", "example/7za433_tar.tar"])
        .assert()
        .success()
        .stdout(predicate::str::contains("TAR"));
}

#[test]
fn info_example_7z_lzma() {
    cmd()
        .args(["info", "example/7za433_7zip_lzma.7z"])
        .assert()
        .success()
        .stdout(predicate::str::contains("7Z"));
}

#[test]
fn info_example_tar_gz() {
    cmd()
        .args(["info", "example/testfile_with_option_cvzf.tar.gz"])
        .assert()
        .success()
        .stdout(predicate::str::contains("TAR.GZ"));
}

// ─── RAR extraction ─────────────────────────────────────────────

#[test]
fn list_rar_v4() {
    cmd()
        .args(["list", "-f", "rar", "example/7za433_rar4.rar"])
        .assert()
        .success()
        .stdout(predicate::str::contains("7za.exe"));
}

#[test]
fn extract_rar_v4() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "-f",
            "rar",
            "example/7za433_rar4.rar",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success()
        .stderr(predicate::str::contains("Extracted"));
}

#[test]
fn validate_rar_v5() {
    cmd()
        .args(["validate", "-f", "rar", "example/7za433_rar.rar"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

// ─── LZMA extraction ────────────────────────────────────────────

#[test]
fn validate_lzma_standalone() {
    cmd()
        .args(["validate", "example/7za.exe.lzma"])
        .assert()
        .success()
        .stdout(predicate::str::contains("VALID"));
}

#[test]
fn extract_lzma_standalone() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "example/7za.exe.lzma",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();

    assert!(tmp.path().join("decompressed").exists());
}

// ─── Example archive extraction ─────────────────────────────────

#[test]
fn extract_example_tar() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "example/7za433_tar.tar",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();
}

#[test]
fn extract_example_tar_gz() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "example/testfile_with_option_cvzf.tar.gz",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();
}

#[test]
fn extract_example_zip() {
    let tmp = TempDir::new().unwrap();
    cmd()
        .args([
            "extract",
            "example/testfile_with_option_v9.zip.zip",
            "-o",
            tmp.path().to_str().unwrap(),
        ])
        .assert()
        .success();
}
