//! Regression tests: per-CLI `build_command` produces the expected argv.
//!
//! These tests assert that the argv produced by each `CliCommandBuilder`
//! implementation matches exactly what the old `build_command_with_options`
//! match block in `pipe/process.rs` (git 8c0e428) would have produced.
//!
//! We test the bare `Command` returned by `build_command` — the Windows
//! `cmd /C` wrapping is applied by `pipe/process.rs` and is tested separately
//! via the shell-quoting helpers in that module.

use gate4agent::pipe::cli_builder;
use gate4agent::{CliTool, SpawnOptions};

fn get_program(cmd: &std::process::Command) -> &str {
    cmd.get_program().to_str().unwrap()
}

fn get_args(cmd: &std::process::Command) -> Vec<&str> {
    cmd.get_args().map(|a| a.to_str().unwrap()).collect()
}

fn make_opts(prompt: &str) -> SpawnOptions {
    SpawnOptions {
        prompt: prompt.to_string(),
        ..Default::default()
    }
}

// ─────────────────────────────────────────────
// Claude Code
// ─────────────────────────────────────────────

#[test]
fn claude_fresh_session_argv() {
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = make_opts("hello world");
    let cmd = builder.build_command(&opts);

    assert_eq!(get_program(&cmd), "claude");
    let got = get_args(&cmd);
    assert_eq!(
        got,
        &[
            "-p",
            "--output-format",
            "stream-json",
            "--verbose",
            "--dangerously-skip-permissions",
        ],
        "Claude fresh session: prompt must NOT appear in argv (delivered via stdin)"
    );
}

#[test]
fn claude_with_resume_argv() {
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = SpawnOptions {
        prompt: "hello".to_string(),
        resume_session_id: Some("ses_abc123".to_string()),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    assert_eq!(get_program(&cmd), "claude");
    let got = get_args(&cmd);
    assert_eq!(
        got,
        &[
            "-p",
            "--output-format",
            "stream-json",
            "--verbose",
            "--dangerously-skip-permissions",
            "--resume",
            "ses_abc123",
        ]
    );
}

#[test]
fn claude_with_model_and_append_system_prompt_argv() {
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = SpawnOptions {
        prompt: "hello".to_string(),
        model: Some("claude-opus-4".to_string()),
        append_system_prompt: Some("Be concise.".to_string()),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    assert_eq!(get_program(&cmd), "claude");
    let got = get_args(&cmd);
    // append_system_prompt comes before model in the builder
    assert_eq!(
        got,
        &[
            "-p",
            "--output-format",
            "stream-json",
            "--verbose",
            "--dangerously-skip-permissions",
            "--append-system-prompt",
            "Be concise.",
            "--model",
            "claude-opus-4",
        ]
    );
}

#[test]
fn claude_extra_args_appear_in_argv() {
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = SpawnOptions {
        prompt: "hello".to_string(),
        extra_args: vec!["--foo".to_string(), "bar".to_string()],
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(got.contains(&"--foo"), "extra_args must appear in argv");
    assert!(got.contains(&"bar"), "extra_args values must appear in argv");
    // Prompt still must NOT appear
    assert!(
        !got.contains(&"hello"),
        "Claude prompt must not appear in argv"
    );
}

// ─────────────────────────────────────────────
// Claude — new SpawnOptions fields
// ─────────────────────────────────────────────

#[test]
fn claude_continue_last_argv() {
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = SpawnOptions {
        prompt: "hello".to_string(),
        continue_last: true,
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        got.contains(&"--continue"),
        "continue_last=true must add --continue to Claude argv"
    );
    assert!(
        !got.contains(&"--resume"),
        "--resume must NOT appear when continue_last is used without resume_session_id"
    );
}

#[test]
fn claude_continue_last_ignored_when_resume_session_id_set() {
    // resume_session_id takes priority over continue_last.
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = SpawnOptions {
        prompt: "hello".to_string(),
        continue_last: true,
        resume_session_id: Some("ses_explicit".to_string()),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        got.contains(&"--resume"),
        "--resume must appear when resume_session_id is set"
    );
    assert!(
        !got.contains(&"--continue"),
        "--continue must NOT appear when resume_session_id is also set"
    );
}

#[test]
fn claude_allowed_tools_argv() {
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = SpawnOptions {
        prompt: "hello".to_string(),
        allowed_tools: vec!["Edit".to_string(), "Read".to_string(), "Bash".to_string()],
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    let tools_pos = got.iter().position(|a| *a == "--allowedTools");
    assert!(tools_pos.is_some(), "--allowedTools flag must appear in argv");
    assert_eq!(
        got.get(tools_pos.unwrap() + 1).copied(),
        Some("Edit,Read,Bash"),
        "--allowedTools value must be comma-joined tool names"
    );
}

#[test]
fn claude_permission_mode_replaces_dangerously_skip() {
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = SpawnOptions {
        prompt: "hello".to_string(),
        permission_mode: Some("default".to_string()),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        got.contains(&"--permission-mode"),
        "--permission-mode flag must appear when permission_mode is set"
    );
    assert_eq!(
        got.iter().position(|a| *a == "--permission-mode")
            .and_then(|i| got.get(i + 1).copied()),
        Some("default"),
        "--permission-mode value must be 'default'"
    );
    assert!(
        !got.contains(&"--dangerously-skip-permissions"),
        "--dangerously-skip-permissions must NOT appear when permission_mode is set"
    );
}

#[test]
fn claude_permission_mode_accept_all() {
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = SpawnOptions {
        prompt: "hello".to_string(),
        permission_mode: Some("accept-all".to_string()),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        got.windows(2).any(|w| w == ["--permission-mode", "accept-all"]),
        "--permission-mode accept-all must appear in argv"
    );
    assert!(
        !got.contains(&"--dangerously-skip-permissions"),
        "--dangerously-skip-permissions must NOT appear when permission_mode is explicitly set"
    );
}

#[test]
fn claude_mcp_config_argv() {
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = SpawnOptions {
        prompt: "hello".to_string(),
        mcp_config: Some(std::path::PathBuf::from("/tmp/mcp.json")),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    let mcp_pos = got.iter().position(|a| *a == "--mcp-config");
    assert!(mcp_pos.is_some(), "--mcp-config flag must appear in argv");
    assert!(
        got.get(mcp_pos.unwrap() + 1)
            .map(|v| v.contains("mcp.json"))
            .unwrap_or(false),
        "--mcp-config value must contain the path"
    );
}

#[test]
fn claude_max_turns_argv() {
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = SpawnOptions {
        prompt: "hello".to_string(),
        max_turns: Some(10),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        got.windows(2).any(|w| w == ["--max-turns", "10"]),
        "--max-turns 10 must appear in Claude argv"
    );
}

#[test]
fn claude_no_permission_mode_keeps_dangerously_skip() {
    // Default behavior: no permission_mode set → --dangerously-skip-permissions stays.
    let builder = cli_builder(CliTool::ClaudeCode);
    let opts = make_opts("hello");
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        got.contains(&"--dangerously-skip-permissions"),
        "--dangerously-skip-permissions must appear when permission_mode is None"
    );
}

// ─────────────────────────────────────────────
// Codex
// ─────────────────────────────────────────────

#[test]
fn codex_fresh_session_argv() {
    let builder = cli_builder(CliTool::Codex);
    let opts = make_opts("write a hello world in rust");
    let cmd = builder.build_command(&opts);

    assert_eq!(get_program(&cmd), "codex");
    let got = get_args(&cmd);
    assert_eq!(
        got,
        &[
            "exec",
            "--json",
            "--full-auto",
            "--skip-git-repo-check",
            "write a hello world in rust",
        ],
        "Codex fresh: exec subcommand, --json, --full-auto, then prompt as final arg"
    );
}

#[test]
fn codex_with_resume_argv() {
    let builder = cli_builder(CliTool::Codex);
    let opts = SpawnOptions {
        prompt: "continue".to_string(),
        resume_session_id: Some("rollout-20260409-abc".to_string()),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    assert_eq!(get_program(&cmd), "codex");
    let got = get_args(&cmd);
    // Resumed shape: codex exec resume <id> --json --full-auto <prompt>
    assert_eq!(
        got,
        &[
            "exec",
            "resume",
            "rollout-20260409-abc",
            "--json",
            "--full-auto",
            "--skip-git-repo-check",
            "continue",
        ],
        "Codex resume: exec resume <id> sub-sub-command, then flags, then prompt"
    );
}

#[test]
fn codex_prompt_is_last_arg() {
    let builder = cli_builder(CliTool::Codex);
    let opts = make_opts("my prompt");
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert_eq!(
        got.last().copied(),
        Some("my prompt"),
        "Codex: prompt must be the last argv token"
    );
}

#[test]
fn codex_continue_last_argv() {
    let builder = cli_builder(CliTool::Codex);
    let opts = SpawnOptions {
        prompt: "continue".to_string(),
        continue_last: true,
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    assert_eq!(get_program(&cmd), "codex");
    let got = get_args(&cmd);
    // Shape: codex exec resume --last --json --full-auto <prompt>
    assert_eq!(
        &got[..3],
        &["exec", "resume", "--last"],
        "Codex continue_last must produce: exec resume --last ..."
    );
    assert!(got.contains(&"--json"), "--json must appear in Codex continue_last argv");
    assert!(got.contains(&"--full-auto"), "--full-auto must appear in Codex continue_last argv");
    assert_eq!(
        got.last().copied(),
        Some("continue"),
        "Codex: prompt must still be the last argv token"
    );
}

#[test]
fn codex_continue_last_ignored_when_resume_session_id_set() {
    let builder = cli_builder(CliTool::Codex);
    let opts = SpawnOptions {
        prompt: "go".to_string(),
        continue_last: true,
        resume_session_id: Some("rollout-explicit".to_string()),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        got.contains(&"rollout-explicit"),
        "explicit session ID must appear in argv"
    );
    assert!(
        !got.contains(&"--last"),
        "--last must NOT appear when resume_session_id is also set"
    );
}

// ─────────────────────────────────────────────
// Gemini
// ─────────────────────────────────────────────

#[test]
fn gemini_fresh_session_argv() {
    let builder = cli_builder(CliTool::Gemini);
    let opts = make_opts("explain rust lifetimes");
    let cmd = builder.build_command(&opts);

    assert_eq!(get_program(&cmd), "gemini");
    let got = get_args(&cmd);
    assert_eq!(
        got,
        &["--output-format", "stream-json", "-p", "explain rust lifetimes"],
        "Gemini: --output-format stream-json -p <prompt>, NO --verbose"
    );
}

#[test]
fn gemini_no_verbose_flag() {
    let builder = cli_builder(CliTool::Gemini);
    let opts = make_opts("test");
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        !got.contains(&"--verbose"),
        "--verbose must NOT appear in Gemini argv (removed in Phase 1)"
    );
}

#[test]
fn gemini_resume_passes_flag() {
    // Gemini supports --resume <id> in -p mode.
    // Source: packages/cli/src/config/config.ts — --resume / -r flag.
    let builder = cli_builder(CliTool::Gemini);
    let opts = SpawnOptions {
        prompt: "test".to_string(),
        resume_session_id: Some("some-id".to_string()),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        got.contains(&"--resume"),
        "Gemini must pass --resume when resume_session_id is set"
    );
    assert!(
        got.contains(&"some-id"),
        "Gemini resume ID must appear in argv after --resume"
    );
}

#[test]
fn gemini_prompt_is_last_arg() {
    let builder = cli_builder(CliTool::Gemini);
    let opts = make_opts("my gemini prompt");
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert_eq!(
        got.last().copied(),
        Some("my gemini prompt"),
        "Gemini: prompt must be the last argv token (follows -p)"
    );
}

#[test]
fn gemini_sandbox_argv() {
    let builder = cli_builder(CliTool::Gemini);
    let opts = SpawnOptions {
        prompt: "test".to_string(),
        sandbox: true,
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        got.contains(&"--sandbox"),
        "--sandbox must appear in Gemini argv when sandbox=true"
    );
}

#[test]
fn gemini_no_sandbox_by_default() {
    let builder = cli_builder(CliTool::Gemini);
    let opts = make_opts("test");
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        !got.contains(&"--sandbox"),
        "--sandbox must NOT appear in Gemini argv when sandbox=false"
    );
}

// ─────────────────────────────────────────────
// OpenCode
// ─────────────────────────────────────────────
//
// Source: https://opencode.ai/docs/cli/ — packages/opencode/src/cli/cmd/run.ts
// Argv shape: opencode --format json [--session <ses_XXXX>] [<extra>...] "<prompt>"
//
// There is NO "run" subcommand — the default command takes the message directly.

#[test]
fn opencode_fresh_session_argv() {
    let builder = cli_builder(CliTool::OpenCode);
    let opts = make_opts("write a function");
    let cmd = builder.build_command(&opts);

    assert_eq!(get_program(&cmd), "opencode");
    let got = get_args(&cmd);
    // Must start with "run" — the `run` subcommand is required for headless pipe mode.
    assert_eq!(got.first().copied(), Some("run"), "OpenCode argv must start with 'run' subcommand");
    // Must contain --format json
    assert!(
        got.windows(2).any(|w| w == ["--format", "json"]),
        "OpenCode argv must include --format json"
    );
    // Prompt must be last
    assert_eq!(
        got.last().copied(),
        Some("write a function"),
        "OpenCode: prompt must be the last positional argv token"
    );
    // Must NOT contain --session when no resume
    assert!(
        !got.contains(&"--session"),
        "OpenCode fresh session must not contain --session"
    );
}

#[test]
fn opencode_with_session_argv() {
    let builder = cli_builder(CliTool::OpenCode);
    let opts = SpawnOptions {
        prompt: "continue".to_string(),
        resume_session_id: Some("ses_abc".to_string()),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    assert_eq!(get_program(&cmd), "opencode");
    let got = get_args(&cmd);
    // --session ses_abc must appear before the prompt
    let session_pos = got.iter().position(|a| *a == "--session");
    let prompt_pos = got.iter().position(|a| *a == "continue");
    assert!(session_pos.is_some(), "--session flag must appear in argv");
    // The value after --session must be the session ID
    assert_eq!(
        got.get(session_pos.unwrap() + 1).copied(),
        Some("ses_abc"),
        "--session value must be the session ID"
    );
    // --session must appear before the prompt
    assert!(
        session_pos.unwrap() < prompt_pos.unwrap(),
        "--session must appear before the prompt in argv"
    );
}

#[test]
fn opencode_prompt_is_last_arg() {
    let builder = cli_builder(CliTool::OpenCode);
    let opts = make_opts("my opencode prompt");
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert_eq!(
        got.last().copied(),
        Some("my opencode prompt"),
        "OpenCode: prompt must be the last argv token"
    );
}

#[test]
fn opencode_continue_last_argv() {
    let builder = cli_builder(CliTool::OpenCode);
    let opts = SpawnOptions {
        prompt: "continue".to_string(),
        continue_last: true,
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    assert_eq!(get_program(&cmd), "opencode");
    let got = get_args(&cmd);
    assert!(
        got.contains(&"--continue"),
        "--continue must appear in OpenCode argv when continue_last=true"
    );
    assert!(
        !got.contains(&"--session"),
        "--session must NOT appear when continue_last is used without resume_session_id"
    );
    assert_eq!(
        got.last().copied(),
        Some("continue"),
        "OpenCode: prompt must still be the last argv token"
    );
}

#[test]
fn opencode_continue_last_ignored_when_resume_session_id_set() {
    let builder = cli_builder(CliTool::OpenCode);
    let opts = SpawnOptions {
        prompt: "go".to_string(),
        continue_last: true,
        resume_session_id: Some("ses_explicit".to_string()),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        got.contains(&"--session"),
        "--session must appear when resume_session_id is set"
    );
    assert!(
        !got.contains(&"--continue"),
        "--continue must NOT appear when resume_session_id is also set"
    );
}

#[test]
fn opencode_model_argv() {
    let builder = cli_builder(CliTool::OpenCode);
    let opts = SpawnOptions {
        prompt: "hello".to_string(),
        model: Some("anthropic/claude-opus-4".to_string()),
        ..Default::default()
    };
    let cmd = builder.build_command(&opts);

    let got = get_args(&cmd);
    assert!(
        got.windows(2).any(|w| w == ["-m", "anthropic/claude-opus-4"]),
        "-m <model> must appear in OpenCode argv when model is set"
    );
}

