complior-cli 1.0.0

AI Act Compliance Scanner & Fixer — CLI
use crate::cli::CertAction;
use crate::config::TuiConfig;

use super::common::{ensure_engine, resolve_project_path, url_encode};

pub async fn run_cert_command(action: &CertAction, config: &TuiConfig) -> i32 {
    match action {
        CertAction::Readiness { name, json, path } => {
            run_cert_readiness(name, *json, path.as_deref(), config).await
        }
        CertAction::Test {
            name,
            adversarial,
            categories,
            json,
            path,
        } => {
            if !adversarial {
                eprintln!("Error: --adversarial flag is required for cert test");
                return 1;
            }
            run_cert_test_adversarial(name, categories.as_deref(), *json, path.as_deref(), config)
                .await
        }
    }
}

async fn run_cert_readiness(name: &str, json: bool, path: Option<&str>, config: &TuiConfig) -> i32 {
    let client = match ensure_engine(config).await {
        Ok(c) => c,
        Err(code) => return code,
    };

    let project_path = resolve_project_path(path);

    let url = format!(
        "/cert/readiness?name={}&path={}",
        url_encode(name),
        url_encode(&project_path),
    );

    match client.get_json(&url).await {
        Ok(value) => {
            if json {
                println!(
                    "{}",
                    serde_json::to_string_pretty(&value).unwrap_or_default()
                );
            } else {
                print_readiness_human(&value, name);
            }
            0
        }
        Err(e) => {
            eprintln!("Error: {e}");
            1
        }
    }
}

async fn run_cert_test_adversarial(
    name: &str,
    categories: Option<&str>,
    json: bool,
    path: Option<&str>,
    config: &TuiConfig,
) -> i32 {
    let client = match ensure_engine(config).await {
        Ok(c) => c,
        Err(code) => return code,
    };

    let _project_path = resolve_project_path(path);

    let mut body = serde_json::json!({ "agent_name": name });
    if let Some(cats) = categories {
        let cat_list: Vec<&str> = cats.split(',').map(str::trim).collect();
        body["test_categories"] = serde_json::json!(cat_list);
    }

    if !json {
        eprintln!("Running adversarial tests for '{name}'... (this may take several minutes)");
    }

    match client.post_json_long("/cert/test/adversarial", &body).await {
        Ok(value) => {
            if json {
                println!(
                    "{}",
                    serde_json::to_string_pretty(&value).unwrap_or_default()
                );
            } else {
                print_adversarial_human(&value, name);
            }
            0
        }
        Err(e) => {
            eprintln!("Error: {e}");
            1
        }
    }
}

fn print_readiness_human(value: &serde_json::Value, name: &str) {
    let score = value
        .get("overallScore")
        .and_then(serde_json::Value::as_u64)
        .unwrap_or(0);
    let level = value
        .get("readinessLevel")
        .and_then(|v| v.as_str())
        .unwrap_or("unknown");
    let met = value
        .get("metRequirements")
        .and_then(serde_json::Value::as_u64)
        .unwrap_or(0);
    let partial = value
        .get("partialRequirements")
        .and_then(serde_json::Value::as_u64)
        .unwrap_or(0);
    let unmet = value
        .get("unmetRequirements")
        .and_then(serde_json::Value::as_u64)
        .unwrap_or(0);
    let total = value
        .get("totalRequirements")
        .and_then(serde_json::Value::as_u64)
        .unwrap_or(0);

    let level_icon = match level {
        "certified" => "V",
        "near_ready" => "~",
        "in_progress" => ">",
        _ => "!",
    };

    println!();
    println!("  AIUC-1 Readiness: {name}");
    println!("  -------------------------");
    println!("  [{level_icon}] Score: {score}% ({level})");
    println!("  Requirements: {met}/{total} met, {partial} partial, {unmet} unmet");
    println!();

    // Category breakdown
    if let Some(categories) = value.get("categories").and_then(|v| v.as_array()) {
        println!("  Category Scores:");
        for cat in categories {
            let label = cat.get("label").and_then(|v| v.as_str()).unwrap_or("?");
            let cat_score = cat
                .get("score")
                .and_then(serde_json::Value::as_u64)
                .unwrap_or(0);
            let bar_len = (cat_score as usize) / 5;
            let bar = "#".repeat(bar_len);
            let empty = ".".repeat(20 - bar_len);
            println!("    {label:<25} [{bar}{empty}] {cat_score}%");
        }
        println!();
    }

    // Gaps
    if let Some(gaps) = value.get("gaps").and_then(|v| v.as_array())
        && !gaps.is_empty()
    {
        println!("  Gaps ({} items):", gaps.len());
        for gap in gaps {
            if let Some(g) = gap.as_str() {
                println!("    - {g}");
            }
        }
        println!();
    }
}

fn print_adversarial_human(value: &serde_json::Value, name: &str) {
    let overall = value
        .get("overallScore")
        .and_then(serde_json::Value::as_u64)
        .unwrap_or(0);
    let total = value
        .get("totalTests")
        .and_then(serde_json::Value::as_u64)
        .unwrap_or(0);
    let passed = value
        .get("passCount")
        .and_then(serde_json::Value::as_u64)
        .unwrap_or(0);
    let failed = value
        .get("failCount")
        .and_then(serde_json::Value::as_u64)
        .unwrap_or(0);
    let inconclusive = value
        .get("inconclusiveCount")
        .and_then(serde_json::Value::as_u64)
        .unwrap_or(0);
    let duration = value
        .get("duration")
        .and_then(serde_json::Value::as_u64)
        .unwrap_or(0);

    let icon = if overall >= 80 {
        "V"
    } else if overall >= 50 {
        "~"
    } else {
        "!"
    };

    println!();
    println!("  Adversarial Test Results: {name}");
    println!("  ----------------------------------");
    println!("  [{icon}] Overall Score: {overall}%");
    println!(
        "  Tests: {total} total, {passed} passed, {failed} failed, {inconclusive} inconclusive"
    );
    println!("  Duration: {:.1}s", duration as f64 / 1000.0);
    println!();

    // Category breakdown
    if let Some(cats) = value.get("categories").and_then(|v| v.as_object()) {
        println!("  Category Results:");
        for (cat_name, cat_data) in cats {
            let cat_score = cat_data
                .get("score")
                .and_then(serde_json::Value::as_u64)
                .unwrap_or(0);
            let cat_total = cat_data
                .get("total")
                .and_then(serde_json::Value::as_u64)
                .unwrap_or(0);
            let cat_passed = cat_data
                .get("passed")
                .and_then(serde_json::Value::as_u64)
                .unwrap_or(0);
            let cat_failed = cat_data
                .get("failed")
                .and_then(serde_json::Value::as_u64)
                .unwrap_or(0);
            let label = cat_name.replace('_', " ");
            let bar_len = (cat_score as usize) / 5;
            let bar = "#".repeat(bar_len);
            let empty = ".".repeat(20 - bar_len);
            println!(
                "    {label:<20} [{bar}{empty}] {cat_score}% ({cat_passed}/{cat_total} pass, {cat_failed} fail)"
            );
        }
        println!();
    }

    // Per-test results
    if let Some(results) = value.get("results").and_then(|v| v.as_array()) {
        let failures: Vec<_> = results
            .iter()
            .filter(|r| r.get("verdict").and_then(|v| v.as_str()) == Some("fail"))
            .collect();
        if !failures.is_empty() {
            println!("  Failed Tests:");
            for r in &failures {
                let sid = r.get("scenarioId").and_then(|v| v.as_str()).unwrap_or("?");
                let rname = r.get("name").and_then(|v| v.as_str()).unwrap_or("?");
                let reasoning = r.get("reasoning").and_then(|v| v.as_str()).unwrap_or("");
                println!("    [FAIL] {sid}: {rname}");
                println!("           {reasoning}");
            }
            println!();
        }
    }

    // Obligation refs
    if let Some(refs) = value.get("obligationRefs").and_then(|v| v.as_array()) {
        let ref_strs: Vec<&str> = refs.iter().filter_map(|r| r.as_str()).collect();
        if !ref_strs.is_empty() {
            println!("  Obligation Coverage: {}", ref_strs.join(", "));
            println!();
        }
    }
}