//! Integration tests for M7P4 Operational Visibility — `QueryStats` + Prometheus format.
//!
//! - Task 01: `QueryStats` — per-query execution statistics attached to every result
//! - Task 05: Prometheus exposition format correctness
#![allow(clippy::too_many_lines, clippy::unwrap_used)]

use std::{collections::HashMap, time::Duration};

use tidaldb::{
    TidalDb,
    schema::{DecaySpec, EntityId, EntityKind, Timestamp, Window},
};

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

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 01: QueryStats ──────────────────────────────────────────────────────

/// RETRIEVE results carry `QueryStats` with the profile name and pipeline counts.
#[test]
fn retrieve_results_include_query_stats() {
    use tidaldb::query::retrieve::{ProfileRef, RetrieveBuilder};

    let schema = build_test_schema();
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema)
        .open()
        .unwrap();

    // Write items so the candidate generation stage has something to score.
    for i in 1u64..=5 {
        let mut meta = HashMap::new();
        meta.insert("category".to_string(), "news".to_string());
        db.write_item_with_metadata(EntityId::new(i), &meta)
            .unwrap();
        db.signal("view", EntityId::new(i), 1.0, Timestamp::now())
            .unwrap();
    }

    let query = RetrieveBuilder::new(EntityKind::Item, ProfileRef::new("new"))
        .limit(5)
        .build()
        .unwrap();
    let results = db.retrieve(&query).unwrap();

    // QueryStats is unconditionally populated (not feature-gated).
    assert_eq!(
        results.stats.profile_name, "new",
        "stats must capture the profile name used for scoring"
    );
    // Pipeline stage fields are accessible; exact values depend on item count.
    let _ = results.stats.candidates_considered;
    let _ = results.stats.candidates_after_filter;
    let _ = results.stats.total_time_us;
    let _ = results.stats.scoring_time_us;
    let _ = results.stats.diversity_time_us;
}

/// SEARCH results carry `QueryStats` with the "search" profile name.
#[test]
fn search_results_include_query_stats() {
    use tidaldb::query::search::Search;

    let schema = build_test_schema();
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema)
        .open()
        .unwrap();

    // No text schema, so BM25 returns empty — but stats are still populated.
    let query = Search::builder()
        .query("test article")
        .limit(5)
        .build()
        .unwrap();
    let results = db.search(&query).unwrap();

    assert_eq!(
        results.stats.profile_name, "search",
        "search pipeline must record the 'search' builtin profile in stats"
    );
    let _ = results.stats.candidates_considered;
    let _ = results.stats.total_time_us;
}

// ── Task 05: Prometheus Exposition Format ────────────────────────────────────

/// Every `# HELP` line must be immediately followed by a `# TYPE` line.
#[test]
fn prometheus_format_help_always_followed_by_type() {
    let schema = build_test_schema();
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema)
        .open()
        .unwrap();

    let prom = db.metrics().render_prometheus();
    let lines: Vec<&str> = prom.lines().collect();

    for (i, line) in lines.iter().enumerate() {
        if line.starts_with("# HELP ") {
            assert!(
                i + 1 < lines.len() && lines[i + 1].starts_with("# TYPE "),
                "# HELP line must be immediately followed by # TYPE at index {i}: {line:?}"
            );
        }
    }
}

/// /healthz JSON contains the required fields.
#[test]
fn healthz_json_contains_required_fields() {
    let schema = build_test_schema();
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema)
        .open()
        .unwrap();

    let healthz = db.metrics().render_healthz();
    assert!(
        healthz.contains("\"status\":"),
        "healthz must contain status"
    );
    assert!(
        healthz.contains("\"uptime_seconds\":"),
        "healthz must contain uptime_seconds"
    );
    assert!(
        healthz.contains("\"version\":"),
        "healthz must contain version"
    );
    assert!(
        healthz.contains("\"build_hash\":"),
        "healthz must contain build_hash"
    );
}
