#[path = "../support/utils.rs"]
mod _utils;

use _utils::{boxed_error, cleanup_run, create_client, new_run_id, print_summary, require};
use mubit_sdk::{RecallOptions, RememberOptions, TransportMode};
use serde_json::json;
use std::error::Error;
use std::time::Instant;
use tokio::time::{sleep, Duration};

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn Error>> {
    let name = "public_09_user_scoping_and_isolation";
    let started = Instant::now();
    let client = create_client().await?;
    let user_a = "sdk-user-a".to_string();
    let user_b = "sdk-user-b".to_string();
    let seed_run = new_run_id("public_user_scope_seed");
    let allowed_run = new_run_id("public_user_scope_allowed");
    let blocked_run = new_run_id("public_user_scope_blocked");
    client.set_transport(TransportMode::Http);
    client.set_run_id(Some(seed_run.clone()));

    let mut passed = true;
    let mut detail = "validated user scoping and same-user cross-run reuse".to_string();
    let mut metrics = json!({});

    let scenario = async {
        let mut lesson = RememberOptions::new("When a claim is missing a city, check the original intake note before escalating.");
        lesson.run_id = Some(seed_run.clone());
        lesson.user_id = Some(user_a.clone());
        lesson.agent_id = Some("planner".into());
        lesson.intent = Some("lesson".into());
        lesson.lesson_type = Some("success".into());
        lesson.lesson_scope = Some("global".into());
        lesson.lesson_importance = Some("high".into());
        lesson.metadata = Some(json!({"source": "public-example", "family": "user-scope"}));
        let lesson_write = client.remember(lesson).await?;

        let mut lessons = serde_json::Value::Null;
        for _ in 0..24 {
            let current = client
                .control
                .lessons(json!({"run_id": seed_run, "user_id": user_a, "limit": 10}))
                .await?;
            let has_lesson = current
                .get("lessons")
                .and_then(|v| v.as_array())
                .map(|items| !items.is_empty())
                .unwrap_or(false);
            lessons = current;
            if has_lesson {
                break;
            }
            sleep(Duration::from_millis(500)).await;
        }
        let lesson_id = lessons.get("lessons").and_then(|v| v.as_array()).and_then(|items| items.iter().find_map(|item| item.get("id").and_then(|v| v.as_str()))).ok_or_else(|| boxed_error(format!("expected lesson for isolation scenario: {lessons}")))?.to_string();

        let mut allowed = RecallOptions::new(
            "A claim is missing a city. Before escalating, what note should I check?",
        );
        allowed.run_id = Some(allowed_run.clone());
        allowed.user_id = Some(user_a.clone());
        allowed.entry_types = vec!["lesson".into()];
        allowed.limit = 10;
        let allowed_response = client.recall(allowed).await?;

        let mut blocked = RecallOptions::new(
            "A claim is missing a city. Before escalating, what note should I check?",
        );
        blocked.run_id = Some(blocked_run.clone());
        blocked.user_id = Some(user_b.clone());
        blocked.entry_types = vec!["lesson".into()];
        blocked.limit = 10;
        let blocked_response = client.recall(blocked).await?;

        let allowed_text = format!("{} {}",
            allowed_response.get("final_answer").and_then(|v| v.as_str()).unwrap_or(""),
            allowed_response.get("evidence").and_then(|v| v.as_array()).map(|items| items.iter().filter_map(|item| item.get("content").and_then(|v| v.as_str())).collect::<Vec<_>>().join(" ")).unwrap_or_default()
        ).to_lowercase();
        let blocked_text = format!("{} {}",
            blocked_response.get("final_answer").and_then(|v| v.as_str()).unwrap_or(""),
            blocked_response.get("evidence").and_then(|v| v.as_array()).map(|items| items.iter().filter_map(|item| item.get("content").and_then(|v| v.as_str())).collect::<Vec<_>>().join(" ")).unwrap_or_default()
        ).to_lowercase();

        require(lesson_write.get("accepted").and_then(|v| v.as_bool()).unwrap_or(false) || lesson_write.get("job_id").is_some(), format!("lesson write failed: {lesson_write}"))?;
        require(allowed_text.contains("original intake note"), format!("same-user cross-run lesson reuse failed: {allowed_response}"))?;
        require(!blocked_text.contains("original intake note"), format!("cross-user leak detected: {blocked_response}"))?;

        metrics = json!({
            "seed_run": seed_run,
            "allowed_run": allowed_run,
            "blocked_run": blocked_run,
            "lesson_id": lesson_id,
            "allowed_evidence_count": allowed_response.get("evidence").and_then(|v| v.as_array()).map(|items| items.len()).unwrap_or(0),
            "blocked_evidence_count": blocked_response.get("evidence").and_then(|v| v.as_array()).map(|items| items.len()).unwrap_or(0),
        });
        Ok::<(), Box<dyn Error>>(())
    }
    .await;

    if let Err(err) = scenario {
        passed = false;
        detail = err.to_string();
    }

    let mut cleanup_ok = true;
    for run_id in [&seed_run, &allowed_run, &blocked_run] {
        cleanup_ok = cleanup_ok && cleanup_run(&client, run_id).await;
    }
    if !cleanup_ok {
        passed = false;
        detail = format!("{detail} | cleanup failures");
    }

    print_summary(name, passed, &detail, &metrics, started.elapsed().as_secs_f64(), cleanup_ok);
    if passed { Ok(()) } else { Err(boxed_error(detail)) }
}
