use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use pi_logger::observability::{
    FieldRuleConfig, FieldRuleOp, FuzzyMatchType, FuzzyRuleConfig, FuzzyRuleKind, LogFilterConfig,
    LogFilterOverrideConfig, SharedOrderedFilter,
};
use serde_json::json;
use tracing::Dispatch;
use tracing_subscriber::{layer::SubscriberExt, Layer};

#[derive(Clone, Copy)]
struct NoopLayer;

impl<S> Layer<S> for NoopLayer where S: tracing::Subscriber {}

fn dispatch(config: LogFilterConfig) -> Dispatch {
    let filter = SharedOrderedFilter::new(config).expect("benchmark filter should compile");
    Dispatch::new(tracing_subscriber::registry().with(NoopLayer.with_filter(filter)))
}

fn baseline_dispatch() -> Dispatch {
    Dispatch::new(tracing_subscriber::registry().with(NoopLayer))
}

fn default_config(level: &str) -> LogFilterConfig {
    LogFilterConfig {
        default_level: level.to_string(),
        overrides: Vec::new(),
    }
}

fn override_rule(
    name: String,
    pattern: String,
    field_rules: Vec<FieldRuleConfig>,
) -> LogFilterOverrideConfig {
    override_rule_with_match(name, FuzzyMatchType::Contains, pattern, field_rules)
}

fn override_rule_with_match(
    name: String,
    match_type: FuzzyMatchType,
    pattern: String,
    field_rules: Vec<FieldRuleConfig>,
) -> LogFilterOverrideConfig {
    LogFilterOverrideConfig {
        name,
        enabled: Some(true),
        level: "debug".to_string(),
        priority: 100,
        fuzzy_rules: vec![FuzzyRuleConfig {
            kind: FuzzyRuleKind::Target,
            match_type,
            pattern,
            level: None,
        }],
        field_rules,
    }
}

fn overrides_config(count: usize, matching_last: bool) -> LogFilterConfig {
    let mut overrides = (0..count)
        .map(|index| {
            override_rule(
                format!("rule_{index}"),
                format!("unmatched_{index}"),
                Vec::new(),
            )
        })
        .collect::<Vec<_>>();

    if matching_last {
        overrides.push(LogFilterOverrideConfig {
            priority: 0,
            ..override_rule(
                "matching_rule".to_string(),
                "bench_target".to_string(),
                Vec::new(),
            )
        });
    }

    LogFilterConfig {
        default_level: "info".to_string(),
        overrides,
    }
}

fn indexed_overrides_config(count: usize, matching_last: bool) -> LogFilterConfig {
    let mut overrides = (0..count)
        .map(|index| {
            override_rule_with_match(
                format!("rule_{index}"),
                FuzzyMatchType::Exact,
                format!("unmatched_{index}"),
                Vec::new(),
            )
        })
        .collect::<Vec<_>>();

    if matching_last {
        overrides.push(LogFilterOverrideConfig {
            priority: 0,
            ..override_rule_with_match(
                "matching_rule".to_string(),
                FuzzyMatchType::Exact,
                "bench_target".to_string(),
                Vec::new(),
            )
        });
    }

    LogFilterConfig {
        default_level: "info".to_string(),
        overrides,
    }
}

fn prefix_overrides_config(count: usize, matching_last: bool) -> LogFilterConfig {
    let buckets = ["alpha", "beta", "gamma", "delta", "epsilon"];
    let mut overrides = (0..count)
        .map(|index| {
            override_rule_with_match(
                format!("rule_{index}"),
                FuzzyMatchType::Prefix,
                format!("{}_{index}::", buckets[index % buckets.len()]),
                Vec::new(),
            )
        })
        .collect::<Vec<_>>();

    if matching_last {
        overrides.push(LogFilterOverrideConfig {
            priority: 0,
            ..override_rule_with_match(
                "matching_rule".to_string(),
                FuzzyMatchType::Prefix,
                "zeta::".to_string(),
                Vec::new(),
            )
        });
    }

    LogFilterConfig {
        default_level: "info".to_string(),
        overrides,
    }
}

fn field_config(rule_count: usize) -> LogFilterConfig {
    let mut field_rules = vec![FieldRuleConfig {
        field: "id".to_string(),
        op: FieldRuleOp::Eq,
        value: json!(100),
    }];

    if rule_count > 1 {
        field_rules.push(FieldRuleConfig {
            field: "msg".to_string(),
            op: FieldRuleOp::Contains,
            value: json!("decode"),
        });
    }

    LogFilterConfig {
        default_level: "info".to_string(),
        overrides: vec![override_rule(
            "field_rule".to_string(),
            "bench_target".to_string(),
            field_rules,
        )],
    }
}

fn bench_baseline(c: &mut Criterion) {
    let mut group = c.benchmark_group("observability/baseline");
    let dispatch = baseline_dispatch();

    group.bench_function("noop_layer_info", |b| {
        tracing::dispatcher::with_default(&dispatch, || {
            b.iter(|| tracing::info!(target: "bench_target", value = black_box(42_u64), "event"))
        });
    });

    group.finish();
}

fn bench_default_level(c: &mut Criterion) {
    let mut group = c.benchmark_group("observability/default_level");

    let enabled = dispatch(default_config("info"));
    group.bench_function("enabled_info", |b| {
        tracing::dispatcher::with_default(&enabled, || {
            b.iter(|| tracing::info!(target: "bench_target", value = black_box(42_u64), "event"))
        });
    });

    let disabled = dispatch(default_config("info"));
    group.bench_function("disabled_debug", |b| {
        tracing::dispatcher::with_default(&disabled, || {
            b.iter(|| tracing::debug!(target: "bench_target", value = black_box(42_u64), "event"))
        });
    });

    group.finish();
}

fn bench_level_quick_skip(c: &mut Criterion) {
    let mut group = c.benchmark_group("observability/level_quick_skip");

    let with_info_overrides = dispatch(overrides_config(100, true));
    group.bench_function("disabled_trace_with_100_info_overrides", |b| {
        tracing::dispatcher::with_default(&with_info_overrides, || {
            b.iter(|| tracing::trace!(target: "bench_target", "event"))
        });
    });

    group.finish();
}

fn bench_overrides(c: &mut Criterion) {
    let mut group = c.benchmark_group("observability/overrides");

    for count in [1_usize, 10, 100] {
        let miss = dispatch(overrides_config(count, false));
        group.bench_with_input(BenchmarkId::new("miss", count), &count, |b, _| {
            tracing::dispatcher::with_default(&miss, || {
                b.iter(|| tracing::debug!(target: "bench_target", "event"))
            });
        });

        let match_last = dispatch(overrides_config(count, true));
        group.bench_with_input(BenchmarkId::new("match_last", count), &count, |b, _| {
            tracing::dispatcher::with_default(&match_last, || {
                b.iter(|| tracing::debug!(target: "bench_target", "event"))
            });
        });
    }

    group.finish();
}

fn bench_indexed_overrides(c: &mut Criterion) {
    let mut group = c.benchmark_group("observability/indexed_overrides");

    for count in [1_usize, 10, 100] {
        let miss = dispatch(indexed_overrides_config(count, false));
        group.bench_with_input(BenchmarkId::new("exact_miss", count), &count, |b, _| {
            tracing::dispatcher::with_default(&miss, || {
                b.iter(|| tracing::debug!(target: "bench_target", "event"))
            });
        });

        let match_last = dispatch(indexed_overrides_config(count, true));
        group.bench_with_input(
            BenchmarkId::new("exact_match_last", count),
            &count,
            |b, _| {
                tracing::dispatcher::with_default(&match_last, || {
                    b.iter(|| tracing::debug!(target: "bench_target", "event"))
                });
            },
        );
    }

    group.finish();
}

fn bench_fields(c: &mut Criterion) {
    let mut group = c.benchmark_group("observability/fields");

    let one_rule = dispatch(field_config(1));
    group.bench_function("one_rule_match", |b| {
        tracing::dispatcher::with_default(&one_rule, || {
            b.iter(|| {
                tracing::debug!(
                    target: "bench_target",
                    id = black_box(100_u64),
                    msg = "decode payload",
                    "event"
                )
            })
        });
    });

    let one_rule_miss = dispatch(field_config(1));
    group.bench_function("one_rule_miss", |b| {
        tracing::dispatcher::with_default(&one_rule_miss, || {
            b.iter(|| {
                tracing::debug!(
                    target: "bench_target",
                    id = black_box(101_u64),
                    msg = "decode payload",
                    "event"
                )
            })
        });
    });

    let two_rules = dispatch(field_config(2));
    group.bench_function("two_rules_match", |b| {
        tracing::dispatcher::with_default(&two_rules, || {
            b.iter(|| {
                tracing::debug!(
                    target: "bench_target",
                    id = black_box(100_u64),
                    msg = "decode payload",
                    "event"
                )
            })
        });
    });

    let two_rules_extra_fields = dispatch(field_config(2));
    group.bench_function("two_rules_match_extra_fields", |b| {
        tracing::dispatcher::with_default(&two_rules_extra_fields, || {
            b.iter(|| {
                tracing::debug!(
                    target: "bench_target",
                    id = black_box(100_u64),
                    msg = "decode payload",
                    payload_len = black_box(128_usize),
                    protocol = "ton",
                    user = "bench-user",
                    retry = false,
                    "event"
                )
            })
        });
    });

    group.finish();
}

fn bench_reload(c: &mut Criterion) {
    let mut group = c.benchmark_group("observability/reload");

    let default_filter =
        SharedOrderedFilter::new(default_config("info")).expect("benchmark filter should compile");
    let mut use_debug = false;
    group.bench_function("default_level", |b| {
        b.iter(|| {
            use_debug = !use_debug;
            default_filter
                .reload_default_level(if use_debug { "debug" } else { "info" })
                .expect("default level reload should succeed");
        });
    });

    let toggle_filter = SharedOrderedFilter::new(overrides_config(10, true))
        .expect("benchmark filter should compile");
    let mut enabled = false;
    group.bench_function("set_override_enabled", |b| {
        b.iter(|| {
            enabled = !enabled;
            toggle_filter
                .set_override_enabled("matching_rule", enabled)
                .expect("override toggle should succeed");
        });
    });

    let reload_filter =
        SharedOrderedFilter::new(default_config("info")).expect("benchmark filter should compile");
    group.bench_function("reload_100_overrides", |b| {
        b.iter(|| {
            reload_filter
                .reload_filter(black_box(overrides_config(100, true)))
                .expect("override reload should succeed");
        });
    });

    group.finish();
}

fn bench_prefix_overrides(c: &mut Criterion) {
    let mut group = c.benchmark_group("observability/prefix_overrides");

    for count in [10_usize, 100] {
        let miss = dispatch(prefix_overrides_config(count, false));
        group.bench_with_input(BenchmarkId::new("prefix_miss", count), &count, |b, _| {
            tracing::dispatcher::with_default(&miss, || {
                b.iter(|| tracing::debug!(target: "zeta::worker", "event"))
            });
        });

        let match_last = dispatch(prefix_overrides_config(count, true));
        group.bench_with_input(
            BenchmarkId::new("prefix_match_last", count),
            &count,
            |b, _| {
                tracing::dispatcher::with_default(&match_last, || {
                    b.iter(|| tracing::debug!(target: "zeta::worker", "event"))
                });
            },
        );
    }

    group.finish();
}

criterion_group!(
    benches,
    bench_baseline,
    bench_default_level,
    bench_level_quick_skip,
    bench_overrides,
    bench_indexed_overrides,
    bench_prefix_overrides,
    bench_fields,
    bench_reload
);
criterion_main!(benches);
