use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};

use fafb_q::wildcard::{SubscriptionId, SubscriptionTrie, TopicPattern};

/// Build a realistic pattern like "segment0.segment1.*.segment3" with a wildcard at the given depth.
fn make_pattern(prefix_depth: usize, suffix_depth: usize, wildcard: &str) -> String {
    let mut parts: Vec<String> = (0..prefix_depth).map(|i| format!("seg{i}")).collect();
    parts.push(wildcard.to_owned());
    for i in 0..suffix_depth {
        parts.push(format!("tail{i}"));
    }
    parts.join(".")
}

/// Build a concrete topic like "seg0.seg1.anytoken.tail0" that matches a pattern from make_pattern.
fn make_topic(prefix_depth: usize, suffix_depth: usize, mid_token: &str) -> String {
    let mut parts: Vec<String> = (0..prefix_depth).map(|i| format!("seg{i}")).collect();
    parts.push(mid_token.to_owned());
    for i in 0..suffix_depth {
        parts.push(format!("tail{i}"));
    }
    parts.join(".")
}

// ---------------------------------------------------------------------------
// Benchmark: TopicPattern::parse
// ---------------------------------------------------------------------------

fn bench_pattern_parse(c: &mut Criterion) {
    let mut group = c.benchmark_group("wildcard_pattern_parse");

    group.bench_function("exact/3_segments", |b| {
        b.iter(|| TopicPattern::parse("orders.us.east").unwrap());
    });

    group.bench_function("single_star/3_segments", |b| {
        b.iter(|| TopicPattern::parse("orders.*.east").unwrap());
    });

    group.bench_function("multi_trail/2_segments", |b| {
        b.iter(|| TopicPattern::parse("orders.>").unwrap());
    });

    group.bench_function("deep/10_segments", |b| {
        let topic = "a.b.c.d.e.f.g.h.*.j";
        b.iter(|| TopicPattern::parse(topic).unwrap());
    });

    group.finish();
}

// ---------------------------------------------------------------------------
// Benchmark: TopicPattern::matches
// ---------------------------------------------------------------------------

fn bench_pattern_matches(c: &mut Criterion) {
    let mut group = c.benchmark_group("wildcard_pattern_matches");

    let exact = TopicPattern::parse("orders.us.east").unwrap();
    let star = TopicPattern::parse("orders.*.east").unwrap();
    let trail = TopicPattern::parse("orders.>").unwrap();

    group.bench_function("exact_hit", |b| {
        b.iter(|| exact.matches("orders.us.east"));
    });

    group.bench_function("exact_miss", |b| {
        b.iter(|| exact.matches("orders.eu.west"));
    });

    group.bench_function("star_hit", |b| {
        b.iter(|| star.matches("orders.eu.east"));
    });

    group.bench_function("star_miss", |b| {
        b.iter(|| star.matches("orders.eu.west"));
    });

    group.bench_function("trail_hit_2seg", |b| {
        b.iter(|| trail.matches("orders.us"));
    });

    group.bench_function("trail_hit_4seg", |b| {
        b.iter(|| trail.matches("orders.us.east.dc1"));
    });

    group.finish();
}

// ---------------------------------------------------------------------------
// Benchmark: SubscriptionTrie::insert
// ---------------------------------------------------------------------------

fn bench_trie_insert(c: &mut Criterion) {
    let mut group = c.benchmark_group("wildcard_trie_insert");

    for count in [100, 1_000, 10_000] {
        // Pre-parse patterns — keep depth small to stay under 255-byte limit
        let patterns: Vec<TopicPattern> = (0..count)
            .map(|i| {
                let prefix = (i % 5) + 1;
                let suffix = i % 3;
                TopicPattern::parse(&make_pattern(prefix, suffix, "*")).unwrap()
            })
            .collect();

        group.bench_with_input(BenchmarkId::new("patterns", count), &patterns, |b, pats| {
            b.iter(|| {
                let mut trie = SubscriptionTrie::new();
                for (i, pat) in pats.iter().enumerate() {
                    trie.insert(pat, SubscriptionId(i as u64));
                }
                trie
            });
        });
    }

    group.finish();
}

// ---------------------------------------------------------------------------
// Benchmark: SubscriptionTrie::matching  (the hot-path benchmark)
// ---------------------------------------------------------------------------

fn bench_trie_matching(c: &mut Criterion) {
    let mut group = c.benchmark_group("wildcard_trie_matching");
    group.sample_size(50);

    // Create diverse patterns: mix of *, >, and exact at varying depths
    for num_patterns in [100, 1_000, 10_000] {
        let mut trie = SubscriptionTrie::new();

        for i in 0..num_patterns {
            let pat = match i % 3 {
                0 => {
                    // Star pattern: "prefix{i%5}.*.suffix{i%3}"
                    let p = (i % 5) + 1;
                    let s = i % 3;
                    TopicPattern::parse(&make_pattern(p, s, "*")).unwrap()
                }
                1 => {
                    // Trail pattern: "prefix{i%5}.>"
                    let depth = (i % 5) + 1;
                    let parts: Vec<String> = (0..depth).map(|d| format!("seg{d}")).collect();
                    TopicPattern::parse(&format!("{}.>", parts.join("."))).unwrap()
                }
                _ => {
                    // Exact pattern
                    let depth = (i % 5) + 2;
                    let parts: Vec<String> = (0..depth).map(|d| format!("seg{d}")).collect();
                    TopicPattern::parse(&parts.join(".")).unwrap()
                }
            };
            trie.insert(&pat, SubscriptionId(i as u64));
        }

        // Topic that matches SOME patterns (not all)
        let topic_hit = make_topic(3, 2, "anything");
        // Topic that matches NO patterns (different root)
        let topic_miss = "zzz.nomatch.here";

        group.bench_with_input(
            BenchmarkId::new("hit", num_patterns),
            &(&trie, &topic_hit),
            |b, (trie, topic)| {
                b.iter(|| trie.matching(topic));
            },
        );

        group.bench_with_input(
            BenchmarkId::new("miss", num_patterns),
            &(&trie, topic_miss),
            |b, (trie, topic)| {
                b.iter(|| trie.matching(topic));
            },
        );
    }

    group.finish();
}

// ---------------------------------------------------------------------------
// Benchmark: SubscriptionTrie::remove
// ---------------------------------------------------------------------------

fn bench_trie_remove(c: &mut Criterion) {
    let mut group = c.benchmark_group("wildcard_trie_remove");

    let num_patterns = 1_000;
    let patterns: Vec<TopicPattern> = (0..num_patterns)
        .map(|i| {
            let prefix = (i % 5) + 1;
            TopicPattern::parse(&make_pattern(prefix, i % 3, "*")).unwrap()
        })
        .collect();

    group.bench_function("remove_1000", |b| {
        b.iter_with_setup(
            || {
                let mut trie = SubscriptionTrie::new();
                for (i, pat) in patterns.iter().enumerate() {
                    trie.insert(pat, SubscriptionId(i as u64));
                }
                trie
            },
            |mut trie| {
                for (i, pat) in patterns.iter().enumerate() {
                    trie.remove(pat, SubscriptionId(i as u64));
                }
            },
        );
    });

    group.finish();
}

// ---------------------------------------------------------------------------
// Benchmark: Mixed insert + match workload (realistic scenario)
// ---------------------------------------------------------------------------

fn bench_trie_mixed_workload(c: &mut Criterion) {
    let mut group = c.benchmark_group("wildcard_trie_mixed");
    group.sample_size(20);

    // Pre-build a trie with 10K patterns, then do 1000 match operations
    let num_patterns = 10_000;
    let mut trie = SubscriptionTrie::new();
    for i in 0..num_patterns {
        let pat = match i % 4 {
            0 => TopicPattern::parse(&format!("svc{}.*.events", i % 100)).unwrap(),
            1 => TopicPattern::parse(&format!("svc{}.>", i % 100)).unwrap(),
            2 => TopicPattern::parse(&format!("svc{}.orders.*", i % 100)).unwrap(),
            _ => TopicPattern::parse(&format!("svc{}.orders.region{}", i % 100, i % 10)).unwrap(),
        };
        trie.insert(&pat, SubscriptionId(i as u64));
    }

    // Pre-build concrete topics for matching
    let topics: Vec<String> = (0..1_000)
        .map(|i| format!("svc{}.orders.region{}", i % 100, i % 10))
        .collect();

    group.bench_function("10k_patterns_1k_lookups", |b| {
        b.iter(|| {
            let mut total_matches = 0usize;
            for topic in &topics {
                total_matches += trie.matching(topic).len();
            }
            total_matches
        });
    });

    group.finish();
}

// ---------------------------------------------------------------------------
// Benchmark: contains_wildcard() utility
// ---------------------------------------------------------------------------

fn bench_contains_wildcard(c: &mut Criterion) {
    use fafb_q::wildcard::contains_wildcard;

    let mut group = c.benchmark_group("wildcard_contains_check");

    group.bench_function("no_wildcard", |b| {
        b.iter(|| contains_wildcard("orders.us.east.dc1"));
    });

    group.bench_function("has_star", |b| {
        b.iter(|| contains_wildcard("orders.*.east"));
    });

    group.bench_function("has_trail", |b| {
        b.iter(|| contains_wildcard("orders.>"));
    });

    group.finish();
}

criterion_group!(
    benches,
    bench_pattern_parse,
    bench_pattern_matches,
    bench_trie_insert,
    bench_trie_matching,
    bench_trie_remove,
    bench_trie_mixed_workload,
    bench_contains_wildcard,
);
criterion_main!(benches);
