use super::*;

#[test]
fn new_creates_healthy_state() {
    let state = MetricsState::new();
    assert!(state.health_ok.load(Ordering::Relaxed));
}

#[test]
fn uptime_is_non_negative() {
    let state = MetricsState::new();
    assert!(state.uptime_seconds() >= 0.0);
}

#[test]
fn health_ok_value_returns_one_when_healthy() {
    let state = MetricsState::new();
    assert!((state.health_ok_value() - 1.0).abs() < f64::EPSILON);
}

#[test]
fn health_ok_value_returns_zero_when_degraded() {
    let state = MetricsState::new();
    state.health_ok.store(false, Ordering::Relaxed);
    assert!(state.health_ok_value().abs() < f64::EPSILON);
}

#[test]
fn render_prometheus_contains_expected_metrics() {
    let state = MetricsState::new();
    let output = state.render_prometheus();
    assert!(output.contains("tidaldb_uptime_seconds"));
    assert!(output.contains("tidaldb_health_ok"));
    assert!(output.contains("tidaldb_info"));
    assert!(output.contains("partition_id=\"0\""));
}

#[test]
fn render_healthz_contains_expected_fields() {
    let state = MetricsState::new();
    let output = state.render_healthz();
    assert!(output.contains("\"status\":\"ok\""));
    assert!(output.contains("\"uptime_seconds\":"));
    assert!(output.contains("\"version\":"));
    assert!(output.contains("\"build_hash\":"));
}

#[test]
fn render_healthz_degraded() {
    let state = MetricsState::new();
    state.health_ok.store(false, Ordering::Relaxed);
    let output = state.render_healthz();
    assert!(output.contains("\"status\":\"degraded\""));
}

#[cfg(feature = "metrics")]
#[test]
fn metrics_state_renders_signal_metrics() {
    let state = MetricsState::new();
    state.signal_writes_total.store(42, Ordering::Relaxed);
    state.signal_hot_entries.store(100, Ordering::Relaxed);
    state.wal_lag_bytes.store(8192, Ordering::Relaxed);
    state
        .wal_compacted_segments_total
        .store(3, Ordering::Relaxed);

    let output = state.render_prometheus();

    assert!(
        output.contains("tidaldb_signal_writes_total"),
        "missing signal_writes_total: {output}"
    );
    assert!(output.contains("42"), "missing value 42: {output}");
    assert!(
        output.contains("tidaldb_signal_hot_entries"),
        "missing signal_hot_entries: {output}"
    );
    assert!(output.contains("100"), "missing value 100: {output}");
    assert!(
        output.contains("tidaldb_wal_lag_bytes"),
        "missing wal_lag_bytes: {output}"
    );
    assert!(output.contains("8192"), "missing value 8192: {output}");
    assert!(
        output.contains("tidaldb_wal_compacted_segments_total"),
        "missing wal_compacted_segments_total: {output}"
    );
    assert!(
        output.contains("tidaldb_checkpoint_age_seconds"),
        "missing checkpoint_age_seconds: {output}"
    );
    assert!(
        output.contains("tidaldb_signal_write_latency_us"),
        "missing signal_write_latency_us histogram: {output}"
    );
}

#[cfg(feature = "metrics")]
#[test]
fn render_prometheus_contains_index_metrics() {
    let m = MetricsState::new();
    m.tantivy_segment_count.store(3, Ordering::Relaxed);
    m.tantivy_indexed_docs.store(10000, Ordering::Relaxed);
    m.usearch_vector_count.store(500, Ordering::Relaxed);
    m.usearch_index_size_bytes
        .store(1_048_576, Ordering::Relaxed);
    m.bitmap_index_cardinality.store(42, Ordering::Relaxed);

    let prom = m.render_prometheus();
    assert!(
        prom.contains("tidaldb_tantivy_segment_count 3"),
        "missing tantivy_segment_count: {prom}"
    );
    assert!(
        prom.contains("tidaldb_tantivy_indexed_docs 10000"),
        "missing tantivy_indexed_docs: {prom}"
    );
    assert!(
        prom.contains("tidaldb_usearch_vector_count 500"),
        "missing usearch_vector_count: {prom}"
    );
    assert!(
        prom.contains("tidaldb_usearch_index_size_bytes 1048576"),
        "missing usearch_index_size_bytes: {prom}"
    );
    assert!(
        prom.contains("tidaldb_bitmap_index_cardinality 42"),
        "missing bitmap_index_cardinality: {prom}"
    );
}

#[cfg(feature = "metrics")]
#[test]
fn active_sessions_tracks_lifecycle() {
    let state = MetricsState::new();
    state.active_sessions.fetch_add(1, Ordering::Relaxed);
    state.active_sessions.fetch_add(1, Ordering::Relaxed);
    assert_eq!(state.active_sessions.load(Ordering::Relaxed), 2);
    state.active_sessions.fetch_sub(1, Ordering::Relaxed);
    assert_eq!(state.active_sessions.load(Ordering::Relaxed), 1);
}

#[cfg(feature = "metrics")]
#[test]
fn degradation_level_renders_correctly() {
    let state = MetricsState::new();
    state.degradation_level.store(2, Ordering::Relaxed);
    let output = state.render_prometheus();
    assert!(
        output.contains("tidaldb_degradation_level"),
        "missing tidaldb_degradation_level: {output}"
    );
}

#[cfg(feature = "metrics")]
#[test]
fn render_prometheus_contains_session_metrics() {
    let state = MetricsState::new();
    let prom = state.render_prometheus();
    assert!(
        prom.contains("tidaldb_active_sessions"),
        "missing active_sessions: {prom}"
    );
    assert!(
        prom.contains("tidaldb_closed_sessions_total"),
        "missing closed_sessions_total: {prom}"
    );
    assert!(
        prom.contains("tidaldb_session_auto_closed_total"),
        "missing session_auto_closed_total: {prom}"
    );
    assert!(
        prom.contains("tidaldb_rate_limited_total"),
        "missing rate_limited_total: {prom}"
    );
    assert!(
        prom.contains("tidaldb_degradation_level"),
        "missing degradation_level: {prom}"
    );
}

#[cfg(feature = "metrics")]
#[test]
fn metrics_state_checkpoint_age_zero_when_no_checkpoint() {
    let state = MetricsState::new();
    // last_checkpoint_ns is 0 (default) -- checkpoint_age should be 0.
    let output = state.render_prometheus();
    // Find the checkpoint_age_seconds line and verify it's 0.
    let age_line = output
        .lines()
        .find(|l| l.starts_with("tidaldb_checkpoint_age_seconds "))
        .expect("missing checkpoint_age_seconds line");
    assert!(
        age_line.contains(" 0"),
        "checkpoint age should be 0 when no checkpoint: {age_line}"
    );
}

// ── Feature-flag verification tests (m7p4, task-07) ─────────────────

/// `QueryStats` is NOT feature-gated -- always available regardless of
/// whether the `metrics` feature is enabled.
#[test]
fn query_stats_always_available() {
    use crate::query::QueryStats;
    let stats = QueryStats::new("test".to_owned());
    assert_eq!(stats.profile_name, "test");
    assert_eq!(stats.total_time_us, 0);
}

/// Base `MetricsState` fields (`uptime_seconds`, `health_ok_value`) work
/// without the `metrics` feature -- they are unconditionally compiled.
#[test]
fn metrics_state_base_always_available() {
    let state = MetricsState::new();
    assert!(state.uptime_seconds() >= 0.0);
    assert!((state.health_ok_value() - 1.0).abs() < f64::EPSILON);
}

/// Feature-gated counters (`signal_writes_total`, etc.) only exist when
/// the `metrics` feature is enabled -- this test proves they compile and
/// are functional.
#[cfg(feature = "metrics")]
#[test]
fn metrics_feature_counters_exist() {
    let state = MetricsState::new();
    state.signal_writes_total.fetch_add(1, Ordering::Relaxed);
    assert_eq!(state.signal_writes_total.load(Ordering::Relaxed), 1);
}
