#![allow(clippy::unwrap_used, clippy::doc_markdown)]
//! M0–M10 review pass 2, zone C regression: FOR SESSION missing-session handling
//! must NOT diverge between RETRIEVE and SEARCH.
//!
//! A RETRIEVE/SEARCH with `FOR SESSION <id>` pointing at an expired/evicted
//! session is a WELL-FORMED query. Per CODING_GUIDELINES §6 ("graceful
//! degradation, never failure") the engine must execute it WITHOUT the session
//! boost rather than hard-erroring. Before the fix RETRIEVE returned
//! `Err(SessionNotFound)` while the structurally-identical SEARCH degraded — a
//! confusing surface-specific outage the moment a session was swept. This test
//! proves both surfaces now degrade identically.

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

use tidaldb::{
    SessionId, TidalDb,
    query::{retrieve::Retrieve, search::Search},
    schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Timestamp, Window},
};

fn test_schema() -> tidaldb::schema::Schema {
    let mut builder = SchemaBuilder::new();
    let _ = builder
        .signal(
            "view",
            EntityKind::Item,
            DecaySpec::Exponential {
                half_life: Duration::from_secs(7 * 24 * 3600),
            },
        )
        .windows(&[Window::AllTime])
        .add();
    builder.build().unwrap()
}

fn test_db() -> TidalDb {
    TidalDb::builder()
        .ephemeral()
        .with_schema(test_schema())
        .open()
        .unwrap()
}

#[test]
fn retrieve_and_search_degrade_identically_on_missing_session() {
    let db = test_db();

    // Seed one item with a `view` signal so both queries have something to rank.
    let now = Timestamp::now();
    db.signal("view", EntityId::new(1), 1.0, now).unwrap();
    let mut meta = HashMap::new();
    meta.insert("title".to_string(), "jazz piano".to_string());
    db.write_item_with_metadata(EntityId::new(1), &meta)
        .unwrap();

    // A session id that was never started (the "swept session" case).
    let missing = SessionId::from_raw(9_999_999);

    // RETRIEVE FOR SESSION <missing> must NOT error — it degrades to no-boost.
    let retrieve = Retrieve::builder()
        .profile("new")
        .for_session(missing)
        .limit(10)
        .build()
        .unwrap();
    let r = db.retrieve(&retrieve);
    assert!(
        r.is_ok(),
        "RETRIEVE with a missing session must degrade gracefully, got {:?}",
        r.err()
    );

    // SEARCH FOR SESSION <missing> must likewise degrade (this already worked;
    // asserted here to lock the two surfaces together).
    let search = Search::builder()
        .query("jazz")
        .using_profile("search")
        .for_session(missing)
        .limit(10)
        .build()
        .unwrap();
    let s = db.search(&search);
    assert!(
        s.is_ok(),
        "SEARCH with a missing session must degrade gracefully, got {:?}",
        s.err()
    );

    db.close().unwrap();
}
