use super::*;
use crate::test_support::with_isolated_home;
use clap::error::ErrorKind;
use clap::Parser;
use homeboy::core::extension::bench::aggregate_comparison;
use std::fs;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use tempfile::TempDir;

/// Minimal CLI wrapper to exercise clap parsing of `BenchArgs`.
#[derive(Parser)]
struct TestCli {
    #[command(flatten)]
    bench: BenchArgs,
}

fn write_bench_extension(home: &TempDir) {
    let extension_dir = home
        .path()
        .join(".config")
        .join("homeboy")
        .join("extensions")
        .join("fixture-bench");
    fs::create_dir_all(&extension_dir).expect("mkdir extension");
    fs::write(
        extension_dir.join("fixture-bench.json"),
        r#"{
                "name": "Fixture Bench",
                "version": "0.0.0",
                "bench": { "extension_script": "bench-runner.sh" }
            }"#,
    )
    .expect("write extension manifest");

    let script_path = extension_dir.join("bench-runner.sh");
    fs::write(
            &script_path,
            r#"#!/bin/sh
if [ -n "$HOMEBOY_BENCH_EXTRA_WORKLOADS" ]; then
  all_scenarios=""
  old_ifs="$IFS"
  IFS=":"
  for workload in $HOMEBOY_BENCH_EXTRA_WORKLOADS; do
    name="$(basename "$workload")"
    name="${name%%.bench.*}"
    name="${name%.*}"
    if [ -n "$all_scenarios" ]; then
      all_scenarios="$all_scenarios $name"
    else
      all_scenarios="$name"
    fi
  done
  IFS="$old_ifs"
else
  all_scenarios="in-tree slow"
fi

# Rig-declared workload selection is owned by Homeboy core because the core
# process builds HOMEBOY_BENCH_EXTRA_WORKLOADS. This intentionally ignores
# HOMEBOY_BENCH_SCENARIOS when extra workloads are present, matching the real
# Node runner class that caused #1843.
if [ -n "$HOMEBOY_BENCH_EXTRA_WORKLOADS" ] || [ "$HOMEBOY_BENCH_LIST_ONLY" = "1" ] || [ -z "$HOMEBOY_BENCH_SCENARIOS" ]; then
  selected="$all_scenarios"
else
  selected=$(printf '%s' "$HOMEBOY_BENCH_SCENARIOS" | tr ',' ' ')
fi

cat > "$HOMEBOY_BENCH_RESULTS_FILE" <<JSON
{
  "component_id": "$HOMEBOY_COMPONENT_ID",
  "iterations": ${HOMEBOY_BENCH_ITERATIONS:-0},
  "scenarios": [
JSON

comma=""
for scenario in $selected; do
  cat >> "$HOMEBOY_BENCH_RESULTS_FILE" <<JSON
    $comma{ "id": "$scenario", "iterations": ${HOMEBOY_BENCH_ITERATIONS:-0}, "metrics": { "p95_ms": 1.0, "warmup_iterations": ${HOMEBOY_BENCH_WARMUP_ITERATIONS:--1} } }
JSON
  comma=",
"
done

cat >> "$HOMEBOY_BENCH_RESULTS_FILE" <<JSON
  ],
  "metric_policies": { "p95_ms": { "direction": "lower_is_better" } }
}
JSON
"#,
        )
        .expect("write bench script");

    #[cfg(unix)]
    {
        let mut permissions = fs::metadata(&script_path)
            .expect("script metadata")
            .permissions();
        permissions.set_mode(0o755);
        fs::set_permissions(&script_path, permissions).expect("chmod script");
    }
}

fn write_failing_bench_extension(home: &TempDir) {
    let extension_dir = home
        .path()
        .join(".config")
        .join("homeboy")
        .join("extensions")
        .join("fixture-bench");
    fs::create_dir_all(&extension_dir).expect("mkdir extension");
    fs::write(
        extension_dir.join("fixture-bench.json"),
        r#"{
                "name": "Fixture Bench",
                "version": "0.0.0",
                "bench": { "extension_script": "bench-runner.sh" }
            }"#,
    )
    .expect("write extension manifest");

    let script_path = extension_dir.join("bench-runner.sh");
    fs::write(
        &script_path,
        r#"#!/bin/sh
if [ "$HOMEBOY_BENCH_LIST_ONLY" = "1" ]; then
  cat > "$HOMEBOY_BENCH_RESULTS_FILE" <<JSON
{
  "component_id": "$HOMEBOY_COMPONENT_ID",
  "iterations": 0,
  "scenarios": [
    { "id": "studio-agent-site-build", "iterations": 0, "metrics": {} }
  ]
}
JSON
  exit 0
fi

cat > "$HOMEBOY_BENCH_RESULTS_FILE" <<JSON
{
  "component_id": "$HOMEBOY_COMPONENT_ID",
  "iterations": ${HOMEBOY_BENCH_ITERATIONS:-0},
  "scenarios": []
}
JSON
printf 'WORKLOAD_ERROR: studio-agent-site-build - warmup iteration 1/1 returned artifact "visual_comparison_dir" with empty path\n' >&2
exit 7
"#,
    )
    .expect("write bench script");

    #[cfg(unix)]
    {
        let mut permissions = fs::metadata(&script_path)
            .expect("script metadata")
            .permissions();
        permissions.set_mode(0o755);
        fs::set_permissions(&script_path, permissions).expect("chmod script");
    }
}

fn write_registered_component(home: &TempDir, component_id: &str, path: &std::path::Path) {
    let component_dir = home
        .path()
        .join(".config")
        .join("homeboy")
        .join("components");
    fs::create_dir_all(&component_dir).expect("mkdir components");
    fs::write(
        component_dir.join(format!("{}.json", component_id)),
        serde_json::json!({
            "id": component_id,
            "local_path": path,
            "extensions": { "fixture-bench": {} }
        })
        .to_string(),
    )
    .expect("write component");
}

fn write_rig(home: &TempDir, rig_id: &str, component_id: &str, path: &std::path::Path) {
    write_rig_with_profiles(home, rig_id, component_id, path, "{}");
}

fn write_rig_with_profiles(
    home: &TempDir,
    rig_id: &str,
    component_id: &str,
    path: &std::path::Path,
    bench_profiles: &str,
) {
    let rig_dir = home.path().join(".config").join("homeboy").join("rigs");
    fs::create_dir_all(&rig_dir).expect("mkdir rigs");
    fs::write(
        rig_dir.join(format!("{}.json", rig_id)),
        format!(
            r#"{{
                    "components": {{
                        "{component_id}": {{
                            "path": "{}",
                            "extensions": {{ "fixture-bench": {{}} }}
                        }}
                    }},
                    "bench": {{ "default_component": "{component_id}" }},
                    "bench_workloads": {{ "fixture-bench": [
                        {{ "path": "${{components.{component_id}.path}}/rig-extra.bench.js" }},
                        {{ "path": "${{components.{component_id}.path}}/rig-slow.bench.mjs" }}
                    ] }},
                    "bench_profiles": {bench_profiles}
                }}"#,
            path.display()
        ),
    )
    .expect("write rig");
}

fn set_rig_warmup(home: &TempDir, rig_id: &str, warmup: u64) {
    let rig_path = home
        .path()
        .join(".config")
        .join("homeboy")
        .join("rigs")
        .join(format!("{}.json", rig_id));
    let mut rig_json: serde_json::Value =
        serde_json::from_str(&fs::read_to_string(&rig_path).expect("read rig")).expect("parse rig");
    rig_json["bench"]["warmup_iterations"] = serde_json::json!(warmup);
    fs::write(
        &rig_path,
        serde_json::to_string(&rig_json).expect("serialize rig"),
    )
    .expect("write rig");
}

fn first_warmup_metric(output: BenchOutput) -> f64 {
    match output {
        BenchOutput::Single(result) => result.results.expect("results").scenarios[0]
            .metrics
            .get("warmup_iterations")
            .expect("warmup metric"),
        _ => panic!("expected single output"),
    }
}

fn list_args(component: Option<&str>, rig: Vec<String>) -> BenchListArgs {
    BenchListArgs {
        comp: PositionalComponentArgs {
            component: component.map(str::to_string),
            path: None,
        },
        extension_override: ExtensionOverrideArgs::default(),
        rig,
        scenario_ids: Vec::new(),
        setting_args: SettingArgs::default(),
        args: Vec::new(),
    }
}

pub(super) fn run_args(
    component: Option<&str>,
    rig: Vec<String>,
    scenario_ids: Vec<String>,
) -> BenchArgs {
    BenchArgs {
        command: None,
        run: BenchRunArgs {
            comp: PositionalComponentArgs {
                component: component.map(str::to_string),
                path: None,
            },
            extension_override: ExtensionOverrideArgs::default(),
            iterations: 1,
            warmup: None,
            runs: 1,
            run_id: None,
            shared_state: None,
            concurrency: 1,
            matrix: Vec::new(),
            runner_pool: None,
            matrix_max_tasks: None,
            matrix_max_queue_depth: None,
            expected_artifact: Vec::new(),
            baseline_args: BaselineArgs {
                baseline: false,
                ignore_baseline: true,
                ratchet: false,
            },
            regression_threshold: 5.0,
            setting_args: SettingArgs::default(),
            args: Vec::new(),
            json_summary: false,
            status_file: None,
            report: Vec::new(),
            rig,
            rig_order: BenchRigOrder::Input,
            rig_concurrency: 1,
            scenario_ids,
            profile: None,
            ci_profile: None,
            ignore_default_baseline: false,
        },
    }
}

fn run_args_with_profile(component: Option<&str>, rig: Vec<String>, profile: &str) -> BenchArgs {
    let mut args = run_args(component, rig, Vec::new());
    args.run.profile = Some(profile.to_string());
    args
}

#[test]
fn filter_strips_boolean_flags() {
    let args = vec!["--ratchet".to_string(), "--filter=Scenario".to_string()];
    let result = filter_homeboy_flags(&args);
    assert_eq!(result, vec!["--filter=Scenario"]);
}

#[test]
fn parses_force_hot_after_bench_without_losing_settings() {
    let normalized = crate::commands::utils::args::normalize(
        [
            "homeboy",
            "bench",
            "--rig",
            "studio-bfb",
            "--iterations",
            "1",
            "--force-hot",
            "--setting",
            "studio_site_build_prompt_variant=astro-docs-content-collection",
        ]
        .into_iter()
        .map(str::to_string)
        .collect(),
    );

    let cli = crate::cli_surface::Cli::try_parse_from(normalized)
        .expect("bench --force-hot --setting should parse");

    assert!(cli.force_hot);
    let crate::cli_surface::Commands::Bench(args) = cli.command else {
        panic!("expected bench command");
    };
    assert_eq!(
        args.run.setting_args.setting,
        vec![(
            "studio_site_build_prompt_variant".to_string(),
            "astro-docs-content-collection".to_string()
        )]
    );
    assert!(
        !args.run.args.iter().any(|arg| arg == "--force-hot"),
        "--force-hot must not be forwarded to bench workloads"
    );
}

#[test]
fn run_list_uses_rig_default_component_and_workloads() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_rig(home, "studio-bfb", "studio", component_dir.path());

        let (output, exit_code) = run_list(&list_args(None, vec!["studio-bfb".to_string()]))
            .expect("rig bench list should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::List(result) => {
                assert_eq!(result.component, "studio");
                assert_eq!(result.component_id, "studio");
                assert_eq!(result.count, 2);
                assert_eq!(result.scenarios[0].id, "rig-extra");
            }
            _ => panic!("expected list output"),
        }
    });
}

#[test]
fn run_list_preserves_registered_component_path() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_registered_component(home, "studio", component_dir.path());

        let (output, exit_code) =
            run_list(&list_args(Some("studio"), Vec::new())).expect("plain bench list should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::List(result) => {
                assert_eq!(result.component, "studio");
                assert_eq!(result.component_id, "studio");
                assert_eq!(result.count, 2);
                assert_eq!(result.scenarios[0].id, "in-tree");
            }
            _ => panic!("expected list output"),
        }
    });
}

#[test]
fn run_list_filters_selected_scenario() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_registered_component(home, "studio", component_dir.path());

        let mut args = list_args(Some("studio"), Vec::new());
        args.scenario_ids = vec!["slow".to_string()];
        let (output, exit_code) = run_list(&args).expect("plain bench list should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::List(result) => {
                assert_eq!(result.count, 1);
                assert_eq!(result.scenarios[0].id, "slow");
            }
            _ => panic!("expected list output"),
        }
    });
}

#[test]
fn run_selects_single_scenario() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_registered_component(home, "studio", component_dir.path());

        let (output, exit_code) = run(
            run_args(Some("studio"), Vec::new(), vec!["slow".to_string()]),
            &GlobalArgs {},
        )
        .expect("selected bench should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::Single(result) => {
                let scenarios = result.results.expect("results").scenarios;
                assert_eq!(scenarios.len(), 1);
                assert_eq!(scenarios[0].id, "slow");
            }
            _ => panic!("expected single output"),
        }
    });
}

#[test]
fn selected_scenario_workload_failure_preserves_runner_error() {
    with_isolated_home(|home| {
        write_failing_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_registered_component(home, "studio", component_dir.path());

        let (output, exit_code) = run(
            run_args(
                Some("studio"),
                Vec::new(),
                vec!["studio-agent-site-build".to_string()],
            ),
            &GlobalArgs {},
        )
        .expect("runner failure should return structured bench output");

        assert_eq!(exit_code, 7);
        match output {
            BenchOutput::Single(result) => {
                assert_eq!(result.status, "failed");
                assert_eq!(result.exit_code, 7);
                assert_eq!(result.results.expect("partial results").scenarios.len(), 0);
                let failure = result.failure.expect("runner failure metadata");
                assert_eq!(
                    failure.scenario_id.as_deref(),
                    Some("studio-agent-site-build")
                );
                assert!(failure.stderr_tail.contains("WORKLOAD_ERROR"));
                assert!(failure.stderr_tail.contains("warmup iteration 1/1"));
                assert!(failure.stderr_tail.contains("component id `studio`"));
                assert!(failure
                    .stderr_tail
                    .contains("workload id `studio-agent-site-build`"));
                assert!(failure
                    .stderr_tail
                    .contains("scenario id `studio-agent-site-build`"));
                assert!(failure.stderr_tail.contains("phase `warmup`"));
                assert!(failure.stderr_tail.contains("iteration 1/1"));
                assert!(failure
                    .stderr_tail
                    .contains("artifact key `visual_comparison_dir`"));
                assert!(failure.stderr_tail.contains("Omit optional artifacts"));
            }
            _ => panic!("expected single output"),
        }
    });
}

#[test]
fn parses_rig_run_options_without_component() {
    let cli = TestCli::try_parse_from([
        "bench",
        "--rig",
        "studio-agent-sdk,studio-agent-pi",
        "--scenario",
        "studio-agent-runtime",
        "--runs",
        "3",
        "--iterations",
        "1",
    ])
    .expect("rig bench options without component should parse");

    assert_eq!(
        cli.bench.run.rig,
        vec!["studio-agent-sdk", "studio-agent-pi"]
    );
    assert_eq!(cli.bench.run.scenario_ids, vec!["studio-agent-runtime"]);
    assert_eq!(cli.bench.run.runs, 3);
    assert_eq!(cli.bench.run.iterations, 1);
    assert!(cli.bench.run.comp.id().is_none());
}

#[test]
fn parses_rig_order_flag() {
    let cli = TestCli::try_parse_from([
        "bench",
        "--rig",
        "studio-agent-sdk,studio-agent-pi",
        "--rig-order",
        "reverse",
    ])
    .expect("bench --rig-order should parse");

    assert_eq!(cli.bench.run.rig_order, BenchRigOrder::Reverse);
}

#[test]
fn parses_rig_concurrency_flag() {
    let cli = TestCli::try_parse_from([
        "bench",
        "--rig",
        "studio-agent-sdk,studio-agent-pi",
        "--rig-concurrency",
        "2",
    ])
    .expect("bench --rig-concurrency should parse");

    assert_eq!(cli.bench.run.rig_concurrency, 2);
}

#[test]
fn rejects_allow_stale_dependencies_flag() {
    let err = match TestCli::try_parse_from(["bench", "homeboy", "--allow-stale-dependencies"]) {
        Ok(_) => panic!("stale dependency bypass should not parse"),
        Err(err) => err,
    };

    assert_eq!(err.kind(), ErrorKind::UnknownArgument);
}

#[test]
fn run_selects_multiple_scenarios() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_registered_component(home, "studio", component_dir.path());

        let (output, exit_code) = run(
            run_args(
                Some("studio"),
                Vec::new(),
                vec!["in-tree".to_string(), "slow".to_string()],
            ),
            &GlobalArgs {},
        )
        .expect("selected bench should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::Single(result) => {
                let scenario_ids: Vec<String> = result
                    .results
                    .expect("results")
                    .scenarios
                    .into_iter()
                    .map(|scenario| scenario.id)
                    .collect();
                assert_eq!(
                    scenario_ids,
                    vec!["in-tree".to_string(), "slow".to_string()]
                );
            }
            _ => panic!("expected single output"),
        }
    });
}

#[test]
fn unknown_scenario_reports_discovered_ids() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_registered_component(home, "studio", component_dir.path());

        let err = match run(
            run_args(Some("studio"), Vec::new(), vec!["missing".to_string()]),
            &GlobalArgs {},
        ) {
            Ok(_) => panic!("unknown scenario should fail"),
            Err(err) => err,
        };
        let message = err.to_string();

        assert!(message.contains("missing"), "got: {}", message);
        assert!(message.contains("in-tree"), "got: {}", message);
        assert!(message.contains("slow"), "got: {}", message);
    });
}

#[test]
fn cross_rig_run_passes_selector_to_each_rig() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_a = tempfile::TempDir::new().expect("component a");
        let component_b = tempfile::TempDir::new().expect("component b");
        write_rig(home, "rig-a", "studio", component_a.path());
        write_rig(home, "rig-b", "studio", component_b.path());

        let (output, exit_code) = run(
            run_args(
                None,
                vec!["rig-a".to_string(), "rig-b".to_string()],
                vec!["rig-slow".to_string()],
            ),
            &GlobalArgs {},
        )
        .expect("cross-rig selected bench should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::Comparison(result) => {
                assert_eq!(result.rigs.len(), 2);
                for rig in result.rigs {
                    let scenarios = rig.results.expect("rig results").scenarios;
                    assert_eq!(scenarios.len(), 1);
                    assert_eq!(scenarios[0].id, "rig-slow");
                }
            }
            _ => panic!("expected comparison output"),
        }
    });
}

#[test]
fn cross_rig_json_summary_omits_full_results_payload() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_a = tempfile::TempDir::new().expect("component a");
        let component_b = tempfile::TempDir::new().expect("component b");
        write_rig(home, "rig-a", "studio", component_a.path());
        write_rig(home, "rig-b", "studio", component_b.path());

        let mut args = run_args(
            None,
            vec!["rig-a".to_string(), "rig-b".to_string()],
            vec!["rig-slow".to_string()],
        );
        args.run.json_summary = true;

        let (output, exit_code) =
            run(args, &GlobalArgs {}).expect("cross-rig selected bench summary should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::ComparisonSummary(result) => {
                assert!(result.summary_only);
                assert_eq!(result.rigs.len(), 2);
                assert_eq!(result.rigs[0].rig_id, "rig-a");
                assert_eq!(result.rigs[1].rig_id, "rig-b");

                let value = serde_json::to_value(result).expect("serialize summary");
                assert!(value.get("diff").is_none());
                assert!(value["rigs"][0].get("results").is_none());
                assert!(value["rigs"][0].get("artifacts").is_none());
                assert!(value["rigs"][0].get("rig_state").is_none());
            }
            _ => panic!("expected comparison summary output"),
        }
    });
}

#[test]
fn cross_rig_reverse_order_flips_reference_and_execution_order() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_a = tempfile::TempDir::new().expect("component a");
        let component_b = tempfile::TempDir::new().expect("component b");
        write_rig(home, "rig-a", "studio", component_a.path());
        write_rig(home, "rig-b", "studio", component_b.path());

        let mut args = run_args(
            None,
            vec!["rig-a".to_string(), "rig-b".to_string()],
            vec!["rig-slow".to_string()],
        );
        args.run.rig_order = BenchRigOrder::Reverse;

        let (output, exit_code) = run(args, &GlobalArgs {})
            .expect("cross-rig selected bench should run in reverse order");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::Comparison(result) => {
                assert_eq!(result.rigs.len(), 2);
                assert_eq!(result.rigs[0].rig_id, "rig-b");
                assert_eq!(result.rigs[1].rig_id, "rig-a");
            }
            _ => panic!("expected comparison output"),
        }
    });
}

#[test]
fn single_rig_selector_filters_extra_workloads_before_execution() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_rig(home, "rig-a", "studio", component_dir.path());

        let (output, exit_code) = run(
            run_args(
                None,
                vec!["rig-a".to_string()],
                vec!["rig-slow".to_string()],
            ),
            &GlobalArgs {},
        )
        .expect("single-rig selected bench should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::Single(result) => {
                assert_eq!(result.iterations, 1);
                let scenarios = result.results.expect("results").scenarios;
                assert_eq!(scenarios.len(), 1);
                assert_eq!(scenarios[0].id, "rig-slow");
                assert_eq!(scenarios[0].iterations, 1);
            }
            _ => panic!("expected single output"),
        }
    });
}

#[test]
fn run_profile_selects_rig_profile_scenarios() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_rig_with_profiles(
            home,
            "studio-bfb",
            "studio",
            component_dir.path(),
            r#"{ "substrate": ["rig-extra"] }"#,
        );

        let (output, exit_code) = run(
            run_args_with_profile(None, vec!["studio-bfb".to_string()], "substrate"),
            &GlobalArgs {},
        )
        .expect("profile bench should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::Single(result) => {
                let scenarios = result.results.expect("results").scenarios;
                assert_eq!(scenarios.len(), 1);
                assert_eq!(scenarios[0].id, "rig-extra");
            }
            _ => panic!("expected single output"),
        }
    });
}

#[test]
fn single_rig_runs_preserve_run_level_summaries_after_selection() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_rig(home, "rig-a", "studio", component_dir.path());
        let mut args = run_args(
            None,
            vec!["rig-a".to_string()],
            vec!["rig-slow".to_string()],
        );
        args.run.runs = 3;

        let (output, exit_code) =
            run(args, &GlobalArgs {}).expect("single-rig selected bench should run multiple runs");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::Single(result) => {
                let scenarios = result.results.expect("results").scenarios;
                assert_eq!(scenarios.len(), 1);
                let runs = scenarios[0].runs.as_ref().expect("per-run metrics");
                assert_eq!(runs.len(), 3);
                assert!(scenarios[0].runs_summary.as_ref().is_some_and(|summary| {
                    summary.get("p95_ms").is_some_and(|metric| metric.n == 3)
                }));
            }
            _ => panic!("expected single output"),
        }
    });
}

#[test]
fn unknown_profile_lists_available_profiles() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_rig_with_profiles(
            home,
            "studio-bfb",
            "studio",
            component_dir.path(),
            r#"{ "substrate": ["rig-extra"], "smoke": ["rig-slow"] }"#,
        );

        let err = match run(
            run_args_with_profile(None, vec!["studio-bfb".to_string()], "agentic"),
            &GlobalArgs {},
        ) {
            Ok(_) => panic!("unknown profile should fail"),
            Err(err) => err,
        };
        let message = err.to_string();

        assert!(message.contains("agentic"), "got: {}", message);
        assert!(message.contains("substrate"), "got: {}", message);
        assert!(message.contains("smoke"), "got: {}", message);
    });
}

#[test]
fn profile_with_unknown_scenario_lists_discovered_scenarios() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_rig_with_profiles(
            home,
            "studio-bfb",
            "studio",
            component_dir.path(),
            r#"{ "broken": ["missing-scenario"] }"#,
        );

        let err = match run(
            run_args_with_profile(None, vec!["studio-bfb".to_string()], "broken"),
            &GlobalArgs {},
        ) {
            Ok(_) => panic!("unknown scenario in profile should fail"),
            Err(err) => err,
        };
        let message = err.to_string();

        assert!(message.contains("missing-scenario"), "got: {}", message);
        assert!(message.contains("rig-extra"), "got: {}", message);
        assert!(message.contains("rig-slow"), "got: {}", message);
    });
}

#[test]
fn cross_rig_runs_preserve_run_level_summaries_after_selection() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_a = tempfile::TempDir::new().expect("component a");
        let component_b = tempfile::TempDir::new().expect("component b");
        write_rig(home, "rig-a", "studio", component_a.path());
        write_rig(home, "rig-b", "studio", component_b.path());
        let mut args = run_args(
            None,
            vec!["rig-a".to_string(), "rig-b".to_string()],
            vec!["rig-slow".to_string()],
        );
        args.run.runs = 3;

        let (output, exit_code) =
            run(args, &GlobalArgs {}).expect("cross-rig selected bench should run multiple runs");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::Comparison(result) => {
                assert_eq!(result.rigs.len(), 2);
                assert!(result
                    .summary
                    .iter()
                    .all(|summary| summary.rows.iter().all(|row| row.n == Some(3))));
                for rig in result.rigs {
                    let scenarios = rig.results.expect("rig results").scenarios;
                    assert_eq!(scenarios.len(), 1);
                    assert_eq!(scenarios[0].id, "rig-slow");
                    assert_eq!(scenarios[0].runs.as_ref().expect("runs").len(), 3);
                }
            }
            _ => panic!("expected comparison output"),
        }
    });
}

#[test]
fn cross_rig_profile_requires_every_rig_to_define_profile() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_a = tempfile::TempDir::new().expect("component a");
        let component_b = tempfile::TempDir::new().expect("component b");
        write_rig_with_profiles(
            home,
            "rig-a",
            "studio",
            component_a.path(),
            r#"{ "substrate": ["rig-extra"] }"#,
        );
        write_rig_with_profiles(
            home,
            "rig-b",
            "studio",
            component_b.path(),
            r#"{ "smoke": ["rig-slow"] }"#,
        );

        let err = match run(
            run_args_with_profile(
                None,
                vec!["rig-a".to_string(), "rig-b".to_string()],
                "substrate",
            ),
            &GlobalArgs {},
        ) {
            Ok(_) => panic!("missing cross-rig profile should fail"),
            Err(err) => err,
        };
        let message = err.to_string();

        assert!(message.contains("rig-b"), "got: {}", message);
        assert!(message.contains("substrate"), "got: {}", message);
        assert!(message.contains("smoke"), "got: {}", message);
    });
}

#[test]
fn cross_rig_profile_selects_profile_for_each_rig() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_a = tempfile::TempDir::new().expect("component a");
        let component_b = tempfile::TempDir::new().expect("component b");
        write_rig_with_profiles(
            home,
            "rig-a",
            "studio",
            component_a.path(),
            r#"{ "substrate": ["rig-extra"] }"#,
        );
        write_rig_with_profiles(
            home,
            "rig-b",
            "studio",
            component_b.path(),
            r#"{ "substrate": ["rig-slow"] }"#,
        );

        let (output, exit_code) = run(
            run_args_with_profile(
                None,
                vec!["rig-a".to_string(), "rig-b".to_string()],
                "substrate",
            ),
            &GlobalArgs {},
        )
        .expect("cross-rig profile bench should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::Comparison(result) => {
                assert_eq!(result.rigs.len(), 2);
                assert_eq!(
                    result.rigs[0]
                        .results
                        .as_ref()
                        .expect("rig a results")
                        .scenarios[0]
                        .id,
                    "rig-extra"
                );
                assert_eq!(
                    result.rigs[1]
                        .results
                        .as_ref()
                        .expect("rig b results")
                        .scenarios[0]
                        .id,
                    "rig-slow"
                );
            }
            _ => panic!("expected comparison output"),
        }
    });
}

#[test]
fn run_list_requires_rig_default_component_when_component_omitted() {
    with_isolated_home(|home| {
        let rig_dir = home.path().join(".config").join("homeboy").join("rigs");
        fs::create_dir_all(&rig_dir).expect("mkdir rigs");
        fs::write(rig_dir.join("empty.json"), r#"{ "bench": {} }"#).expect("write rig");

        let err = match run_list(&list_args(None, vec!["empty".to_string()])) {
            Ok(_) => panic!("missing default component should error"),
            Err(err) => err,
        };
        let message = err.to_string();
        assert!(
            message.contains("bench.default_component"),
            "expected default-component error, got: {}",
            message
        );
    });
}

#[test]
fn filter_strips_all_boolean_flags() {
    let args = vec![
        "--baseline".to_string(),
        "--ignore-baseline".to_string(),
        "--ratchet".to_string(),
        "--json-summary".to_string(),
    ];
    assert!(filter_homeboy_flags(&args).is_empty());
}

#[test]
fn filter_strips_iterations_space_form() {
    let args = vec![
        "--iterations".to_string(),
        "50".to_string(),
        "--filter=Scenario".to_string(),
    ];
    assert_eq!(filter_homeboy_flags(&args), vec!["--filter=Scenario"]);
}

#[test]
fn filter_strips_iterations_equals_form() {
    let args = vec!["--iterations=50".to_string(), "--keep".to_string()];
    assert_eq!(filter_homeboy_flags(&args), vec!["--keep"]);
}

#[test]
fn parses_warmup_flag() {
    let cli = TestCli::try_parse_from(["bench", "homeboy", "--warmup", "3"])
        .expect("bench --warmup should parse");

    assert_eq!(cli.bench.run.warmup, Some(3));
}

#[test]
fn parses_side_by_side_report_flag() {
    let cli = TestCli::try_parse_from([
        "bench",
        "studio",
        "--rig",
        "baseline,candidate",
        "--report",
        "side-by-side",
    ])
    .expect("bench --report side-by-side should parse");

    assert_eq!(cli.bench.run.report, vec![BenchReportFormat::SideBySide]);
}

#[test]
fn rejects_negative_warmup_flag() {
    let err = match TestCli::try_parse_from(["bench", "homeboy", "--warmup", "-1"]) {
        Ok(_) => panic!("negative warmup must fail at parse time"),
        Err(err) => err,
    };

    assert!(
        err.to_string().contains("invalid value"),
        "expected invalid value error, got: {}",
        err
    );
}

#[test]
fn filter_strips_warmup_forms() {
    let args = vec![
        "--warmup".to_string(),
        "3".to_string(),
        "--filter=Scenario".to_string(),
    ];
    assert_eq!(filter_homeboy_flags(&args), vec!["--filter=Scenario"]);

    let args = vec!["--warmup=3".to_string(), "--keep".to_string()];
    assert_eq!(filter_homeboy_flags(&args), vec!["--keep"]);
}

#[test]
fn unrigged_bench_forwards_cli_warmup() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_registered_component(home, "studio", component_dir.path());

        let mut args = run_args(Some("studio"), Vec::new(), Vec::new());
        args.run.warmup = Some(4);

        let (output, exit_code) = run(args, &GlobalArgs {}).expect("bench should run");

        assert_eq!(exit_code, 0);
        assert_eq!(first_warmup_metric(output), 4.0);
    });
}

#[test]
fn unrigged_bench_omits_warmup_env_by_default() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_registered_component(home, "studio", component_dir.path());

        let (output, exit_code) = run(
            run_args(Some("studio"), Vec::new(), Vec::new()),
            &GlobalArgs {},
        )
        .expect("bench should run");

        assert_eq!(exit_code, 0);
        assert_eq!(first_warmup_metric(output), -1.0);
    });
}

#[test]
fn single_rig_bench_forwards_rig_warmup() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_rig(home, "rig-a", "studio", component_dir.path());
        set_rig_warmup(home, "rig-a", 6);

        let (output, exit_code) = run(
            run_args(None, vec!["rig-a".to_string()], Vec::new()),
            &GlobalArgs {},
        )
        .expect("rig bench should run");

        assert_eq!(exit_code, 0);
        assert_eq!(first_warmup_metric(output), 6.0);
    });
}

#[test]
fn cli_warmup_overrides_rig_warmup() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_dir = tempfile::TempDir::new().expect("component dir");
        write_rig(home, "rig-a", "studio", component_dir.path());
        set_rig_warmup(home, "rig-a", 6);

        let mut args = run_args(None, vec!["rig-a".to_string()], Vec::new());
        args.run.warmup = Some(2);

        let (output, exit_code) = run(args, &GlobalArgs {}).expect("rig bench should run");

        assert_eq!(exit_code, 0);
        assert_eq!(first_warmup_metric(output), 2.0);
    });
}

#[test]
fn cross_rig_bench_uses_each_rig_warmup() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_a = tempfile::TempDir::new().expect("component a");
        let component_b = tempfile::TempDir::new().expect("component b");
        write_rig(home, "rig-a", "studio", component_a.path());
        write_rig(home, "rig-b", "studio", component_b.path());
        set_rig_warmup(home, "rig-a", 2);
        set_rig_warmup(home, "rig-b", 5);

        let (output, exit_code) = run(
            run_args(
                None,
                vec!["rig-a".to_string(), "rig-b".to_string()],
                Vec::new(),
            ),
            &GlobalArgs {},
        )
        .expect("cross-rig bench should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::Comparison(result) => {
                assert_eq!(result.rigs.len(), 2);
                assert_eq!(
                    result.rigs[0]
                        .results
                        .as_ref()
                        .expect("rig-a results")
                        .scenarios[0]
                        .metrics
                        .get("warmup_iterations"),
                    Some(2.0)
                );
                assert_eq!(
                    result.rigs[1]
                        .results
                        .as_ref()
                        .expect("rig-b results")
                        .scenarios[0]
                        .metrics
                        .get("warmup_iterations"),
                    Some(5.0)
                );
            }
            _ => panic!("expected comparison output"),
        }
    });
}

#[test]
fn cross_rig_cli_warmup_overrides_all_rigs() {
    with_isolated_home(|home| {
        write_bench_extension(home);
        let component_a = tempfile::TempDir::new().expect("component a");
        let component_b = tempfile::TempDir::new().expect("component b");
        write_rig(home, "rig-a", "studio", component_a.path());
        write_rig(home, "rig-b", "studio", component_b.path());
        set_rig_warmup(home, "rig-a", 2);
        set_rig_warmup(home, "rig-b", 5);

        let mut args = run_args(
            None,
            vec!["rig-a".to_string(), "rig-b".to_string()],
            Vec::new(),
        );
        args.run.warmup = Some(9);

        let (output, exit_code) = run(args, &GlobalArgs {}).expect("cross-rig bench should run");

        assert_eq!(exit_code, 0);
        match output {
            BenchOutput::Comparison(result) => {
                assert_eq!(result.rigs.len(), 2);
                for rig in result.rigs {
                    assert_eq!(
                        rig.results.expect("rig results").scenarios[0]
                            .metrics
                            .get("warmup_iterations"),
                        Some(9.0)
                    );
                }
            }
            _ => panic!("expected comparison output"),
        }
    });
}

#[test]
fn parses_shared_state_and_concurrency_flags() {
    let cli = TestCli::try_parse_from([
        "bench",
        "homeboy",
        "--shared-state",
        "/tmp/foo",
        "--concurrency",
        "4",
    ])
    .expect("shared-state and concurrency flags should parse");

    assert_eq!(cli.bench.run.shared_state, Some(PathBuf::from("/tmp/foo")));
    assert_eq!(cli.bench.run.concurrency, 4);
}

#[test]
fn filter_strips_shared_state_and_concurrency_forms() {
    let args = vec![
        "--shared-state".to_string(),
        "/tmp/foo".to_string(),
        "--concurrency".to_string(),
        "4".to_string(),
        "--filter=Scenario".to_string(),
    ];
    assert_eq!(filter_homeboy_flags(&args), vec!["--filter=Scenario"]);

    let args = vec![
        "--shared-state=/tmp/foo".to_string(),
        "--concurrency=4".to_string(),
        "--keep".to_string(),
    ];
    assert_eq!(filter_homeboy_flags(&args), vec!["--keep"]);
}

#[test]
fn filter_strips_rig_order_forms() {
    let args = vec![
        "--rig-order".to_string(),
        "reverse".to_string(),
        "--filter=Scenario".to_string(),
    ];
    assert_eq!(filter_homeboy_flags(&args), vec!["--filter=Scenario"]);

    let args = vec!["--rig-order=reverse".to_string(), "--keep".to_string()];
    assert_eq!(filter_homeboy_flags(&args), vec!["--keep"]);
}

#[test]
fn filter_strips_report_forms() {
    let args = vec![
        "--report".to_string(),
        "side-by-side".to_string(),
        "--filter=Scenario".to_string(),
    ];
    assert_eq!(filter_homeboy_flags(&args), vec!["--filter=Scenario"]);

    let args = vec!["--report=side-by-side".to_string(), "--keep".to_string()];
    assert_eq!(filter_homeboy_flags(&args), vec!["--keep"]);
}

#[test]
fn filter_strips_rig_concurrency_forms() {
    let args = vec![
        "--rig-concurrency".to_string(),
        "2".to_string(),
        "--filter=Scenario".to_string(),
    ];
    assert_eq!(filter_homeboy_flags(&args), vec!["--filter=Scenario"]);

    let args = vec!["--rig-concurrency=2".to_string(), "--keep".to_string()];
    assert_eq!(filter_homeboy_flags(&args), vec!["--keep"]);
}

#[test]
fn filter_strips_regression_threshold_forms() {
    let args = vec![
        "--regression-threshold".to_string(),
        "10".to_string(),
        "--keep".to_string(),
    ];
    assert_eq!(filter_homeboy_flags(&args), vec!["--keep"]);

    let args = vec![
        "--regression-threshold=10".to_string(),
        "--keep".to_string(),
    ];
    assert_eq!(filter_homeboy_flags(&args), vec!["--keep"]);
}

#[test]
fn filter_preserves_unknown_flags() {
    let args = vec![
        "--filter=Scenario".to_string(),
        "--verbose".to_string(),
        "extra".to_string(),
    ];
    assert_eq!(filter_homeboy_flags(&args), args);
}

#[test]
fn filter_handles_empty() {
    assert!(filter_homeboy_flags(&[]).is_empty());
}

#[test]
fn filter_handles_mixed() {
    let args = vec![
        "--ratchet".to_string(),
        "--iterations".to_string(),
        "25".to_string(),
        "--filter=hot_path".to_string(),
        "--regression-threshold=7.5".to_string(),
        "--verbose".to_string(),
    ];
    assert_eq!(
        filter_homeboy_flags(&args),
        vec!["--filter=hot_path", "--verbose"]
    );
}

#[test]
fn bench_output_single_serializes_with_tagged_payload() {
    let single = BenchCommandOutput {
        passed: true,
        status: "passed".to_string(),
        component: "studio".to_string(),
        exit_code: 0,
        iterations: 10,
        artifacts: Vec::new(),
        results: None,
        budget_findings: Vec::new(),
        gate_results: Vec::new(),
        gate_failures: Vec::new(),
        baseline_comparison: None,
        hints: None,
        rig_state: None,
        failure: None,
        diagnostics: Vec::new(),
        ci_context: None,
    };
    let value = serde_json::to_value(BenchOutput::Single(single)).unwrap();
    assert_eq!(
        value.get("variant"),
        Some(&serde_json::Value::String("single".to_string()))
    );
    let payload = value
        .get("payload")
        .expect("single bench output has payload");
    assert_eq!(payload.get("passed"), Some(&serde_json::Value::Bool(true)));
    assert_eq!(
        payload.get("component"),
        Some(&serde_json::Value::String("studio".to_string()))
    );
}

#[test]
fn bench_output_comparison_serializes_with_tagged_payload() {
    let (cmp, _) = aggregate_comparison("studio".to_string(), 10, Vec::new());
    let value = serde_json::to_value(BenchOutput::Comparison(cmp)).unwrap();
    assert_eq!(
        value.get("variant"),
        Some(&serde_json::Value::String("comparison".to_string()))
    );
    assert_eq!(
        value
            .get("payload")
            .and_then(|payload| payload.get("comparison")),
        Some(&serde_json::Value::String("cross_rig".to_string()))
    );
}

fn ctx_with_accepted_setting_keys(
    accepted: &[&str],
) -> homeboy::core::engine::execution_context::ExecutionContext {
    use homeboy::core::engine::execution_context::ExecutionContext;
    ExecutionContext {
        component: Default::default(),
        component_id: "studio".to_string(),
        source_path: std::path::PathBuf::from("/tmp/studio"),
        git_root: None,
        extension_id: Some("rust".to_string()),
        extension_path: None,
        settings: Vec::new(),
        accepted_setting_keys: accepted.iter().map(|s| s.to_string()).collect(),
    }
}

#[test]
fn unknown_setting_key_warns_before_run() {
    let ctx = ctx_with_accepted_setting_keys(&["workflow_bench_env", "iterations"]);
    let setting_args = SettingArgs {
        // `bench_env` is a typo for the declared `workflow_bench_env`.
        setting: vec![("bench_env.CORPUS".to_string(), "1000".to_string())],
        setting_json: Vec::new(),
    };

    let warning =
        unknown_setting_keys_warning(&ctx, &setting_args).expect("unknown key should warn");
    assert!(
        warning.contains("bench_env"),
        "warning names the typo: {warning}"
    );
    assert!(
        warning.contains("workflow_bench_env"),
        "warning lists accepted settings: {warning}"
    );
    assert!(
        warning.contains("extension 'rust'"),
        "warning names the resolved extension: {warning}"
    );
}

#[test]
fn declared_setting_key_does_not_warn() {
    let ctx = ctx_with_accepted_setting_keys(&["workflow_bench_env"]);
    let setting_args = SettingArgs {
        setting: vec![("workflow_bench_env.CORPUS".to_string(), "1000".to_string())],
        setting_json: Vec::new(),
    };

    assert!(unknown_setting_keys_warning(&ctx, &setting_args).is_none());
}

#[cfg(test)]
#[path = "../../../tests/core/rig/bench_default_baseline_dispatch_test.rs"]
mod bench_default_baseline_dispatch_test;
#[cfg(test)]
#[path = "../../../tests/core/rig/bench_default_baseline_output_test.rs"]
mod bench_default_baseline_output_test;
#[cfg(test)]
#[path = "../../../tests/core/rig/bench_prepare_pipeline_test.rs"]
mod bench_prepare_pipeline_test;
#[cfg(test)]
#[path = "../../../tests/core/rig/bench_resource_lease_test.rs"]
mod bench_resource_lease_test;
#[cfg(test)]
#[path = "../../../tests/core/rig/bench_rig_concurrency_dispatch_test.rs"]
mod bench_rig_concurrency_dispatch_test;
#[cfg(test)]
#[path = "../../../tests/core/rig/bench_rig_path_override_test.rs"]
mod bench_rig_path_override_test;
