use super::super::dispatch::{
    compact_exec_command_run, raw_exec_command_run, render_compact_exec_output,
};
use super::super::exec::{
    exec, exec_workspace_context, prepare_runner_exec_command, prepare_runner_exec_env,
    prepare_runner_exec_secret_env_plan, promote_runner_exec_artifact_dirs,
    promote_runner_exec_artifacts, read_bounded, read_runner_exec_script,
    validate_runner_exec_public_env, RUNNER_EXEC_SCRIPT_LIMIT_BYTES,
};

use homeboy::core::observation::{NewRunRecord, ObservationStore};
use homeboy::core::runners::{self as runner, RunnerExecMode, RunnerExecOutput};
use homeboy::core::server;

#[test]
fn raw_exec_command_run_keeps_structured_output_and_presentation_streams() {
    let run = raw_exec_command_run(
        RunnerExecOutput {
            variant: "exec",
            command: "runner.exec",
            runner_id: "lab".to_string(),
            dry_run: false,
            mode: runner::RunnerExecMode::Daemon,
            argv: vec!["printf".to_string(), "hello".to_string()],
            remote_cwd: "/workspace".to_string(),
            exit_code: 7,
            stdout: "hello\n".to_string(),
            stderr: "warn\n".to_string(),
            source_snapshot: None,
            job: None,
            runner_job: None,
            job_id: Some("job-123".to_string()),
            job_events: None,
            mirror_run_id: None,
            patch: None,
            mutation_artifacts: None,
            artifacts: Vec::new(),
            promoted_outputs: Vec::new(),
            structured_summaries: Vec::new(),
            metrics: None,
            capture: None,
            execution_record: None,
            runner_result: None,
            handoff: None,
            diagnostics: None,
        },
        7,
    );

    assert_eq!(run.exit_code, 7);
    assert_eq!(run.presentation.stdout.as_deref(), Some("hello\n"));
    assert_eq!(run.presentation.stderr.as_deref(), Some("warn\n"));

    let value = run.stdout_result.expect("structured output");
    assert_eq!(value["command"], "runner.exec");
    assert_eq!(value["variant"], "exec");
    assert_eq!(value["stdout"], "hello\n");
    assert_eq!(value["stderr"], "warn\n");
    assert_eq!(value["job_id"], "job-123");
}

#[test]
fn compact_exec_output_shows_metadata_and_streams_once() {
    let output = RunnerExecOutput {
        variant: "exec",
        command: "runner.exec",
        runner_id: "lab".to_string(),
        dry_run: false,
        mode: runner::RunnerExecMode::Daemon,
        argv: vec![
            "homeboy".to_string(),
            "runs".to_string(),
            "list".to_string(),
        ],
        remote_cwd: "/workspace".to_string(),
        exit_code: 0,
        stdout: r#"{"runs":[{"id":"run-1"}]}"#.to_string(),
        stderr: "warn\n".to_string(),
        source_snapshot: None,
        job: None,
        runner_job: None,
        job_id: Some("job-123".to_string()),
        job_events: None,
        mirror_run_id: Some("mirror-1".to_string()),
        patch: None,
        mutation_artifacts: None,
        artifacts: Vec::new(),
        promoted_outputs: Vec::new(),
        structured_summaries: Vec::new(),
        metrics: None,
        capture: None,
        execution_record: None,
        runner_result: None,
        handoff: None,
        diagnostics: None,
    };

    let rendered = render_compact_exec_output(&output);

    assert!(rendered.contains("Runner: lab\n"));
    assert!(rendered.contains("Remote cwd: /workspace\n"));
    assert!(rendered.contains("Exit status: 0\n"));
    assert!(rendered.contains("Job: job-123\n"));
    assert!(rendered.contains("Run: mirror-1\n"));
    assert!(rendered.contains("Stdout:\n{\n  \"runs\": ["));
    assert!(rendered.contains("Stderr:\nwarn\n"));
    assert_eq!(rendered.matches("Stdout:").count(), 1);
    assert_eq!(rendered.matches("Stderr:").count(), 1);
}

#[test]
fn compact_exec_command_run_preserves_full_output_file_payload() {
    let output = RunnerExecOutput {
        variant: "exec",
        command: "runner.exec",
        runner_id: "lab".to_string(),
        dry_run: false,
        mode: runner::RunnerExecMode::Daemon,
        argv: vec!["printf".to_string(), "hello".to_string()],
        remote_cwd: "/workspace".to_string(),
        exit_code: 0,
        stdout: "hello\n".to_string(),
        stderr: String::new(),
        source_snapshot: None,
        job: None,
        runner_job: None,
        job_id: Some("job-123".to_string()),
        job_events: None,
        mirror_run_id: None,
        patch: None,
        mutation_artifacts: None,
        artifacts: Vec::new(),
        promoted_outputs: Vec::new(),
        structured_summaries: Vec::new(),
        metrics: None,
        capture: None,
        execution_record: None,
        runner_result: None,
        handoff: None,
        diagnostics: None,
    };

    let run = compact_exec_command_run(output, 0);

    assert!(run
        .stdout_result
        .as_ref()
        .expect("compact stdout")
        .contains("Stdout:\nhello\n"));
    let output_file = run
        .output_file_result
        .expect("full output file payload")
        .expect("serialized payload");
    assert_eq!(output_file["command"], "runner.exec");
    assert_eq!(output_file["stdout"], "hello\n");
    assert_eq!(output_file["job_id"], "job-123");
}

#[test]
fn sync_workspace_exec_rejects_explicit_cwd() {
    let err = exec_workspace_context(
        "lab",
        Some("/runner/workspace/project".to_string()),
        Some("/local/project".to_string()),
        false,
    )
    .expect_err("cwd and sync-workspace must conflict");

    assert!(err
        .to_string()
        .contains("--cwd and --sync-workspace are mutually exclusive"));
}

#[test]
fn read_bounded_retains_full_source_within_limit() {
    let (bytes, capture) = read_bounded(&b"echo hi"[..], 1024).expect("read bounded");

    assert_eq!(bytes, b"echo hi");
    assert_eq!(capture.limit_bytes, 1024);
    assert_eq!(capture.seen_bytes, 7);
    assert_eq!(capture.retained_bytes, 7);
    assert!(!capture.truncated);
}

#[test]
fn read_bounded_marks_truncated_when_source_exceeds_limit() {
    let source = [b'x'; 16];
    let (bytes, capture) = read_bounded(&source[..], 4).expect("read bounded");

    assert_eq!(bytes.len(), 4);
    assert_eq!(capture.limit_bytes, 4);
    assert_eq!(capture.retained_bytes, 4);
    assert!(capture.seen_bytes > capture.retained_bytes);
    assert!(capture.truncated);
}

#[test]
fn read_runner_exec_script_rejects_oversized_script() {
    use std::io::Write;

    let mut file = tempfile::NamedTempFile::new().expect("temp script");
    let oversized = vec![b'a'; RUNNER_EXEC_SCRIPT_LIMIT_BYTES + 1];
    file.write_all(&oversized).expect("write script");
    let path = file.path().to_string_lossy().to_string();

    let err = read_runner_exec_script(&path).expect_err("oversized script rejected");
    assert!(err.to_string().contains("byte limit"));
}

#[test]
fn script_file_prepares_bash_stdin_command() {
    let command = prepare_runner_exec_command(Some(&"echo '$GREETING'".to_string()), Vec::new())
        .expect("script command");

    assert_eq!(command[0], "bash");
    assert_eq!(command[1], "-c");
    assert!(command[2].contains("printf '%s'"));
    assert!(command[2].contains("bash -s"));
    assert!(!command[2].contains("HOMEBOY_RUNNER_EXEC_SCRIPT"));
    assert!(command[2].contains("'echo '\\''$GREETING'\\'''"));
}

#[test]
fn script_file_rejects_extra_argv() {
    let err = prepare_runner_exec_command(Some(&"echo hi".to_string()), vec!["printf".to_string()])
        .expect_err("script plus argv should fail");

    assert!(err
        .to_string()
        .contains("either --script-file or a command"));
}

#[test]
fn env_parser_injects_script_body_without_shell_quoting() {
    let env = prepare_runner_exec_env(
        vec!["GREETING=hello world".to_string()],
        Some("echo \"$GREETING\""),
    )
    .expect("env");

    assert_eq!(env["GREETING"], "hello world");
    assert!(!env.contains_key("HOMEBOY_RUNNER_EXEC_SCRIPT"));
}

#[test]
fn runner_exec_secret_env_plan_merges_names_and_public_env() {
    let plan = prepare_runner_exec_secret_env_plan(
        vec![" DIRECT_SECRET ".to_string()],
        Some(
            r#"{
                "public_env": {"PUBLIC_FLAG": "1"},
                "secret_env_names": ["PLAN_SECRET", "DIRECT_SECRET"]
            }"#
            .to_string(),
        ),
        None,
    )
    .expect("secret env plan");

    assert_eq!(plan.public_env["PUBLIC_FLAG"], "1");
    assert_eq!(
        plan.secret_env_names(),
        vec!["DIRECT_SECRET".to_string(), "PLAN_SECRET".to_string()]
    );
}

#[test]
fn runner_exec_rejects_declared_secret_like_public_env() {
    let env = prepare_runner_exec_env(vec!["API_TOKEN=public".to_string()], None).expect("env");
    let err = validate_runner_exec_public_env(&env, &["API_TOKEN".to_string()])
        .expect_err("secret-like declared public env should fail");

    assert_eq!(err.code.as_str(), "validation.invalid_argument");
    assert_eq!(err.details["field"], "env");
}

#[test]
fn runner_exec_allows_non_secret_public_env_even_when_declared() {
    let env = prepare_runner_exec_env(vec!["FEATURE_FLAG=1".to_string()], None).expect("env");

    validate_runner_exec_public_env(&env, &["FEATURE_FLAG".to_string()])
        .expect("non-sensitive public env is allowed");
}

#[test]
fn runner_exec_promotes_declared_artifacts_to_run_store() {
    homeboy::test_support::with_isolated_home(|_| {
        let workspace = tempfile::tempdir().expect("workspace");
        runner::create(
            &format!(
                r#"{{"id":"lab-local","kind":"local","workspace_root":"{}"}}"#,
                workspace.path().display()
            ),
            false,
        )
        .expect("create local runner");
        let store = ObservationStore::open_initialized().expect("store");
        let run = store
            .start_run(
                NewRunRecord::builder("runner-exec")
                    .command("homeboy runner exec lab-local".to_string())
                    .cwd_path(workspace.path())
                    .metadata(serde_json::json!({}))
                    .build(),
            )
            .expect("run");

        let (output, exit_code) = exec(
            "lab-local",
            Some(workspace.path().display().to_string()),
            None,
            None,
            false,
            false,
            Vec::new(),
            None,
            Vec::new(),
            Vec::new(),
            None,
            None,
            false,
            Some(run.id.clone()),
            vec!["out.txt".to_string(), "reports".to_string()],
            Vec::new(),
            Vec::new(),
            vec![
                "sh".to_string(),
                "-c".to_string(),
                "printf hello > out.txt && mkdir reports && printf '{}' > reports/result.json"
                    .to_string(),
            ],
        )
        .expect("runner exec");

        assert_eq!(exit_code, 0);
        let binaries = output
            .diagnostics
            .as_ref()
            .and_then(|diagnostics| diagnostics.homeboy_binaries.as_ref())
            .expect("runner exec reports Homeboy binary role diagnostics");
        assert_eq!(binaries.controller_cli.owner, "operator_command");
        assert_eq!(
            binaries.job_command_binary.owner,
            "runner_config.settings.homeboy_path"
        );
        assert!(binaries
            .guidance
            .contains("verify job_command_binary on the runner"));
        let artifacts = store.list_artifacts(&run.id).expect("artifacts");
        assert_eq!(artifacts.len(), 2);
        assert_eq!(artifacts[0].kind, "out_txt");
        assert_eq!(artifacts[0].artifact_type, "file");
        assert!(std::path::Path::new(&artifacts[0].path).is_file());
        assert_eq!(artifacts[1].kind, "reports");
        assert_eq!(artifacts[1].artifact_type, "directory");
        assert!(std::path::Path::new(&artifacts[1].path)
            .join("result.json")
            .is_file());
    });
}

#[test]
fn runner_exec_promotes_declared_summaries_as_typed_evidence() {
    homeboy::test_support::with_isolated_home(|_| {
        let workspace = tempfile::tempdir().expect("workspace");
        runner::create(
            &format!(
                r#"{{"id":"lab-local","kind":"local","workspace_root":"{}"}}"#,
                workspace.path().display()
            ),
            false,
        )
        .expect("create local runner");
        let store = ObservationStore::open_initialized().expect("store");
        let run = store
            .start_run(
                NewRunRecord::builder("runner-exec")
                    .command("homeboy runner exec lab-local".to_string())
                    .cwd_path(workspace.path())
                    .metadata(serde_json::json!({}))
                    .build(),
            )
            .expect("run");

        let (output, exit_code) = exec(
            "lab-local",
            Some(workspace.path().display().to_string()),
            None,
            None,
            false,
            false,
            Vec::new(),
            None,
            Vec::new(),
            Vec::new(),
            None,
            None,
            false,
            Some(run.id.clone()),
            Vec::new(),
            Vec::new(),
            vec!["summary.json".to_string()],
            vec![
                "sh".to_string(),
                "-c".to_string(),
                r#"printf '{"matrix":{"passed":1}}' > summary.json"#.to_string(),
            ],
        )
        .expect("runner exec");

        assert_eq!(exit_code, 0);
        let artifacts = store.list_artifacts(&run.id).expect("artifacts");
        assert_eq!(artifacts.len(), 1);
        assert_eq!(artifacts[0].kind, "summary");
        assert_eq!(artifacts[0].artifact_type, "file");
        assert_eq!(artifacts[0].metadata_json["declared_path"], "summary.json");
        assert_eq!(artifacts[0].metadata_json["evidence_role"], "summary");
        assert_eq!(artifacts[0].metadata_json["promoted_by"], "runner.exec");
        assert!(std::path::Path::new(&artifacts[0].path).is_file());
        assert_eq!(output.promoted_outputs.len(), 1);
        assert_eq!(output.promoted_outputs[0].role, "summary");
        assert_eq!(output.promoted_outputs[0].run_id, run.id);
        assert_eq!(output.promoted_outputs[0].runner_id, "lab-local");
        assert_eq!(output.promoted_outputs[0].declared_path, "summary.json");
        assert_eq!(output.promoted_outputs[0].artifact_kind, "summary");
        assert_eq!(
            output.promoted_outputs[0].runner_path,
            workspace.path().join("summary.json").display().to_string()
        );
        assert_eq!(output.structured_summaries.len(), 1);
        assert_eq!(
            output.structured_summaries[0].summary["matrix"]["passed"],
            1
        );
        assert_eq!(
            output.structured_summaries[0].artifact_id,
            output.promoted_outputs[0].artifact_id
        );
    });
}

#[test]
fn runner_exec_structured_summary_is_independent_of_large_stdout() {
    homeboy::test_support::with_isolated_home(|_| {
        let workspace = tempfile::tempdir().expect("workspace");
        runner::create(
            &format!(
                r#"{{"id":"lab-local","kind":"local","workspace_root":"{}"}}"#,
                workspace.path().display()
            ),
            false,
        )
        .expect("create local runner");
        let store = ObservationStore::open_initialized().expect("store");
        let run = store
            .start_run(
                NewRunRecord::builder("runner-exec")
                    .command("homeboy runner exec lab-local".to_string())
                    .cwd_path(workspace.path())
                    .metadata(serde_json::json!({}))
                    .build(),
            )
            .expect("run");

        let (output, exit_code) = exec(
            "lab-local",
            Some(workspace.path().display().to_string()),
            None,
            None,
            false,
            false,
            Vec::new(),
            None,
            Vec::new(),
            Vec::new(),
            None,
            None,
            false,
            Some(run.id.clone()),
            Vec::new(),
            Vec::new(),
            vec!["summary.json".to_string()],
            vec![
                "sh".to_string(),
                "-c".to_string(),
                r#"yes noisy | head -n 2000; printf '{"status":"pass","count":2000}' > summary.json"#.to_string(),
            ],
        )
        .expect("runner exec");

        assert_eq!(exit_code, 0);
        assert!(output.stdout.len() > 10_000);
        assert_eq!(output.structured_summaries.len(), 1);
        assert_eq!(output.structured_summaries[0].summary["status"], "pass");
        assert_eq!(output.structured_summaries[0].summary["count"], 2000);
    });
}

#[test]
fn runner_exec_promotes_offloaded_artifacts_from_runner_path() {
    homeboy::test_support::with_isolated_home(|home| {
        let artifact_root = home.path().join("artifacts");
        homeboy::core::set_artifact_root_override(Some(artifact_root));
        let workspace = tempfile::tempdir().expect("workspace");
        let report = workspace.path().join("report.txt");
        std::fs::write(&report, "runner output").expect("write runner output");
        server::create(
            r#"{"id":"local-ssh","host":"localhost","user":"user"}"#,
            false,
        )
        .expect("create local ssh server");
        runner::create(
            &format!(
                r#"{{"id":"local-ssh","kind":"ssh","server_id":"local-ssh","workspace_root":"{}"}}"#,
                workspace.path().display()
            ),
            false,
        )
        .expect("create ssh runner");
        let store = ObservationStore::open_initialized().expect("store");
        let run = store
            .start_run(
                NewRunRecord::builder("runner-exec")
                    .command("homeboy runner exec lab-ssh".to_string())
                    .cwd_path(workspace.path())
                    .metadata(serde_json::json!({}))
                    .build(),
            )
            .expect("run");
        let output = runner_exec_output(
            "local-ssh",
            RunnerExecMode::ReverseBroker,
            &workspace.path().display().to_string(),
        );

        promote_runner_exec_artifacts(&run.id, &output, &["report.txt".to_string()])
            .expect("promote offloaded artifact");

        let artifacts = store.list_artifacts(&run.id).expect("artifacts");
        assert_eq!(artifacts.len(), 1);
        assert_eq!(artifacts[0].metadata_json["source"], "runner_path_attach");
        assert_eq!(artifacts[0].metadata_json["runner_id"], "local-ssh");
        assert_eq!(
            artifacts[0].metadata_json["runner_path"],
            report.display().to_string()
        );
        assert_eq!(
            std::fs::read_to_string(&artifacts[0].path).unwrap(),
            "runner output"
        );
    });
}

#[test]
fn runner_exec_promotes_offloaded_directory_artifacts_from_runner_path() {
    homeboy::test_support::with_isolated_home(|home| {
        let artifact_root = home.path().join("artifacts");
        homeboy::core::set_artifact_root_override(Some(artifact_root));
        let workspace = tempfile::tempdir().expect("workspace");
        let site = workspace.path().join("site");
        std::fs::create_dir_all(&site).expect("create site");
        std::fs::write(site.join("index.html"), "<h1>Preview</h1>").expect("write index");
        server::create(
            r#"{"id":"local-ssh","host":"localhost","user":"user"}"#,
            false,
        )
        .expect("create local ssh server");
        runner::create(
            &format!(
                r#"{{"id":"local-ssh","kind":"ssh","server_id":"local-ssh","workspace_root":"{}"}}"#,
                workspace.path().display()
            ),
            false,
        )
        .expect("create ssh runner");
        let store = ObservationStore::open_initialized().expect("store");
        let run = store
            .start_run(
                NewRunRecord::builder("runner-exec")
                    .command("homeboy runner exec lab-ssh".to_string())
                    .cwd_path(workspace.path())
                    .metadata(serde_json::json!({}))
                    .build(),
            )
            .expect("run");
        let output = runner_exec_output(
            "local-ssh",
            RunnerExecMode::ReverseBroker,
            &workspace.path().display().to_string(),
        );

        promote_runner_exec_artifacts(&run.id, &output, &["site".to_string()])
            .expect("promote offloaded directory artifact");

        let artifacts = store.list_artifacts(&run.id).expect("artifacts");
        assert_eq!(artifacts.len(), 1);
        assert_eq!(artifacts[0].kind, "site");
        assert_eq!(artifacts[0].artifact_type, "directory");
        assert_eq!(artifacts[0].metadata_json["source"], "runner_path_attach");
        assert_eq!(artifacts[0].metadata_json["runner_id"], "local-ssh");
        assert_eq!(
            artifacts[0].metadata_json["runner_path"],
            site.display().to_string()
        );
        assert!(std::path::Path::new(&artifacts[0].path)
            .join("index.html")
            .is_file());
        let entrypoints = homeboy::core::artifacts::html_preview_entrypoints(&artifacts[0]);
        assert_eq!(entrypoints.len(), 1);
        assert_eq!(entrypoints[0].path, "index.html");
    });
}

#[test]
fn runner_exec_promotes_artifact_dir_children_to_run_store() {
    homeboy::test_support::with_isolated_home(|home| {
        let artifact_root = home.path().join("artifacts");
        homeboy::core::set_artifact_root_override(Some(artifact_root));
        let workspace = tempfile::tempdir().expect("workspace");
        let outputs = workspace.path().join("outputs");
        let report_dir = outputs.join("report");
        std::fs::create_dir_all(&report_dir).expect("create report dir");
        std::fs::write(outputs.join("summary.json"), r#"{"passed":true}"#).expect("write summary");
        std::fs::write(report_dir.join("index.html"), "<h1>Report</h1>").expect("write report");
        let store = ObservationStore::open_initialized().expect("store");
        let run = store
            .start_run(
                NewRunRecord::builder("runner-exec")
                    .command("homeboy runner exec lab-local".to_string())
                    .cwd_path(workspace.path())
                    .metadata(serde_json::json!({}))
                    .build(),
            )
            .expect("run");
        let output = runner_exec_output(
            "lab-local",
            RunnerExecMode::Local,
            &workspace.path().display().to_string(),
        );

        let promoted =
            promote_runner_exec_artifact_dirs(&run.id, &output, &["outputs".to_string()])
                .expect("promote artifact dir children");

        assert_eq!(promoted.len(), 2);
        let artifacts = store.list_artifacts(&run.id).expect("artifacts");
        assert_eq!(artifacts.len(), 2);
        assert_eq!(artifacts[0].kind, "report");
        assert_eq!(artifacts[0].artifact_type, "directory");
        assert_eq!(artifacts[0].metadata_json["artifact_dir"], "outputs");
        assert_eq!(
            artifacts[0].metadata_json["declared_path"],
            "outputs/report"
        );
        assert!(std::path::Path::new(&artifacts[0].path)
            .join("index.html")
            .is_file());
        assert_eq!(artifacts[1].kind, "summary_json");
        assert_eq!(artifacts[1].artifact_type, "file");
        assert_eq!(artifacts[1].metadata_json["artifact_dir"], "outputs");
        assert_eq!(
            artifacts[1].metadata_json["declared_path"],
            "outputs/summary.json"
        );
        assert!(std::path::Path::new(&artifacts[1].path).is_file());
    });
}

#[test]
fn runner_exec_promotes_fuzz_envelope_observations_and_hotspots_as_typed_artifacts() {
    homeboy::test_support::with_isolated_home(|home| {
        let artifact_root = home.path().join("artifacts");
        homeboy::core::set_artifact_root_override(Some(artifact_root));
        let workspace = tempfile::tempdir().expect("workspace");
        let envelope_path = workspace.path().join("fuzz-result-envelope.json");
        std::fs::write(
            &envelope_path,
            r#"{
                "schema": "homeboy/fuzz-result-envelope/v1",
                "observation_set": {
                    "schema": "homeboy/fuzz-observation-set/v1",
                    "version": 1,
                    "id": "observations-1",
                    "observations": [
                        {
                            "id": "obs-1",
                            "family": "timing",
                            "subject": "search route",
                            "metric": "duration",
                            "value": 120.0,
                            "unit": "ms",
                            "fingerprint": "route:search",
                            "sample_count": 3
                        }
                    ]
                }
            }"#,
        )
        .expect("write fuzz envelope");
        let store = ObservationStore::open_initialized().expect("store");
        let run = store
            .start_run(
                NewRunRecord::builder("runner-exec")
                    .command("homeboy runner exec lab-local".to_string())
                    .cwd_path(workspace.path())
                    .metadata(serde_json::json!({}))
                    .build(),
            )
            .expect("run");
        let output = runner_exec_output(
            "lab-local",
            RunnerExecMode::Local,
            &workspace.path().display().to_string(),
        );

        let promoted = promote_runner_exec_artifacts(
            &run.id,
            &output,
            &["fuzz-result-envelope.json".to_string()],
        )
        .expect("promote fuzz envelope");

        assert_eq!(promoted.len(), 3);
        let artifacts = store.list_artifacts(&run.id).expect("artifacts");
        let kinds = artifacts
            .iter()
            .map(|artifact| artifact.kind.as_str())
            .collect::<Vec<_>>();
        assert_eq!(
            kinds,
            vec![
                "fuzz_result_envelope",
                "fuzz_observation_set",
                "fuzz_hotspot_set"
            ]
        );
        assert_eq!(
            artifacts[0].metadata_json["schema"],
            "homeboy/fuzz-result-envelope/v1"
        );
        assert_eq!(
            artifacts[1].metadata_json["derived_from_artifact_id"],
            artifacts[0].id
        );
        assert_eq!(
            artifacts[2].metadata_json["derived_from_observation_set_id"],
            "observations-1"
        );
        let hotspot_json: serde_json::Value = serde_json::from_str(
            &std::fs::read_to_string(&artifacts[2].path).expect("read hotspot artifact"),
        )
        .expect("parse hotspot artifact");
        assert_eq!(hotspot_json["schema"], "homeboy/fuzz-hotspot-set/v1");
        assert_eq!(hotspot_json["items"][0]["id"], "route:search");
    });
}

#[test]
fn runner_exec_promotes_artifact_dir_typed_schema_children() {
    homeboy::test_support::with_isolated_home(|home| {
        let artifact_root = home.path().join("artifacts");
        homeboy::core::set_artifact_root_override(Some(artifact_root));
        let workspace = tempfile::tempdir().expect("workspace");
        let outputs = workspace.path().join("outputs");
        std::fs::create_dir_all(&outputs).expect("create outputs");
        std::fs::write(
            outputs.join("fuzz-observations.json"),
            r#"{
                "schema": "homeboy/fuzz-observation-set/v1",
                "version": 1,
                "id": "observations-1",
                "observations": [
                    {
                        "id": "obs-1",
                        "family": "timing",
                        "subject": "route-a",
                        "metric": "duration",
                        "value": 40.0,
                        "unit": "ms"
                    }
                ]
            }"#,
        )
        .expect("write observations");
        std::fs::write(
            outputs.join("performance-hotspots.json"),
            r#"{
                "schema": "homeboy/performance-hotspots-summary/v1",
                "version": 1,
                "id": "bench-hotspots",
                "aggregation": {
                    "method": "sum_by_dimension",
                    "score_metric": "duration_ms"
                },
                "rankings": [
                    {
                        "id": "scenario:route-a",
                        "rank": 1,
                        "score": 40.0,
                        "primary_metric": { "name": "duration_ms", "value": 40.0, "unit": "ms" }
                    }
                ]
            }"#,
        )
        .expect("write performance hotspots");
        std::fs::write(
            outputs.join("runner-specific-report.json"),
            r#"{"schema":"runner/mutation-isolation/v1","passed":true}"#,
        )
        .expect("write runner report");
        let store = ObservationStore::open_initialized().expect("store");
        let run = store
            .start_run(
                NewRunRecord::builder("runner-exec")
                    .command("homeboy runner exec lab-local".to_string())
                    .cwd_path(workspace.path())
                    .metadata(serde_json::json!({}))
                    .build(),
            )
            .expect("run");
        let output = runner_exec_output(
            "lab-local",
            RunnerExecMode::Local,
            &workspace.path().display().to_string(),
        );

        let promoted =
            promote_runner_exec_artifact_dirs(&run.id, &output, &["outputs".to_string()])
                .expect("promote artifact dir children");

        assert_eq!(promoted.len(), 3);
        let artifacts = store.list_artifacts(&run.id).expect("artifacts");
        assert_eq!(artifacts.len(), 3);
        assert_eq!(artifacts[0].kind, "fuzz_observation_set");
        assert_eq!(
            artifacts[0].metadata_json["schema"],
            "homeboy/fuzz-observation-set/v1"
        );
        assert_eq!(artifacts[0].metadata_json["artifact_dir"], "outputs");
        assert_eq!(
            artifacts[0].metadata_json["promoted_kind"],
            "fuzz-observations_json"
        );
        assert_eq!(artifacts[1].kind, "performance_hotspots_summary");
        assert_eq!(
            artifacts[1].metadata_json["schema"],
            "homeboy/performance-hotspots-summary/v1"
        );
        assert_eq!(
            artifacts[1].metadata_json["typed_artifact_kind"],
            "performance_hotspots_summary"
        );
        assert_eq!(
            artifacts[1].metadata_json["promoted_kind"],
            "performance-hotspots_json"
        );
        assert_eq!(artifacts[2].kind, "runner-specific-report_json");
        assert_eq!(artifacts[2].metadata_json["artifact_dir"], "outputs");
        assert!(artifacts[2]
            .metadata_json
            .get("typed_artifact_kind")
            .is_none());
    });
}

#[test]
fn runner_exec_rejects_artifacts_without_run_id() {
    let err = exec(
        "lab-local",
        None,
        None,
        None,
        false,
        false,
        Vec::new(),
        None,
        Vec::new(),
        Vec::new(),
        None,
        None,
        false,
        None,
        vec!["out.txt".to_string()],
        Vec::new(),
        Vec::new(),
        vec!["sh".to_string(), "-c".to_string(), "printf ok".to_string()],
    )
    .expect_err("artifact requires run id");

    assert_eq!(err.code.as_str(), "validation.invalid_argument");
    assert_eq!(err.details["field"], "run_id");
}

fn runner_exec_output(runner_id: &str, mode: RunnerExecMode, remote_cwd: &str) -> RunnerExecOutput {
    RunnerExecOutput {
        variant: "exec",
        command: "runner.exec",
        runner_id: runner_id.to_string(),
        dry_run: false,
        mode,
        argv: vec!["sh".to_string(), "-c".to_string(), "true".to_string()],
        remote_cwd: remote_cwd.to_string(),
        exit_code: 0,
        stdout: String::new(),
        stderr: String::new(),
        source_snapshot: None,
        job: None,
        runner_job: None,
        job_id: None,
        job_events: None,
        mirror_run_id: None,
        patch: None,
        mutation_artifacts: None,
        artifacts: Vec::new(),
        promoted_outputs: Vec::new(),
        structured_summaries: Vec::new(),
        metrics: None,
        capture: None,
        execution_record: None,
        runner_result: None,
        handoff: None,
        diagnostics: None,
    }
}

#[test]
fn runner_exec_rejects_summaries_without_run_id() {
    let err = exec(
        "lab-local",
        None,
        None,
        None,
        false,
        false,
        Vec::new(),
        None,
        Vec::new(),
        Vec::new(),
        None,
        None,
        false,
        None,
        Vec::new(),
        Vec::new(),
        vec!["summary.json".to_string()],
        vec!["sh".to_string(), "-c".to_string(), "printf ok".to_string()],
    )
    .expect_err("summary requires run id");

    assert_eq!(err.code.as_str(), "validation.invalid_argument");
    assert_eq!(err.details["field"], "run_id");
}
