//! Integration tests for M7P4 Operational Visibility — signal/WAL + index health metrics.
//!
//! - Task 02: Signal + WAL metrics
//! - Task 03: Index health metrics
//!
//! Every test in this file is gated behind the `metrics` feature.
#![allow(clippy::too_many_lines, clippy::unwrap_used)]

#[cfg(feature = "metrics")]
use std::time::Duration;

#[cfg(feature = "metrics")]
use tidaldb::{
    TidalDb,
    schema::{DecaySpec, EntityId, EntityKind, Timestamp, Window},
};

// ── Prometheus output helpers ────────────────────────────────────────────────

/// Extract the numeric value for a metric from Prometheus text output.
///
/// Looks for a line that starts with `name` (not a comment), then parses
/// the trailing float. Returns `None` if the metric is absent.
#[cfg(feature = "metrics")]
fn prometheus_value(prom: &str, name: &str) -> Option<f64> {
    prom.lines()
        .filter(|l| !l.starts_with('#'))
        .find(|l| l.starts_with(name) && l[name.len()..].starts_with(' '))
        .and_then(|l| l.split_whitespace().last())
        .and_then(|v| v.parse().ok())
}

// ── Shared test schema ───────────────────────────────────────────────────────

#[cfg(feature = "metrics")]
fn build_test_schema() -> tidaldb::schema::Schema {
    use tidaldb::{AgentPolicy, schema::SchemaBuilder};

    let mut builder = SchemaBuilder::new();
    let _ = builder
        .signal(
            "view",
            EntityKind::Item,
            DecaySpec::Exponential {
                half_life: Duration::from_secs(7 * 24 * 3600),
            },
        )
        .windows(&[Window::OneHour])
        .add();
    let _ = builder
        .signal(
            "like",
            EntityKind::Item,
            DecaySpec::Exponential {
                half_life: Duration::from_secs(30 * 24 * 3600),
            },
        )
        .windows(&[Window::OneHour])
        .add();
    builder.session_policy(
        "default",
        AgentPolicy {
            allowed_signals: vec!["view".to_string(), "like".to_string()],
            denied_signals: vec![],
            max_session_duration: Duration::from_secs(3600),
            max_signals_per_session: 1000,
        },
    );
    builder.build().unwrap()
}

// ── Task 02: Signal + WAL Metrics ───────────────────────────────────────────

#[cfg(feature = "metrics")]
#[test]
fn signal_write_increments_counter() {
    let schema = build_test_schema();
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema)
        .open()
        .unwrap();

    let before = prometheus_value(
        &db.metrics().render_prometheus(),
        "tidaldb_signal_writes_total",
    )
    .unwrap_or(0.0);
    db.signal("view", EntityId::new(1), 1.0, Timestamp::now())
        .unwrap();
    db.signal("like", EntityId::new(2), 1.0, Timestamp::now())
        .unwrap();
    db.signal("view", EntityId::new(3), 0.5, Timestamp::now())
        .unwrap();
    let after = prometheus_value(
        &db.metrics().render_prometheus(),
        "tidaldb_signal_writes_total",
    )
    .unwrap();

    assert!(
        ((after - before) - 3.0).abs() < 0.5,
        "signal_writes_total must increment once per signal write"
    );
}

#[cfg(feature = "metrics")]
#[test]
fn signal_write_latency_appears_in_prometheus() {
    let schema = build_test_schema();
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema)
        .open()
        .unwrap();

    for i in 1u64..=3 {
        db.signal("view", EntityId::new(i), 1.0, Timestamp::now())
            .unwrap();
    }

    let prom = db.metrics().render_prometheus();
    assert!(
        prom.contains("tidaldb_signal_write_latency_us"),
        "Prometheus output must include signal write latency histogram"
    );
    assert!(
        prom.contains("tidaldb_signal_writes_total"),
        "Prometheus output must include signal_writes_total counter"
    );
    assert!(
        prom.contains("tidaldb_wal_lag_bytes"),
        "Prometheus output must include wal_lag_bytes gauge"
    );
    assert!(
        prom.contains("tidaldb_checkpoint_age_seconds"),
        "Prometheus output must include checkpoint_age_seconds gauge"
    );
}

// ── Task 03: Index Health Metrics ────────────────────────────────────────────

#[cfg(feature = "metrics")]
#[test]
fn prometheus_output_contains_all_index_gauge_names() {
    let schema = build_test_schema();
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema)
        .open()
        .unwrap();

    let prom = db.metrics().render_prometheus();

    assert!(
        prom.contains("tidaldb_tantivy_segment_count"),
        "Prometheus output must include tantivy_segment_count gauge"
    );
    assert!(
        prom.contains("tidaldb_tantivy_indexed_docs"),
        "Prometheus output must include tantivy_indexed_docs gauge"
    );
    assert!(
        prom.contains("tidaldb_usearch_vector_count"),
        "Prometheus output must include usearch_vector_count gauge"
    );
    assert!(
        prom.contains("tidaldb_usearch_index_size_bytes"),
        "Prometheus output must include usearch_index_size_bytes gauge"
    );
    assert!(
        prom.contains("tidaldb_bitmap_index_cardinality"),
        "Prometheus output must include bitmap_index_cardinality gauge"
    );
}
