use crate::ai_agents::AiAgentPermissionMode;
use crate::gemini_cli::AgentStreamRequest;
use std::path::{Path, PathBuf};
use std::process::Stdio;

pub(crate) fn build_command(
    binary: &Path,
    request: &AgentStreamRequest,
    settings_dir: &Path,
) -> Result<std::process::Command, String> {
    let settings_path = write_settings(
        settings_dir,
        &request.vault_path,
        &request.vault_paths,
        request.permission_mode,
    )?;
    let target = crate::cli_agent_runtime::command_target_avoiding_windows_cmd_shim(binary)?;
    let mut command = crate::hidden_command(&target.program);
    crate::cli_agent_runtime::configure_agent_command_environment(&mut command, binary);
    if let Some(first_arg) = target.first_arg {
        command.arg(first_arg);
    }
    command
        .args(build_args(request.permission_mode))
        .arg("--prompt")
        .arg(build_prompt(request))
        .env("GEMINI_CLI_SYSTEM_SETTINGS_PATH", settings_path)
        .env("GEMINI_CLI_TRUST_WORKSPACE", "true")
        .env("NO_COLOR", "1")
        .current_dir(&request.vault_path)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());
    Ok(command)
}

fn build_args(permission_mode: AiAgentPermissionMode) -> Vec<String> {
    vec![
        "--output-format".into(),
        "stream-json".into(),
        "--approval-mode".into(),
        approval_mode(permission_mode).into(),
    ]
}

fn approval_mode(permission_mode: AiAgentPermissionMode) -> &'static str {
    match permission_mode {
        AiAgentPermissionMode::Safe => "auto_edit",
        AiAgentPermissionMode::PowerUser => "yolo",
    }
}

fn build_prompt(request: &AgentStreamRequest) -> String {
    crate::cli_agent_runtime::build_prompt(&request.message, request.system_prompt.as_deref())
}

fn write_settings(
    settings_dir: &Path,
    vault_path: &str,
    vault_paths: &[String],
    permission_mode: AiAgentPermissionMode,
) -> Result<PathBuf, String> {
    std::fs::create_dir_all(settings_dir)
        .map_err(|error| format!("Failed to create Gemini settings directory: {error}"))?;
    let settings_path = settings_dir.join("settings.json");
    let settings = build_settings(vault_path, vault_paths, permission_mode)?;
    std::fs::write(&settings_path, settings)
        .map_err(|error| format!("Failed to write Gemini settings: {error}"))?;
    Ok(settings_path)
}

fn build_settings(
    vault_path: &str,
    vault_paths: &[String],
    permission_mode: AiAgentPermissionMode,
) -> Result<String, String> {
    let mcp_server_path = crate::cli_agent_runtime::mcp_server_path_string()?;
    let vault_paths = crate::cli_agent_runtime::active_vault_paths_json(vault_path, vault_paths);
    let mut settings = serde_json::json!({
        "mcpServers": {
            "tolaria": {
                "command": "node",
                "args": [mcp_server_path],
                "env": {
                    "VAULT_PATH": vault_path,
                    "VAULT_PATHS": vault_paths,
                    "WS_UI_PORT": "9711"
                },
                "description": "Tolaria active vault MCP server",
                "trust": permission_mode == AiAgentPermissionMode::PowerUser
            }
        }
    });

    if permission_mode == AiAgentPermissionMode::Safe {
        settings["tools"] = serde_json::json!({
            "exclude": ["run_shell_command"]
        });
    }

    serde_json::to_string(&settings)
        .map_err(|error| format!("Failed to serialize Gemini settings: {error}"))
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::ffi::OsStr;

    fn request() -> AgentStreamRequest {
        AgentStreamRequest {
            message: "Rename the note".into(),
            system_prompt: None,
            vault_path: "/tmp/vault".into(),
            vault_paths: Vec::new(),
            permission_mode: AiAgentPermissionMode::Safe,
        }
    }

    #[test]
    fn command_uses_headless_stream_json_mode_and_temp_settings() {
        let settings_dir = tempfile::tempdir().unwrap();
        let command =
            build_command(&PathBuf::from("gemini"), &request(), settings_dir.path()).unwrap();
        let actual_args: Vec<&OsStr> = command.get_args().collect();
        let settings_path = command
            .get_envs()
            .find(|(key, _)| *key == OsStr::new("GEMINI_CLI_SYSTEM_SETTINGS_PATH"))
            .and_then(|(_, value)| value);

        assert_eq!(command.get_program(), OsStr::new("gemini"));
        assert_eq!(actual_args[0], OsStr::new("--output-format"));
        assert_eq!(actual_args[1], OsStr::new("stream-json"));
        assert!(actual_args.contains(&OsStr::new("--prompt")));
        assert_eq!(actual_args.last(), Some(&OsStr::new("Rename the note")));
        assert_eq!(command.get_current_dir(), Some(Path::new("/tmp/vault")));
        assert!(settings_path.is_some());
        assert!(settings_dir.path().join("settings.json").exists());
        let trust_workspace = command
            .get_envs()
            .find(|(key, _)| *key == OsStr::new("GEMINI_CLI_TRUST_WORKSPACE"))
            .and_then(|(_, value)| value);
        assert_eq!(trust_workspace, Some(OsStr::new("true")));
    }

    #[test]
    fn command_extends_path_with_resolved_homebrew_bin() {
        let settings_dir = tempfile::tempdir().unwrap();
        let command = build_command(
            &PathBuf::from("/opt/homebrew/bin/gemini"),
            &request(),
            settings_dir.path(),
        )
        .unwrap();
        let path_value = command
            .get_envs()
            .find(|(key, _)| *key == OsStr::new("PATH"))
            .and_then(|(_, value)| value)
            .expect("PATH should be set");
        let paths = std::env::split_paths(path_value).collect::<Vec<_>>();

        assert!(
            paths.contains(&PathBuf::from("/opt/homebrew/bin")),
            "PATH should include the resolved Gemini binary directory, got {paths:?}"
        );
    }

    #[test]
    fn command_avoids_windows_cmd_shim_for_prompt_args() {
        let dir = tempfile::tempdir().unwrap();
        let shim = dir.path().join("gemini.cmd");
        let script = dir
            .path()
            .join("node_modules")
            .join("@google")
            .join("gemini-cli")
            .join("dist")
            .join("index.js");
        std::fs::create_dir_all(script.parent().unwrap()).unwrap();
        std::fs::write(&script, "console.log('gemini')\n").unwrap();
        std::fs::write(
            &shim,
            r#"@ECHO off
"%_prog%" "%~dp0\node_modules\@google\gemini-cli\dist\index.js" %*
"#,
        )
        .unwrap();

        let settings_dir = tempfile::tempdir().unwrap();
        let command = build_command(&shim, &request(), settings_dir.path()).unwrap();
        let actual_args = command.get_args().collect::<Vec<_>>();

        assert_ne!(
            command.get_program(),
            shim.as_os_str(),
            "Gemini npm .cmd shims cannot safely receive prompt args directly"
        );
        assert_eq!(actual_args.first().copied(), Some(script.as_os_str()));
        assert!(actual_args.iter().any(|arg| *arg == OsStr::new("--prompt")));
        assert!(actual_args
            .iter()
            .any(|arg| *arg == OsStr::new("Rename the note")));
    }

    #[test]
    fn safe_settings_include_tolaria_mcp_and_exclude_shell() {
        let settings = build_settings("/tmp/vault", &[], AiAgentPermissionMode::Safe).unwrap();
        let json: serde_json::Value = serde_json::from_str(&settings).unwrap();

        assert_eq!(json["mcpServers"]["tolaria"]["command"], "node");
        assert_eq!(
            json["mcpServers"]["tolaria"]["env"]["VAULT_PATH"],
            "/tmp/vault"
        );
        assert_eq!(json["mcpServers"]["tolaria"]["env"]["WS_UI_PORT"], "9711");
        assert_eq!(json["mcpServers"]["tolaria"]["trust"], false);
        assert_eq!(json["tools"]["exclude"][0], "run_shell_command");
        assert!(json["mcpServers"]["tolaria"]["args"][0]
            .as_str()
            .unwrap()
            .ends_with("index.js"));
    }

    #[test]
    fn power_user_settings_trust_tolaria_and_allow_shell_discovery() {
        let settings = build_settings("/tmp/vault", &[], AiAgentPermissionMode::PowerUser).unwrap();
        let json: serde_json::Value = serde_json::from_str(&settings).unwrap();

        assert_eq!(json["mcpServers"]["tolaria"]["trust"], true);
        assert!(json.get("tools").is_none());
        assert_eq!(approval_mode(AiAgentPermissionMode::PowerUser), "yolo");
    }

    #[test]
    fn prompt_keeps_system_prompt_first() {
        let prompt = build_prompt(&AgentStreamRequest {
            system_prompt: Some("Be concise".into()),
            ..request()
        });

        assert!(prompt.starts_with("System instructions:\nBe concise"));
        assert!(prompt.contains("User request:\nRename the note"));
    }
}
