use serde_json::{Value, json};

use crate::util::{
    api_request, check_auth_configured, client, exit_error, print_json_stderr, print_json_stdout,
    raw_api_request, resolve_token,
};

fn parse_version_triplet(raw: &str) -> Option<(u64, u64, u64)> {
    let mut parts = raw.trim().split('.');
    let major = parts.next()?.parse().ok()?;
    let minor = parts.next()?.parse().ok()?;
    let patch = parts.next()?.parse().ok()?;
    Some((major, minor, patch))
}

fn is_cli_version_compatible(current: &str, minimum: &str) -> Option<bool> {
    let current = parse_version_triplet(current)?;
    let minimum = parse_version_triplet(minimum)?;
    Some(current >= minimum)
}

fn runtime_reason_and_next_action(payload: &serde_json::Value) -> Option<String> {
    let reason = payload
        .get("reason_code")
        .and_then(|value| value.as_str())
        .or_else(|| payload.get("error_code").and_then(|value| value.as_str()))
        .or_else(|| payload.get("error").and_then(|value| value.as_str()));
    let action = payload
        .get("next_action")
        .and_then(|value| value.as_str())
        .or_else(|| payload.get("docs_hint").and_then(|value| value.as_str()));

    match (reason, action) {
        (Some(reason), Some(action)) => Some(format!("reason_code={reason}; next_action={action}")),
        (Some(reason), None) => Some(format!("reason_code={reason}")),
        (None, Some(action)) => Some(format!("next_action={action}")),
        _ => None,
    }
}

pub async fn config(api_url: &str, token: Option<&str>) -> i32 {
    api_request(
        api_url,
        reqwest::Method::GET,
        "/v1/system/config",
        token,
        None,
        &[],
        &[],
        false,
        false,
    )
    .await
}

pub async fn snapshot(api_url: &str, token: Option<&str>) -> i32 {
    api_request(
        api_url,
        reqwest::Method::GET,
        "/v1/projections",
        token,
        None,
        &[],
        &[],
        false,
        false,
    )
    .await
}

pub async fn discover(
    api_url: &str,
    endpoints_only: bool,
    exercises: bool,
    query: Option<String>,
    limit: Option<u32>,
    token: Option<&str>,
) -> i32 {
    if exercises {
        return discover_exercises(api_url, query, limit, token).await;
    }
    if query.is_some() || limit.is_some() {
        let err = json!({
            "error": "usage_error",
            "message": "--query/--limit are only supported with --exercises"
        });
        print_json_stderr(&err);
        return 4;
    }

    let resp = match client()
        .get(format!("{api_url}/api-doc/openapi.json"))
        .send()
        .await
    {
        Ok(r) => r,
        Err(e) => {
            let err = json!({
                "error": "connection_error",
                "message": format!("{e}"),
                "docs_hint": "Is the API server running? Check KURA_API_URL."
            });
            print_json_stderr(&err);
            return 3;
        }
    };

    if !resp.status().is_success() {
        let status = resp.status().as_u16();
        let body: serde_json::Value = resp.json().await.unwrap_or(json!({"error": "unknown"}));
        print_json_stderr(&body);
        return if (400..500).contains(&status) { 1 } else { 2 };
    }

    let spec: serde_json::Value = match resp.json().await {
        Ok(v) => v,
        Err(e) => exit_error(&format!("Failed to parse OpenAPI spec: {e}"), None),
    };

    if !endpoints_only {
        print_json_stdout(&spec);
        return 0;
    }

    // Extract compact endpoint list from OpenAPI paths
    let mut endpoints = Vec::new();

    if let Some(paths) = spec.get("paths").and_then(|p| p.as_object()) {
        for (path, methods) in paths {
            if let Some(methods_obj) = methods.as_object() {
                for (method, details) in methods_obj {
                    // Skip OpenAPI metadata keys
                    if !["get", "post", "put", "delete", "patch", "head", "options"]
                        .contains(&method.as_str())
                    {
                        continue;
                    }

                    let summary = details
                        .get("summary")
                        .and_then(|s| s.as_str())
                        .unwrap_or("");

                    let requires_auth = details
                        .get("security")
                        .map(|s| !s.as_array().map(|a| a.is_empty()).unwrap_or(true))
                        .unwrap_or(false);

                    endpoints.push(json!({
                        "method": method.to_uppercase(),
                        "path": path,
                        "summary": summary,
                        "auth": requires_auth
                    }));
                }
            }
        }
    }

    // Sort by path, then method
    endpoints.sort_by(|a, b| {
        let pa = a["path"].as_str().unwrap_or("");
        let pb = b["path"].as_str().unwrap_or("");
        pa.cmp(pb).then_with(|| {
            let ma = a["method"].as_str().unwrap_or("");
            let mb = b["method"].as_str().unwrap_or("");
            ma.cmp(mb)
        })
    });

    let output = json!({
        "endpoints": endpoints,
        "total": endpoints.len(),
        "base_url": api_url
    });

    print_json_stdout(&output);
    0
}

async fn discover_exercises(
    api_url: &str,
    query: Option<String>,
    limit: Option<u32>,
    token: Option<&str>,
) -> i32 {
    let normalized_query = query
        .map(|q| q.trim().to_string())
        .filter(|q| !q.is_empty());

    if let Some(query) = normalized_query.as_deref() {
        if let Some(body) =
            discover_exercises_via_agent_resolution(api_url, query, limit, token).await
        {
            print_json_stdout(&body);
            return 0;
        }

        if let Some(body) =
            discover_exercises_via_semantic_resolution(api_url, query, limit, token).await
        {
            print_json_stdout(&body);
            return 0;
        }
    }

    let mut url = match reqwest::Url::parse(&format!("{api_url}/v1/semantic/catalog")) {
        Ok(url) => url,
        Err(e) => {
            let err = json!({
                "error": "cli_error",
                "message": format!("Invalid URL: {api_url}/v1/semantic/catalog: {e}")
            });
            print_json_stderr(&err);
            return 4;
        }
    };
    {
        let mut pairs = url.query_pairs_mut();
        pairs.append_pair("domain", "exercise");
        if let Some(value) = normalized_query.as_deref() {
            pairs.append_pair("query", value);
        }
        if let Some(value) = limit {
            pairs.append_pair("limit", &value.to_string());
        }
    }
    let mut req = client().get(url);
    if let Some(t) = token {
        req = req.header("Authorization", format!("Bearer {t}"));
    }

    let resp = match req.send().await {
        Ok(r) => r,
        Err(e) => {
            let err = json!({
                "error": "connection_error",
                "message": format!("{e}"),
                "docs_hint": "Is the API server running? Check KURA_API_URL."
            });
            print_json_stderr(&err);
            return 3;
        }
    };

    if !resp.status().is_success() {
        let status = resp.status().as_u16();
        let body: serde_json::Value = resp.json().await.unwrap_or(json!({"error": "unknown"}));
        print_json_stderr(&body);
        return if (400..500).contains(&status) { 1 } else { 2 };
    }

    let body: serde_json::Value = match resp.json().await {
        Ok(v) => v,
        Err(e) => exit_error(
            &format!("Failed to parse exercise discovery response: {e}"),
            None,
        ),
    };

    print_json_stdout(&body);
    0
}

fn requested_exercise_discovery_limit(limit: Option<u32>) -> usize {
    limit.unwrap_or(10).clamp(1, 200) as usize
}

fn resolved_exercise_lookup_limit(limit: Option<u32>) -> usize {
    requested_exercise_discovery_limit(limit).clamp(1, 10)
}

fn build_discover_exercises_output(
    generated_at: Option<&Value>,
    query: &str,
    requested_limit: usize,
    applied_limit: usize,
    source_surface: &str,
    guidance: Option<&Value>,
    items: Vec<Value>,
) -> Option<Value> {
    if items.is_empty() {
        return None;
    }

    let mut meta = serde_json::Map::new();
    meta.insert("domain".to_string(), json!("exercise"));
    meta.insert("query".to_string(), json!(query));
    meta.insert("limit".to_string(), json!(requested_limit));
    meta.insert("applied_limit".to_string(), json!(applied_limit));
    meta.insert("total".to_string(), json!(items.len()));
    meta.insert("source_surface".to_string(), json!(source_surface));
    if let Some(generated_at) = generated_at.cloned() {
        meta.insert("generated_at".to_string(), generated_at);
    }
    if let Some(guidance) = guidance.cloned() {
        meta.insert("guidance".to_string(), guidance);
    }

    Some(json!({
        "meta": Value::Object(meta),
        "items": items,
    }))
}

fn map_agent_exercise_resolution_payload(
    query: &str,
    limit: Option<u32>,
    payload: &Value,
) -> Option<Value> {
    let result = payload
        .get("results")
        .and_then(Value::as_array)
        .and_then(|results| results.first())?;
    let matches = result.get("matches").and_then(Value::as_array)?;
    let items: Vec<Value> = matches
        .iter()
        .filter_map(|candidate| {
            let canonical_key = candidate.get("exercise_id")?.as_str()?;
            let match_source = candidate.get("source")?.as_str()?;
            let canonical_label = candidate
                .get("label")
                .and_then(Value::as_str)
                .filter(|value| !value.trim().is_empty())
                .unwrap_or(canonical_key);
            let matched_term = candidate
                .get("matched_term")
                .and_then(Value::as_str)
                .map(str::to_string);
            Some(json!({
                "domain": "exercise",
                "canonical_key": canonical_key,
                "canonical_label": canonical_label,
                "variants": matched_term.iter().cloned().collect::<Vec<_>>(),
                "match_score": candidate.get("score").cloned().unwrap_or(Value::Null),
                "match_confidence": candidate.get("confidence").cloned().unwrap_or(Value::Null),
                "match_source": match_source,
                "matched_term": matched_term,
                "requires_confirmation": candidate.get("requires_confirmation").cloned().unwrap_or(Value::Bool(false)),
                "reason": candidate.get("reason").cloned().unwrap_or(Value::Null),
            }))
        })
        .collect();
    let requested_limit = requested_exercise_discovery_limit(limit);
    build_discover_exercises_output(
        payload
            .get("meta")
            .and_then(|meta| meta.get("generated_at")),
        query,
        requested_limit,
        resolved_exercise_lookup_limit(limit),
        "agent.exercise_resolve",
        result.get("guidance"),
        items,
    )
}

fn map_semantic_resolution_payload(
    query: &str,
    limit: Option<u32>,
    payload: &Value,
) -> Option<Value> {
    let result = payload
        .get("results")
        .and_then(Value::as_array)
        .and_then(|results| results.first())?;
    let candidates = result.get("candidates").and_then(Value::as_array)?;
    let items: Vec<Value> = candidates
        .iter()
        .filter_map(|candidate| {
            let canonical_key = candidate.get("canonical_key")?.as_str()?;
            let canonical_label = candidate.get("canonical_label")?.as_str()?;
            let provenance = candidate
                .get("provenance")
                .and_then(Value::as_array)
                .cloned()
                .unwrap_or_default();
            let matched_terms: Vec<String> = provenance
                .iter()
                .filter_map(|entry| entry.get("matched_term").and_then(Value::as_str))
                .map(str::to_string)
                .collect();
            let primary_source = provenance
                .first()
                .and_then(|entry| entry.get("source"))
                .and_then(Value::as_str)
                .unwrap_or("semantic_resolve");
            Some(json!({
                "domain": "exercise",
                "canonical_key": canonical_key,
                "canonical_label": canonical_label,
                "variants": matched_terms,
                "match_score": candidate.get("score").cloned().unwrap_or(Value::Null),
                "match_confidence": candidate.get("confidence").cloned().unwrap_or(Value::Null),
                "match_source": primary_source,
                "matched_term": provenance.first().and_then(|entry| entry.get("matched_term")).cloned().unwrap_or(Value::Null),
                "requires_confirmation": Value::Null,
                "reason": Value::Null,
                "provenance": provenance,
            }))
        })
        .collect();
    let requested_limit = requested_exercise_discovery_limit(limit);
    build_discover_exercises_output(
        payload
            .get("meta")
            .and_then(|meta| meta.get("generated_at")),
        query,
        requested_limit,
        resolved_exercise_lookup_limit(limit),
        "semantic.resolve",
        None,
        items,
    )
}

async fn discover_exercises_via_agent_resolution(
    api_url: &str,
    query: &str,
    limit: Option<u32>,
    token: Option<&str>,
) -> Option<Value> {
    let url = reqwest::Url::parse(&format!("{api_url}/v1/agent/exercise-resolve")).ok()?;
    let mut req = client().post(url).json(&json!({
        "terms": [query],
        "limit": resolved_exercise_lookup_limit(limit),
    }));
    if let Some(t) = token {
        req = req.header("Authorization", format!("Bearer {t}"));
    }
    let response = req.send().await.ok()?;
    if !response.status().is_success() {
        return None;
    }
    let payload = response.json::<Value>().await.ok()?;
    map_agent_exercise_resolution_payload(query, limit, &payload)
}

async fn discover_exercises_via_semantic_resolution(
    api_url: &str,
    query: &str,
    limit: Option<u32>,
    token: Option<&str>,
) -> Option<Value> {
    let url = reqwest::Url::parse(&format!("{api_url}/v1/semantic/resolve")).ok()?;
    let mut req = client().post(url).json(&json!({
        "queries": [{
            "term": query,
            "domain": "exercise",
        }],
        "top_k": resolved_exercise_lookup_limit(limit),
    }));
    if let Some(t) = token {
        req = req.header("Authorization", format!("Bearer {t}"));
    }
    let response = req.send().await.ok()?;
    if !response.status().is_success() {
        return None;
    }
    let payload = response.json::<Value>().await.ok()?;
    map_semantic_resolution_payload(query, limit, &payload)
}

pub async fn doctor(api_url: &str) -> i32 {
    let mut checks: Vec<serde_json::Value> = Vec::new();
    let mut overall = "ok";

    // 1. API reachable
    match raw_api_request(api_url, reqwest::Method::GET, "/health", None).await {
        Ok((status, body)) if status == 200 => {
            let version = body
                .get("version")
                .and_then(|v| v.as_str())
                .unwrap_or("unknown");
            checks.push(json!({
                "name": "api_reachable",
                "status": "ok",
                "detail": format!("{api_url} (v{version})")
            }));
        }
        Ok((status, _)) => {
            overall = "error";
            checks.push(json!({
                "name": "api_reachable",
                "status": "error",
                "detail": format!("{api_url} returned HTTP {status}")
            }));
        }
        Err(e) => {
            overall = "error";
            checks.push(json!({
                "name": "api_reachable",
                "status": "error",
                "detail": format!("{api_url}: {e}")
            }));
            // No point continuing if API is unreachable
            let output = json!({ "checks": checks, "overall": overall });
            print_json_stderr(&output);
            return 1;
        }
    }

    // 2. Auth configured
    match check_auth_configured() {
        Some((method, detail)) => {
            checks.push(json!({
                "name": "auth_configured",
                "status": "ok",
                "detail": format!("{method}: {detail}")
            }));
        }
        None => {
            if overall == "ok" {
                overall = "warn";
            }
            checks.push(json!({
                "name": "auth_configured",
                "status": "warn",
                "detail": "No credentials found. Run `kura login` or set KURA_API_KEY."
            }));
        }
    }

    // 3. Auth valid (only if configured)
    let token = match resolve_token(api_url).await {
        Ok(t) => {
            // Try an authenticated request
            match raw_api_request(
                api_url,
                reqwest::Method::GET,
                "/v1/projections/profile/overview",
                Some(&t),
            )
            .await
            {
                Ok((status, body)) if (200..=404).contains(&status) => {
                    // 200 = projection exists, 404 = user has no profile projection yet
                    let user_hint = if status == 200 {
                        body.get("user_id")
                            .and_then(|u| u.as_str())
                            .map(|u| format!("user_id: {u}"))
                            .unwrap_or_else(|| "authenticated".to_string())
                    } else {
                        "authenticated (no profile projection yet)".to_string()
                    };
                    checks.push(json!({
                        "name": "auth_valid",
                        "status": "ok",
                        "detail": user_hint
                    }));
                }
                Ok((401, _)) => {
                    if overall == "ok" {
                        overall = "warn";
                    }
                    checks.push(json!({
                        "name": "auth_valid",
                        "status": "error",
                        "detail": "Token rejected (HTTP 401). Run `kura login` again."
                    }));
                }
                Ok((status, _)) => {
                    if overall == "ok" {
                        overall = "warn";
                    }
                    checks.push(json!({
                        "name": "auth_valid",
                        "status": "warn",
                        "detail": format!("Unexpected HTTP {status} on auth check")
                    }));
                }
                Err(e) => {
                    if overall == "ok" {
                        overall = "warn";
                    }
                    checks.push(json!({
                        "name": "auth_valid",
                        "status": "error",
                        "detail": format!("Auth check request failed: {e}")
                    }));
                }
            }
            Some(t)
        }
        Err(e) => {
            if overall == "ok" {
                overall = "warn";
            }
            checks.push(json!({
                "name": "auth_valid",
                "status": "warn",
                "detail": format!("{e}")
            }));
            None
        }
    };

    // 4. System config (workers write this at startup — empty = worker never ran)
    if let Some(ref t) = token {
        match raw_api_request(
            api_url,
            reqwest::Method::GET,
            "/v1/agent/capabilities",
            Some(t),
        )
        .await
        {
            Ok((200, body)) => {
                let preferred_read = body
                    .get("preferred_read_endpoint")
                    .and_then(|v| v.as_str())
                    .unwrap_or("unknown");
                let preferred_write = body
                    .get("preferred_write_endpoint")
                    .and_then(|v| v.as_str())
                    .unwrap_or("unknown");
                let preferred_structured_write = body
                    .get("preferred_structured_write_endpoint")
                    .and_then(|v| v.as_str())
                    .unwrap_or(preferred_write);
                let protocol_version = body
                    .get("protocol_version")
                    .and_then(|v| v.as_str())
                    .unwrap_or("unknown");
                let min_cli = body
                    .get("min_cli_version")
                    .and_then(|v| v.as_str())
                    .unwrap_or("");
                let current_cli = env!("CARGO_PKG_VERSION");

                let compat_status = match is_cli_version_compatible(current_cli, min_cli) {
                    Some(true) => "ok",
                    Some(false) => {
                        if overall == "ok" {
                            overall = "warn";
                        }
                        "warn"
                    }
                    None => "ok",
                };
                let compat_hint = match is_cli_version_compatible(current_cli, min_cli) {
                    Some(true) => "compatible".to_string(),
                    Some(false) => format!("upgrade cli (current={current_cli}, min={min_cli})"),
                    None => "version comparison unavailable".to_string(),
                };
                checks.push(json!({
                    "name": "agent_capabilities",
                    "status": compat_status,
                    "detail": format!("protocol={protocol_version}, read={preferred_read}, write={preferred_write}, structured_write={preferred_structured_write}, cli={compat_hint}")
                }));
            }
            Ok((404, _)) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "agent_capabilities",
                    "status": "warn",
                    "detail": "Not found (HTTP 404). API is older than capability-manifest contract."
                }));
            }
            Ok((status, _)) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "agent_capabilities",
                    "status": "warn",
                    "detail": format!("HTTP {status} while checking /v1/agent/capabilities")
                }));
            }
            Err(e) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "agent_capabilities",
                    "status": "error",
                    "detail": format!("{e}")
                }));
            }
        }
    }

    // 5. System config (workers write this at startup — empty = worker never ran)
    if let Some(ref t) = token {
        match raw_api_request(api_url, reqwest::Method::GET, "/v1/system/config", Some(t)).await {
            Ok((200, body)) => {
                let data = body.get("data").unwrap_or(&body);
                let dimensions = data
                    .get("dimensions")
                    .and_then(|d| d.as_object())
                    .map(|d| d.len())
                    .unwrap_or(0);
                let conventions = data
                    .get("event_conventions")
                    .and_then(|c| c.as_object())
                    .map(|c| c.len())
                    .unwrap_or(0);

                if dimensions > 0 {
                    checks.push(json!({
                        "name": "system_config",
                        "status": "ok",
                        "detail": format!("{dimensions} dimensions, {conventions} event conventions")
                    }));
                } else {
                    if overall == "ok" {
                        overall = "warn";
                    }
                    checks.push(json!({
                        "name": "system_config",
                        "status": "warn",
                        "detail": "Config exists but has no dimensions. Worker may not have started."
                    }));
                }
            }
            Ok((404, _)) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "system_config",
                    "status": "warn",
                    "detail": "Not found. Worker has not started yet (worker writes system_config at startup)."
                }));
            }
            Ok((status, _)) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "system_config",
                    "status": "warn",
                    "detail": format!("HTTP {status}")
                }));
            }
            Err(e) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "system_config",
                    "status": "error",
                    "detail": format!("{e}")
                }));
            }
        }
    }

    // 6. Runtime readiness gate (admin-only diagnostic)
    if let Some(ref t) = token {
        match raw_api_request(
            api_url,
            reqwest::Method::GET,
            "/v1/admin/runtime/readiness",
            Some(t),
        )
        .await
        {
            Ok((200, body)) => {
                let gate_status = body
                    .get("gate_status")
                    .and_then(|value| value.as_str())
                    .unwrap_or("unknown");
                let reason_detail = runtime_reason_and_next_action(&body)
                    .unwrap_or_else(|| "reason_code=missing_runtime_reason".to_string());
                let status = match gate_status {
                    "pass" => "ok",
                    "warn" => "warn",
                    "fail" => "warn",
                    _ => "warn",
                };
                if status != "ok" && overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "runtime_readiness",
                    "status": status,
                    "detail": format!("gate_status={gate_status}; {reason_detail}")
                }));
            }
            Ok((403, body)) => {
                if overall == "ok" {
                    overall = "warn";
                }
                let reason_detail = runtime_reason_and_next_action(&body)
                    .unwrap_or_else(|| "reason_code=admin_required".to_string());
                checks.push(json!({
                    "name": "runtime_readiness",
                    "status": "warn",
                    "detail": format!("Admin-only endpoint unavailable (HTTP 403); {reason_detail}")
                }));
            }
            Ok((404, _)) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "runtime_readiness",
                    "status": "warn",
                    "detail": "Not found. API missing runtime readiness surface."
                }));
            }
            Ok((status, body)) => {
                if overall == "ok" {
                    overall = "warn";
                }
                let reason_detail = runtime_reason_and_next_action(&body)
                    .unwrap_or_else(|| "reason_code=runtime_readiness_http_error".to_string());
                checks.push(json!({
                    "name": "runtime_readiness",
                    "status": "warn",
                    "detail": format!("HTTP {status}; {reason_detail}")
                }));
            }
            Err(e) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "runtime_readiness",
                    "status": "error",
                    "detail": format!("{e}")
                }));
            }
        }
    }

    // 7. Runtime tuneables snapshot (admin-only diagnostic)
    if let Some(ref t) = token {
        match raw_api_request(
            api_url,
            reqwest::Method::GET,
            "/v1/admin/runtime/tuneables",
            Some(t),
        )
        .await
        {
            Ok((200, body)) => {
                let schema_version = body
                    .get("schema_version")
                    .and_then(|value| value.as_str())
                    .unwrap_or("unknown");
                let config_version = body
                    .get("config_version")
                    .and_then(|value| value.as_i64())
                    .unwrap_or_default();
                checks.push(json!({
                    "name": "runtime_tuneables",
                    "status": "ok",
                    "detail": format!(
                        "schema_version={schema_version}, config_version={config_version}"
                    )
                }));
            }
            Ok((403, body)) => {
                if overall == "ok" {
                    overall = "warn";
                }
                let reason_detail = runtime_reason_and_next_action(&body)
                    .unwrap_or_else(|| "reason_code=admin_required".to_string());
                checks.push(json!({
                    "name": "runtime_tuneables",
                    "status": "warn",
                    "detail": format!("Admin-only endpoint unavailable (HTTP 403); {reason_detail}")
                }));
            }
            Ok((404, _)) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "runtime_tuneables",
                    "status": "warn",
                    "detail": "Not found. API missing runtime tuneables surface."
                }));
            }
            Ok((status, body)) => {
                if overall == "ok" {
                    overall = "warn";
                }
                let reason_detail = runtime_reason_and_next_action(&body)
                    .unwrap_or_else(|| "reason_code=runtime_tuneables_http_error".to_string());
                checks.push(json!({
                    "name": "runtime_tuneables",
                    "status": "warn",
                    "detail": format!("HTTP {status}; {reason_detail}")
                }));
            }
            Err(e) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "runtime_tuneables",
                    "status": "error",
                    "detail": format!("{e}")
                }));
            }
        }
    }

    // 8. Worker active — check if projections exist (workers create them)
    if let Some(ref t) = token {
        match raw_api_request(api_url, reqwest::Method::GET, "/v1/projections", Some(t)).await {
            Ok((200, body)) => {
                let count = body.as_array().map(|a| a.len()).unwrap_or(0);
                if count > 0 {
                    // Check freshness: find most recent updated_at
                    let newest = body
                        .as_array()
                        .and_then(|arr| {
                            arr.iter()
                                .filter_map(|p| p.get("updated_at").and_then(|u| u.as_str()))
                                .max()
                        })
                        .unwrap_or("unknown");

                    checks.push(json!({
                        "name": "worker_active",
                        "status": "ok",
                        "detail": format!("{count} projections, newest: {newest}")
                    }));
                } else {
                    if overall == "ok" {
                        overall = "warn";
                    }
                    checks.push(json!({
                        "name": "worker_active",
                        "status": "warn",
                        "detail": "No projections found. Worker may not be running or no events logged yet."
                    }));
                }
            }
            Ok((status, _)) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "worker_active",
                    "status": "warn",
                    "detail": format!("HTTP {status} on snapshot check")
                }));
            }
            Err(e) => {
                if overall == "ok" {
                    overall = "warn";
                }
                checks.push(json!({
                    "name": "worker_active",
                    "status": "error",
                    "detail": format!("{e}")
                }));
            }
        }
    }

    let output = json!({
        "checks": checks,
        "overall": overall
    });

    if overall == "ok" {
        print_json_stdout(&output);
        0
    } else if overall == "warn" {
        print_json_stdout(&output);
        0
    } else {
        print_json_stderr(&output);
        1
    }
}

#[cfg(test)]
mod tests {
    use super::{
        is_cli_version_compatible, map_agent_exercise_resolution_payload,
        map_semantic_resolution_payload, parse_version_triplet, runtime_reason_and_next_action,
    };
    use serde_json::json;

    #[test]
    fn parse_version_triplet_accepts_semver_triplets() {
        assert_eq!(parse_version_triplet("0.1.0"), Some((0, 1, 0)));
        assert_eq!(parse_version_triplet("12.34.56"), Some((12, 34, 56)));
    }

    #[test]
    fn parse_version_triplet_rejects_invalid_values() {
        assert_eq!(parse_version_triplet("0.1"), None);
        assert_eq!(parse_version_triplet("x.y.z"), None);
    }

    #[test]
    fn cli_version_compatibility_compares_triplets() {
        assert_eq!(is_cli_version_compatible("0.2.0", "0.1.9"), Some(true));
        assert_eq!(is_cli_version_compatible("0.1.0", "0.1.0"), Some(true));
        assert_eq!(is_cli_version_compatible("0.1.0", "0.2.0"), Some(false));
    }

    #[test]
    fn runtime_operability_diagnosis_prefers_reason_code_next_action() {
        let payload = json!({
            "gate_status": "fail",
            "reason_code": "missing_signal",
            "next_action": "collect runtime evidence"
        });

        let detail = runtime_reason_and_next_action(&payload).expect("detail should exist");
        assert!(detail.contains("reason_code=missing_signal"));
        assert!(detail.contains("next_action=collect runtime evidence"));
    }

    #[test]
    fn agent_exercise_resolution_payload_maps_to_discover_items() {
        let payload = json!({
            "results": [{
                "term": "nordic",
                "normalized_term": "nordic",
                "guidance": {
                    "mode": "confirm_before_reuse",
                    "reason": "Top candidate is plausible.",
                    "recommended_exercise_id": "nordic_hamstring_curl"
                },
                "matches": [{
                    "exercise_id": "nordic_hamstring_curl",
                    "score": 0.93,
                    "confidence": "high",
                    "source": "known_exercise_id_fuzzy",
                    "matched_term": "nordic_hamstring_curl",
                    "label": "Nordic Hamstring Curl",
                    "requires_confirmation": true,
                    "reason": "Close string match."
                }]
            }],
            "meta": {
                "generated_at": "2026-03-14T12:00:00Z",
                "limit": 5
            }
        });

        let mapped = map_agent_exercise_resolution_payload("nordic", Some(25), &payload)
            .expect("mapped discovery payload");
        assert_eq!(
            mapped["meta"]["source_surface"],
            json!("agent.exercise_resolve")
        );
        assert_eq!(mapped["meta"]["limit"], json!(25));
        assert_eq!(mapped["meta"]["applied_limit"], json!(10));
        assert_eq!(
            mapped["meta"]["guidance"]["recommended_exercise_id"],
            json!("nordic_hamstring_curl")
        );
        assert_eq!(
            mapped["items"][0]["canonical_key"],
            json!("nordic_hamstring_curl")
        );
        assert_eq!(
            mapped["items"][0]["match_source"],
            json!("known_exercise_id_fuzzy")
        );
    }

    #[test]
    fn semantic_resolution_payload_maps_to_discover_items() {
        let payload = json!({
            "results": [{
                "term": "nordic",
                "normalized_term": "nordic",
                "domain": "exercise",
                "candidates": [{
                    "canonical_key": "nordic_hamstring_curl",
                    "canonical_label": "Nordic Hamstring Curl",
                    "score": 0.88,
                    "confidence": "high",
                    "provenance": [{
                        "source": "semantic_memory_projection",
                        "matched_term": "nordic curl"
                    }]
                }]
            }],
            "meta": {
                "generated_at": "2026-03-14T12:00:00Z",
                "top_k": 5
            }
        });

        let mapped =
            map_semantic_resolution_payload("nordic", Some(7), &payload).expect("mapped payload");
        assert_eq!(mapped["meta"]["source_surface"], json!("semantic.resolve"));
        assert_eq!(
            mapped["items"][0]["canonical_key"],
            json!("nordic_hamstring_curl")
        );
        assert_eq!(mapped["items"][0]["variants"], json!(["nordic curl"]));
        assert_eq!(
            mapped["items"][0]["match_source"],
            json!("semantic_memory_projection")
        );
    }
}
