#![cfg(feature = "observability")]

use std::{sync::Arc, time::Duration};

use rs_zero::observability::{
    HttpMetricLabels, MetricsRegistry, RedisMetricLabels, SqlMetricLabels,
};

#[test]
fn metrics_registry_handles_concurrent_fixed_label_histograms() {
    let registry = MetricsRegistry::new();
    let registry = Arc::new(registry);
    let workers = env_usize("RS_ZERO_METRICS_STRESS_WORKERS", 8);
    let iterations = env_usize("RS_ZERO_METRICS_STRESS_ITERATIONS", 1_000);

    let mut handles = Vec::new();
    for worker in 0..workers {
        let registry = registry.clone();
        handles.push(std::thread::spawn(move || {
            for index in 0..iterations {
                registry.record_http_request(
                    HttpMetricLabels::new("GET", "/users/{id}", 200),
                    Duration::from_micros((index % 2_500) as u64),
                );
                registry.record_sql_query(
                    SqlMetricLabels::new("sqlite", "users", "find_by_id", "select", "success"),
                    Duration::from_micros((worker + 1) as u64),
                );
                registry.record_redis_command(
                    RedisMetricLabels::new("GET", "primary", "success"),
                    Duration::from_micros((index % 100) as u64),
                );
            }
        }));
    }
    for handle in handles {
        handle.join().expect("metrics stress worker");
    }

    let text = registry.render_prometheus();
    let expected = workers * iterations;
    assert!(text.contains(&format!(
        "rs_zero_http_requests_total{{method=\"GET\",route=\"/users/{{id}}\",status=\"200\"}} {expected}"
    )));
    assert!(text.contains(&format!(
        "rs_zero_sql_queries_total{{db_kind=\"sqlite\",repository=\"users\",method=\"find_by_id\",operation=\"select\",result=\"success\"}} {expected}"
    )));
    assert!(text.contains(&format!(
        "rs_zero_redis_commands_total{{command=\"GET\",shard=\"primary\",result=\"success\"}} {expected}"
    )));
    assert!(!text.contains("/users/42"));
    assert!(!text.contains("users:42"));
}

#[test]
fn metrics_registry_renders_bounded_label_churn_without_sensitive_values() {
    let registry = MetricsRegistry::new();
    let label_sets = env_usize("RS_ZERO_METRICS_STRESS_LABELS", 128);
    let iterations = env_usize("RS_ZERO_METRICS_STRESS_ITERATIONS", 1_000);

    for index in 0..iterations {
        let label = index % label_sets;
        registry.record_http_request(
            HttpMetricLabels::new("GET", format!("/stress/{label}"), 200),
            Duration::from_micros(label as u64),
        );
        registry.record_redis_command(
            RedisMetricLabels::new("SETEX", format!("shard-{label}"), "success"),
            Duration::from_micros(label as u64),
        );
    }

    let text = registry.render_prometheus();
    assert!(text.contains("rs_zero_http_request_duration_seconds_bucket"));
    assert!(text.contains("rs_zero_redis_command_duration_seconds_bucket"));
    assert!(text.len() > label_sets * 128);
    assert!(!text.contains("password"));
    assert!(!text.contains("redis://"));
    assert!(!text.contains("users:42"));
}

fn env_usize(name: &str, default: usize) -> usize {
    std::env::var(name)
        .ok()
        .and_then(|value| value.parse().ok())
        .unwrap_or(default)
}
