//! Handler for `mcptest concurrency`: concurrent-session correctness.
//!
//! Opens a single baseline session and N concurrent ones against the same
//! server, then hands their outcomes to the pure
//! [`mcptest_core::concurrency`] analyzer. The probe is a `tools/list`, reduced
//! to a stable catalog signature, so a server that corrupts or cross-talks
//! under concurrency shows up as a session whose catalog diverges from the
//! baseline (or one that never completes).

use anyhow::{Context, Result};
use serde_json::json;

use mcptest_core::concurrency::{analyze, render, SessionOutcome};
use mcptest_core::connector::connect_server;
use mcptest_core::network::ProxyConfig;
use mcptest_core::suite::ServerSpec;

use crate::cli::args::ConcurrencyArgs;
use crate::cli::handlers::conformance_probe::parse_server_spec;

/// Run the concurrent-session correctness check.
pub(crate) fn concurrency_command(args: ConcurrencyArgs) -> Result<i32> {
    let runtime = tokio::runtime::Runtime::new().context("could not build the tokio runtime")?;
    runtime.block_on(run(args))
}

async fn run(args: ConcurrencyArgs) -> Result<i32> {
    let spec = parse_server_spec(&args.server);

    let Some(baseline) = probe(&spec).await else {
        eprintln!("mcptest concurrency: the baseline session could not connect or list tools");
        return Ok(2);
    };

    let mut handles = Vec::new();
    for index in 0..args.sessions {
        let spec = spec.clone();
        handles.push(tokio::spawn(async move {
            match probe(&spec).await {
                Some(result) => SessionOutcome::completed(index, result),
                None => SessionOutcome::incomplete(index),
            }
        }));
    }
    let mut outcomes = Vec::with_capacity(handles.len());
    for (index, handle) in handles.into_iter().enumerate() {
        outcomes.push(
            handle
                .await
                .unwrap_or_else(|_| SessionOutcome::incomplete(index)),
        );
    }
    outcomes.sort_by_key(|o| o.index);

    let report = analyze(&baseline, &outcomes);
    if args.json {
        println!("{}", serde_json::to_string_pretty(&report)?);
    } else {
        print!("{}", render(&report));
    }
    if !report.passed() && !args.no_fail {
        return Ok(1);
    }
    Ok(0)
}

/// Connect one session, list tools, and return a stable catalog signature
/// (sorted tool names). `None` when the session could not connect or list.
async fn probe(spec: &ServerSpec) -> Option<String> {
    let live = connect_server(
        "concurrency",
        spec,
        "mcptest",
        mcptest_core::version::semver(),
        &ProxyConfig::default(),
        &[],
    )
    .await
    .ok()?;
    let response = live.client.request("tools/list", json!({})).await.ok()?;
    let mut names: Vec<String> = response
        .get("tools")
        .and_then(|t| t.as_array())
        .map(|tools| {
            tools
                .iter()
                .filter_map(|t| t.get("name").and_then(|n| n.as_str()).map(String::from))
                .collect()
        })
        .unwrap_or_default();
    names.sort();
    Some(names.join(","))
}
