//! Universe-scale integration test.
//!
//! Generates thousands of synthetic "universes" -- each a fictional embedded
//! system with its own crypto fingerprint buried in realistic file structures,
//! encoding traps, noise files, and red herrings. Kindi must find every
//! planted sigil across all of them.
//!
//! Each universe is a directory tree representing a different domain:
//!   - Medical device firmware (HIPAA, FIPS 140-2)
//!   - Automotive ECU (AUTOSAR, ISO 21434)
//!   - Avionics flight controller (DO-178C)
//!   - IoT sensor gateway (MQTT, CoAP)
//!   - Telecom base station (3GPP, KASUMI)
//!   - Banking payment terminal (PCI-DSS, EMV)
//!   - Military radio (NSA Suite B)
//!   - Smart grid controller (IEC 62351)
//!   - Satellite modem (CCSDS)
//!   - Industrial PLC (IEC 62443)
//!
//! The turmoil layer adds chaos:
//!   - Random file encodings (UTF-8, Latin-1, UTF-16)
//!   - Binary files with crypto strings at random offsets
//!   - Deep directory nesting (up to 10 levels)
//!   - Decoy files with near-miss patterns
//!   - Empty files, huge files, single-byte files
//!   - Mixed line endings (LF, CRLF, CR)
//!   - Files with no extension
//!   - Unicode filenames

use std::collections::HashMap;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};

use kindi::detect::{scan_package, ScanConfig};
use kindi::pattern::PatternDb;

/// Pattern database matching the real keyword_list.txt format
const UNIVERSE_PATTERNS: &str = r#"
[keyword_list_version]
    1

[{"evidence_type": "algorithm/symmetric/block-cipher/AES", "language": "all"}]
    "AES_encrypt"
    "AES_decrypt"
    "\bAES_set_encrypt_key\b"
    "AES_cbc_encrypt"
    "\bAES_KEY\b"
    "\bAES_BLOCK_SIZE\b"

[{"evidence_type": "algorithm/asymmetric/RSA", "language": "all"}]
    "RSA_generate_key"
    "RSAPublicKey"
    "RSAPrivateKey"
    "RSA_sign"
    "RSA_verify"
    "\bRSA_new\b"

[{"evidence_type": "algorithm/hash/SHA2", "language": "all"}]
    "SHA256_Init"
    "SHA256_Update"
    "SHA256_Final"
    "SHA256_DIGEST_LENGTH"
    "SHA512_Init"

[{"evidence_type": "algorithm/hash/SHA1", "language": "all"}]
    "\bSHA1_Init\b"
    "\bSHA1_Update\b"
    "\bSHA1_Final\b"
    "SHA_DIGEST_LENGTH"

[{"evidence_type": "algorithm/hash/MD5", "language": "all"}]
    "\bMD5_Init\b"
    "\bMD5_Update\b"
    "\bMD5_Final\b"
    "MD5_DIGEST_LENGTH"

[{"evidence_type": "algorithm/symmetric/block-cipher/DES", "language": "all"}]
    "DES_ecb_encrypt"
    "DES_set_key"
    "DES_cblock"
    "\bDES_KEY\b"

[{"evidence_type": "algorithm/symmetric/stream-cipher/ChaCha20", "language": "all"}]
    "ChaCha20_ctr32"
    "chacha20_poly1305"
    "\bCHACHA20\b"

[{"evidence_type": "algorithm/symmetric/block-cipher/Blowfish", "language": "all"}]
    "BF_encrypt"
    "BF_set_key"
    "BF_cbc_encrypt"
    "\bBLOWFISH\b"

[{"evidence_type": "algorithm/asymmetric/ECC", "language": "all"}]
    "EC_KEY_new"
    "EC_POINT_mul"
    "ECDSA_sign"
    "ECDH_compute_key"

[{"evidence_type": "algorithm/asymmetric/DiffieHellman", "language": "all"}]
    "DH_generate_key"
    "DH_compute_key"
    "DHParameterSpec"

[{"evidence_type": "algorithm/hash/HMAC", "language": "all"}]
    "\bHMAC_Init\b"
    "HMAC_Update"
    "HMAC_Final"
    "HMAC_CTX_new"

[{"evidence_type": "protocol/TLS", "language": "all"}]
    "SSL_CTX_new"
    "TLS_method"
    "SSL_connect"
    "SSL_read"
    "SSL_write"
    "SSL_library_init"

[{"evidence_type": "protocol/kerberos", "language": "all"}]
    "krb5_init_context"
    "krb5_get_credentials"
    "\bKERBEROS\b"

[{"evidence_type": "algorithm/symmetric/block-cipher/Camellia", "language": "all"}]
    "Camellia_set_key"
    "Camellia_encrypt"
    "\bCAMELLIA\b"

[{"evidence_type": "algorithm/symmetric/block-cipher/Twofish", "language": "all"}]
    "Twofish_encrypt"
    "TWOFISH_KEY"
    "\bTWOFISH\b"

[{"evidence_type": "algorithm/symmetric/block-cipher/KASUMI", "language": "all"}]
    "\bKASUMI\b"
    "kasumi_encrypt"

[{"evidence_type": "algorithm/symmetric/stream-cipher/RC4", "language": "all"}]
    "\bRC4_set_key\b"
    "RC4_encrypt"
    "\bRC4\b"

[{"evidence_type": "algorithm/hash/SHA3", "language": "all"}]
    "SHA3_256"
    "\bKECCAK\b"
    "sha3_init"

[{"evidence_type": "algorithm/symmetric/block-cipher/Serpent", "language": "all"}]
    "\bSERPENT\b"
    "serpent_encrypt"

[{"evidence_type": "algorithm/hash/BLAKE", "language": "all"}]
    "\bBLAKE2\b"
    "blake2b_init"
"#;

// ---------------------------------------------------------------------------
// Universe definitions
// ---------------------------------------------------------------------------

struct Universe {
    name: &'static str,
    domain: &'static str,
    /// (relative_path, content, expected_evidence_types)
    files: Vec<(&'static str, String, Vec<&'static str>)>,
}

/// Build all universes. Each universe plants specific crypto patterns in
/// realistic file structures with turmoil (noise, encoding chaos, decoys).
fn build_universes() -> Vec<Universe> {
    vec![
        // ── Universe 0: Medical device firmware (FIPS 140-2) ──
        Universe {
            name: "meddev-9000",
            domain: "Medical Device Firmware",
            files: vec![
                ("src/crypto/patient_data.c", format!(
                    "// Patient data encryption module - HIPAA compliant\n\
                     #include <openssl/aes.h>\n\
                     static AES_KEY enc_key;\n\
                     void encrypt_patient_record(const uint8_t *data, size_t len) {{\n\
                         AES_set_encrypt_key(master_key, 256, &enc_key);\n\
                         AES_cbc_encrypt(data, ciphertext, len, &enc_key, iv, AES_ENCRYPT);\n\
                     }}\n\
                     void hash_patient_id(const char *id) {{\n\
                         SHA256_Init(&ctx);\n\
                         SHA256_Update(&ctx, id, strlen(id));\n\
                         SHA256_Final(digest, &ctx);\n\
                     }}\n"
                ), vec!["algorithm/symmetric/block-cipher/AES", "algorithm/hash/SHA2"]),
                ("src/auth/device_auth.c", format!(
                    "// Device authentication using RSA certificates\n\
                     RSA *key = RSA_new();\n\
                     RSA_generate_key(2048, RSA_F4, NULL, NULL);\n\
                     RSA_sign(NID_sha256, digest, 32, sig, &siglen, key);\n"
                ), vec!["algorithm/asymmetric/RSA"]),
                // Noise: no crypto here
                ("src/display/lcd_driver.c", "// LCD driver - no crypto\nvoid lcd_clear(void) { memset(fb, 0, sizeof(fb)); }\n".into(), vec![]),
                // Decoy: near-miss pattern
                ("docs/DESIGN.md", "# Design\nThe CAESAR salad algorithm is not AES.\nSHA is a common abbreviation.\n".into(), vec![]),
            ],
        },

        // ── Universe 1: Automotive ECU ──
        Universe {
            name: "ecu-autosar-v4",
            domain: "Automotive ECU",
            files: vec![
                ("src/secure_boot/verify.c", format!(
                    "// Secure boot verification\n\
                     ECDSA_sign(0, hash, 32, sig, &siglen, ec_key);\n\
                     EC_KEY_new();\n\
                     EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx);\n"
                ), vec!["algorithm/asymmetric/ECC"]),
                ("src/can/encrypted_can.c", format!(
                    "// CAN bus message encryption\n\
                     AES_KEY can_key;\n\
                     AES_set_encrypt_key(session_key, 128, &can_key);\n\
                     AES_encrypt(plaintext, ciphertext, &can_key);\n\
                     // HMAC for message authentication\n\
                     HMAC_Init(&hmac_ctx, key, 16, EVP_sha256());\n\
                     HMAC_Update(&hmac_ctx, msg, msg_len);\n\
                     HMAC_Final(&hmac_ctx, mac, &mac_len);\n"
                ), vec!["algorithm/symmetric/block-cipher/AES", "algorithm/hash/HMAC"]),
                ("src/diag/uds_handler.c", "// UDS diagnostic handler\nvoid handle_request(uint8_t sid) { /* no crypto */ }\n".into(), vec![]),
            ],
        },

        // ── Universe 2: Avionics flight controller ──
        Universe {
            name: "flightctrl-do178c",
            domain: "Avionics",
            files: vec![
                ("src/datalink/acars_crypto.c", format!(
                    "// ACARS message security\n\
                     DH_generate_key(dh);\n\
                     DH_compute_key(shared_secret, peer_pub, dh);\n\
                     AES_set_encrypt_key(shared_secret, 256, &key);\n\
                     AES_cbc_encrypt(acars_msg, encrypted, len, &key, iv, 1);\n"
                ), vec!["algorithm/asymmetric/DiffieHellman", "algorithm/symmetric/block-cipher/AES"]),
                ("src/tls/avionics_tls.c", format!(
                    "// TLS for ground station link\n\
                     SSL_library_init();\n\
                     SSL_CTX *ctx = SSL_CTX_new(TLS_method());\n\
                     SSL_connect(ssl);\n\
                     SSL_read(ssl, buf, sizeof(buf));\n\
                     SSL_write(ssl, response, rlen);\n"
                ), vec!["protocol/TLS"]),
            ],
        },

        // ── Universe 3: IoT sensor gateway ──
        Universe {
            name: "iot-gateway-mqtt",
            domain: "IoT Gateway",
            files: vec![
                ("src/mqtt/tls_transport.c", format!(
                    "// MQTT over TLS\n\
                     SSL_CTX_new(TLS_method());\n\
                     chacha20_poly1305(key, nonce, aad, aad_len, in, out, len, tag);\n\
                     ChaCha20_ctr32(out, in, len, key, nonce, counter);\n"
                ), vec!["protocol/TLS", "algorithm/symmetric/stream-cipher/ChaCha20"]),
                ("src/sensor/data_hash.c", format!(
                    "// Sensor data integrity\n\
                     SHA256_Init(&ctx);\n\
                     SHA256_Update(&ctx, reading, sizeof(reading));\n\
                     SHA256_Final(hash, &ctx);\n\
                     // Legacy fallback\n\
                     MD5_Init(&md5);\n\
                     MD5_Update(&md5, data, len);\n\
                     MD5_Final(digest, &md5);\n"
                ), vec!["algorithm/hash/SHA2", "algorithm/hash/MD5"]),
                // Binary file with crypto string at offset 50
                ("firmware/sensor.bin", {
                    let mut v = vec![0xFFu8; 100];
                    v[50..60].copy_from_slice(b"AES_encryp");  // truncated - should NOT match
                    String::from_utf8_lossy(&v).into_owned()
                }, vec![]),
            ],
        },

        // ── Universe 4: Telecom base station ──
        Universe {
            name: "basestation-3gpp",
            domain: "Telecom",
            files: vec![
                ("src/cipher/kasumi_core.c", format!(
                    "// 3GPP KASUMI block cipher (f8/f9)\n\
                     KASUMI kasumi_ctx;\n\
                     kasumi_encrypt(&kasumi_ctx, input, output);\n"
                ), vec!["algorithm/symmetric/block-cipher/KASUMI"]),
                ("src/auth/aka_auth.c", format!(
                    "// AKA authentication\n\
                     SHA256_Init(&ctx);\n\
                     SHA256_Update(&ctx, rand, 16);\n\
                     SHA256_Final(res, &ctx);\n\
                     HMAC_Init(&hmac, ck, 16, EVP_sha256());\n\
                     HMAC_Update(&hmac, sqn, 6);\n\
                     HMAC_Final(&hmac, mac, &len);\n"
                ), vec!["algorithm/hash/SHA2", "algorithm/hash/HMAC"]),
            ],
        },

        // ── Universe 5: Banking payment terminal ──
        Universe {
            name: "pos-terminal-emv",
            domain: "Payment Terminal",
            files: vec![
                ("src/pin/pin_block.c", format!(
                    "// PIN block encryption (3DES/AES)\n\
                     DES_set_key(&key1, &ks1);\n\
                     DES_ecb_encrypt(&pin_block, &encrypted, &ks1, DES_ENCRYPT);\n\
                     // Migration to AES\n\
                     AES_set_encrypt_key(dukpt_key, 128, &aes);\n\
                     AES_encrypt(pin_block, encrypted_pin, &aes);\n"
                ), vec!["algorithm/symmetric/block-cipher/DES", "algorithm/symmetric/block-cipher/AES"]),
                ("src/emv/card_auth.c", format!(
                    "// EMV card authentication\n\
                     RSA_verify(NID_sha256, hash, 32, sig, siglen, issuer_key);\n\
                     RSAPublicKey *pk = load_ca_key(rid);\n"
                ), vec!["algorithm/asymmetric/RSA"]),
                ("src/hsm/key_inject.c", format!(
                    "// HSM key injection ceremony\n\
                     // Legacy: SHA-1 for key check values\n\
                     SHA1_Init(&ctx);\n\
                     SHA1_Update(&ctx, key, 16);\n\
                     SHA1_Final(kcv, &ctx);\n"
                ), vec!["algorithm/hash/SHA1"]),
            ],
        },

        // ── Universe 6: Military radio ──
        Universe {
            name: "milradio-suiteb",
            domain: "Military Radio",
            files: vec![
                ("src/comsec/suite_b.c", format!(
                    "// NSA Suite B implementation\n\
                     AES_set_encrypt_key(key, 256, &aes_key);\n\
                     AES_cbc_encrypt(plaintext, ciphertext, len, &aes_key, iv, 1);\n\
                     // ECDH key agreement\n\
                     ECDH_compute_key(shared, 32, peer_pub, my_key, NULL);\n\
                     EC_KEY_new();\n\
                     // SHA-384 for Suite B\n\
                     SHA512_Init(&ctx); // SHA-384 uses SHA-512 internals\n"
                ), vec!["algorithm/symmetric/block-cipher/AES", "algorithm/asymmetric/ECC", "algorithm/hash/SHA2"]),
            ],
        },

        // ── Universe 7: Smart grid controller ──
        Universe {
            name: "smartgrid-iec62351",
            domain: "Smart Grid",
            files: vec![
                ("src/dnp3/secure_auth.c", format!(
                    "// DNP3 Secure Authentication\n\
                     HMAC_CTX_new();\n\
                     HMAC_Init(&ctx, key, 32, EVP_sha256());\n\
                     HMAC_Update(&ctx, challenge, clen);\n\
                     HMAC_Final(&ctx, response, &rlen);\n"
                ), vec!["algorithm/hash/HMAC"]),
                ("src/goose/goose_encrypt.c", format!(
                    "// IEC 62351 GOOSE encryption\n\
                     AES_set_encrypt_key(session_key, 128, &key);\n\
                     AES_cbc_encrypt(goose_pdu, encrypted, len, &key, iv, 1);\n"
                ), vec!["algorithm/symmetric/block-cipher/AES"]),
            ],
        },

        // ── Universe 8: Satellite modem ──
        Universe {
            name: "satmodem-ccsds",
            domain: "Satellite Communications",
            files: vec![
                ("src/ccsds/tc_security.c", format!(
                    "// CCSDS TC Space Data Link Security\n\
                     AES_KEY tc_key;\n\
                     AES_set_encrypt_key(master_key, 256, &tc_key);\n\
                     AES_encrypt(counter, keystream, &tc_key); // CTR mode\n\
                     // SHA-256 for authentication\n\
                     SHA256_Init(&hash_ctx);\n\
                     SHA256_Update(&hash_ctx, frame, frame_len);\n\
                     SHA256_Final(fecf, &hash_ctx);\n"
                ), vec!["algorithm/symmetric/block-cipher/AES", "algorithm/hash/SHA2"]),
                // CRLF line endings
                ("docs/SECURITY.txt", "Security overview\r\nNo crypto in this file\r\nJust documentation\r\n".into(), vec![]),
            ],
        },

        // ── Universe 9: Industrial PLC ──
        Universe {
            name: "plc-iec62443",
            domain: "Industrial PLC",
            files: vec![
                ("src/opc_ua/security.c", format!(
                    "// OPC-UA security channel\n\
                     RSA_generate_key(2048, 65537, NULL, NULL);\n\
                     RSA_sign(NID_sha256, hash, 32, sig, &siglen, key);\n\
                     RSAPublicKey *cert_key;\n"
                ), vec!["algorithm/asymmetric/RSA"]),
                ("src/modbus/tls_wrapper.c", format!(
                    "// Modbus/TCP over TLS\n\
                     SSL_library_init();\n\
                     SSL_CTX *ctx = SSL_CTX_new(TLS_method());\n\
                     SSL_connect(ssl);\n"
                ), vec!["protocol/TLS"]),
            ],
        },
    ]
}

/// Turmoil: additional chaos files that stress the scanner without
/// containing real crypto patterns (or contain patterns that should
/// be correctly handled).
fn turmoil_files() -> Vec<(&'static str, Vec<u8>)> {
    vec![
        // Empty file
        ("turmoil/empty.c", vec![]),
        // Single byte
        ("turmoil/single.h", vec![b'x']),
        // File with only null bytes
        ("turmoil/nulls.bin", vec![0u8; 256]),
        // Very long line (no newlines)
        ("turmoil/longline.txt", "a".repeat(100_000).into_bytes()),
        // UTF-16 LE encoded file with crypto keyword
        ("turmoil/utf16.txt", {
            let s = "This file mentions AES_encrypt in UTF-16";
            let mut v = vec![0xFF, 0xFE]; // BOM
            for c in s.encode_utf16() {
                v.extend_from_slice(&c.to_le_bytes());
            }
            v
        }),
        // Latin-1 encoded file
        ("turmoil/latin1.c", {
            let mut v = b"// Caf\xe9 uses SHA256_Init for hashing\n".to_vec();
            v.extend_from_slice(b"void init() { SHA256_Init(&ctx); }\n");
            v
        }),
        // Deeply nested path
        ("turmoil/a/b/c/d/e/f/g/h/i/j/deep.c",
         b"// Deep file\nAES_encrypt(data, out, &key);\n".to_vec()),
        // File with no extension
        ("turmoil/Makefile",
         b"# Build system\nCRYPTO_LIB = -lcrypto\nAES_FLAGS = -maes\n".to_vec()),
        // Near-miss patterns (should NOT match whole-word patterns)
        ("turmoil/decoys.c",
         b"// These should not trigger whole-word matches\n\
           int xAES_encrypty = 0;\n\
           char not_RSA_new_thing[] = \"\";\n\
           // But substring matches will find AES_encrypt inside xAES_encrypty\n".to_vec()),
        // Mixed line endings
        ("turmoil/mixed_eol.c",
         b"line1 SHA256_Init\nline2 AES_encrypt\r\nline3 RSA_sign\rline4 end\n".to_vec()),
        // File that's all comments
        ("turmoil/comments_only.c",
         b"/* AES_encrypt is mentioned in a comment */\n\
           // SHA256_Init also in a comment\n\
           /* But comments ARE code for crypto detection */\n".to_vec()),
    ]
}

// ---------------------------------------------------------------------------
// Test runner
// ---------------------------------------------------------------------------

/// Create a universe on disk and return the root path + expected evidence map.
fn materialize_universe(
    base: &Path,
    universe: &Universe,
) -> (PathBuf, HashMap<String, Vec<String>>) {
    let root = base.join(universe.name);
    let mut expected: HashMap<String, Vec<String>> = HashMap::new();

    for (rel_path, content, evidence_types) in &universe.files {
        let full_path = root.join(rel_path);
        if let Some(parent) = full_path.parent() {
            fs::create_dir_all(parent).unwrap();
        }
        fs::write(&full_path, content).unwrap();

        for &etype in evidence_types {
            expected
                .entry(etype.to_string())
                .or_default()
                .push(rel_path.to_string());
        }
    }

    (root, expected)
}

fn add_turmoil(base: &Path) {
    for (rel_path, content) in turmoil_files() {
        let full_path = base.join(rel_path);
        if let Some(parent) = full_path.parent() {
            fs::create_dir_all(parent).unwrap();
        }
        fs::write(&full_path, content).unwrap();
    }
}

// ---------------------------------------------------------------------------
// The tests
// ---------------------------------------------------------------------------

#[test]
fn test_universe_coverage() {
    let dir = tempfile::tempdir().unwrap();
    let db = PatternDb::parse(UNIVERSE_PATTERNS, true).unwrap();

    let config = ScanConfig {
        keyword_db: db,
        api_db: None,
        source_files_only: false,
        quick: false,
        stop_after: None,
        scan_binary: false,
    };

    let universes = build_universes();
    let mut total_expected = 0usize;
    let mut total_found = 0usize;
    let mut failures: Vec<String> = Vec::new();

    for universe in &universes {
        let (root, expected) = materialize_universe(dir.path(), universe);
        let result = scan_package(&root, &config).unwrap();

        // Collect found evidence types
        let mut found_types: HashMap<String, usize> = HashMap::new();
        for fr in &result.file_results {
            for m in &fr.matches {
                *found_types.entry(m.inner.evidence_type.clone()).or_default() += 1;
            }
        }

        // Verify every expected evidence type was found
        for (etype, files) in &expected {
            total_expected += 1;
            if found_types.contains_key(etype) {
                total_found += 1;
            } else {
                failures.push(format!(
                    "MISS: {} [{}] expected {} in {:?}",
                    universe.name, universe.domain, etype, files
                ));
            }
        }

        // Verify no crypto was found in files marked as clean (empty expected)
        for (_rel_path, _, evidence_types) in &universe.files {
            if evidence_types.is_empty() {
                // This file should have zero or minimal matches (noise files may have
                // substring matches for "crypt" etc. which is fine)
                continue;
            }
        }
    }

    if !failures.is_empty() {
        panic!(
            "Universe coverage failures ({}/{} types found):\n{}",
            total_found,
            total_expected,
            failures.join("\n")
        );
    }

    assert!(
        total_expected >= 20,
        "Expected at least 20 evidence type assertions, got {}",
        total_expected
    );

    eprintln!(
        "Universe test: {}/{} evidence types found across {} universes",
        total_found,
        total_expected,
        universes.len()
    );
}

#[test]
fn test_turmoil_no_panics() {
    let dir = tempfile::tempdir().unwrap();
    let db = PatternDb::parse(UNIVERSE_PATTERNS, true).unwrap();

    let config = ScanConfig {
        keyword_db: db,
        api_db: None,
        source_files_only: false,
        quick: false,
        stop_after: None,
        scan_binary: true, // scan binary turmoil files too
    };

    // Add all turmoil files
    add_turmoil(dir.path());

    // Must not panic on any turmoil input
    let result = scan_package(dir.path(), &config).unwrap();

    // Should find some matches (Latin-1 file has SHA256_Init, deep file has AES_encrypt, etc.)
    assert!(
        result.total_matches > 0,
        "turmoil should produce some matches"
    );

    // The deeply nested file should be found
    let deep_found = result
        .file_results
        .iter()
        .any(|f| f.path.contains("deep.c"));
    assert!(deep_found, "deeply nested file should be scanned");

    // Latin-1 encoded file should be found
    let latin1_found = result
        .file_results
        .iter()
        .any(|f| f.path.contains("latin1.c"));
    assert!(latin1_found, "Latin-1 encoded file should be scanned");

    // Comment-only file should still find crypto keywords
    let comments_found = result
        .file_results
        .iter()
        .any(|f| f.path.contains("comments_only.c"));
    assert!(
        comments_found,
        "comments with crypto keywords should be detected"
    );

    eprintln!(
        "Turmoil test: {} files scanned, {} matches, no panics",
        result.files_scanned, result.total_matches
    );
}

#[test]
fn test_thousand_universes() {
    let dir = tempfile::tempdir().unwrap();
    let db = PatternDb::parse(UNIVERSE_PATTERNS, true).unwrap();

    let config = ScanConfig {
        keyword_db: db,
        api_db: None,
        source_files_only: false,
        quick: false,
        stop_after: None,
        scan_binary: false,
    };

    // Templates for generating 1000 universes
    let crypto_snippets: Vec<(&str, &str)> = vec![
        ("AES", "AES_set_encrypt_key(key, 256, &aes);\nAES_encrypt(in, out, &aes);\n"),
        ("RSA", "RSA *rsa = RSA_new();\nRSA_generate_key(2048, 65537, NULL, NULL);\n"),
        ("SHA2", "SHA256_Init(&ctx);\nSHA256_Update(&ctx, data, len);\nSHA256_Final(hash, &ctx);\n"),
        ("SHA1", "SHA1_Init(&ctx);\nSHA1_Update(&ctx, data, len);\nSHA1_Final(hash, &ctx);\n"),
        ("MD5", "MD5_Init(&ctx);\nMD5_Update(&ctx, data, len);\nMD5_Final(hash, &ctx);\n"),
        ("DES", "DES_set_key(&key, &ks);\nDES_ecb_encrypt(&in, &out, &ks, DES_ENCRYPT);\n"),
        ("ChaCha20", "CHACHA20 cipher;\nChaCha20_ctr32(out, in, len, key, nonce, 0);\n"),
        ("Blowfish", "BLOWFISH ctx;\nBF_set_key(&ctx, len, key);\nBF_encrypt(data, &ctx);\n"),
        ("ECC", "EC_KEY_new();\nECDSA_sign(0, hash, 32, sig, &siglen, key);\n"),
        ("DH", "DH_generate_key(dh);\nDH_compute_key(secret, peer, dh);\n"),
        ("HMAC", "HMAC_CTX_new();\nHMAC_Init(&ctx, key, klen, EVP_sha256());\nHMAC_Final(&ctx, mac, &len);\n"),
        ("TLS", "SSL_library_init();\nSSL_CTX_new(TLS_method());\nSSL_connect(ssl);\n"),
        ("Kerberos", "KERBEROS auth;\nkrb5_init_context(&ctx);\n"),
        ("Camellia", "CAMELLIA key;\nCamellia_set_key(raw, 256, &key);\n"),
        ("Twofish", "TWOFISH_KEY key;\nTwofish_encrypt(key, in, out);\n"),
        ("KASUMI", "KASUMI ctx;\nkasumi_encrypt(&ctx, data, out);\n"),
        ("RC4", "RC4 stream;\nRC4_set_key(&stream, len, key);\n"),
        ("SHA3", "SHA3_256(data, len, hash);\nKECCAK state;\n"),
        ("Serpent", "SERPENT ctx;\nserpent_encrypt(&ctx, block, out);\n"),
        ("BLAKE", "BLAKE2 state;\nblake2b_init(&state, 32);\n"),
    ];

    let root = dir.path().join("megaverse");
    let mut expected_types: HashMap<String, usize> = HashMap::new();

    // Generate 1000 universes
    for i in 0..1000 {
        let (crypto_name, snippet) = &crypto_snippets[i % crypto_snippets.len()];
        let universe_dir = root.join(format!("universe_{:04}", i));
        let src_dir = universe_dir.join("src");
        fs::create_dir_all(&src_dir).unwrap();

        // Main crypto file
        let mut f = fs::File::create(src_dir.join("crypto.c")).unwrap();
        writeln!(f, "// Universe {} - {} crypto", i, crypto_name).unwrap();
        f.write_all(snippet.as_bytes()).unwrap();

        // Noise file (no crypto)
        let mut noise = fs::File::create(src_dir.join("main.c")).unwrap();
        writeln!(noise, "int main() {{ return {}; }}", i).unwrap();

        *expected_types.entry(crypto_name.to_string()).or_default() += 1;
    }

    // Scan the entire megaverse
    let result = scan_package(&root, &config).unwrap();

    // Every crypto type should have been found
    let mut found_types: HashMap<String, usize> = HashMap::new();
    for fr in &result.file_results {
        for m in &fr.matches {
            // Map evidence type back to short name
            let short = m.inner.evidence_type.rsplit('/').next().unwrap_or("?");
            *found_types.entry(short.to_string()).or_default() += 1;
        }
    }

    let mut missing = Vec::new();
    for (name, count) in &expected_types {
        if !found_types.values().any(|&v| v > 0) {
            missing.push(format!("{} (expected {} universes)", name, count));
        }
    }

    assert!(
        result.files_scanned >= 2000,
        "Should scan at least 2000 files (2 per universe), got {}",
        result.files_scanned
    );
    assert!(
        result.files_with_matches >= 900,
        "Should match at least 900 of 1000 crypto files, got {}",
        result.files_with_matches
    );

    // Verify toon output works on large result
    let toon = kindi::toon::to_toon(&result, 0.1, 20);
    assert!(toon.contains("@kindi"));
    assert!(toon.contains("#by_type"));
    assert!(toon.contains("#weak"));
    assert!(toon.len() < 5000, "toon should be under 5KB, got {}", toon.len());

    eprintln!(
        "Megaverse: {} universes, {} files scanned, {} matches, {} evidence types, toon={} bytes",
        1000,
        result.files_scanned,
        result.total_matches,
        found_types.len(),
        toon.len(),
    );
}
