use crate::ai_agents::AiAgentPermissionMode;
use crate::cli_agent_runtime::AgentStreamRequest;
use std::path::Path;
use std::process::Stdio;

pub(crate) fn build_command(
    binary: &Path,
    request: &AgentStreamRequest,
) -> Result<std::process::Command, String> {
    ensure_workspace_mcp_config(request)?;
    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
        .arg("-p")
        .arg(build_prompt(request))
        .arg("--add-dir")
        .arg(&request.vault_path)
        .arg(format!(
            "--sandbox={}",
            sandbox_enabled(request.permission_mode)
        ))
        .arg(format!(
            "--toolPermission={}",
            tool_permission(request.permission_mode)
        ))
        .env("NO_COLOR", "1")
        .current_dir(&request.vault_path)
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped());
    Ok(command)
}

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

fn sandbox_enabled(permission_mode: AiAgentPermissionMode) -> bool {
    permission_mode == AiAgentPermissionMode::Safe
}

fn tool_permission(permission_mode: AiAgentPermissionMode) -> &'static str {
    match permission_mode {
        AiAgentPermissionMode::Safe => "proceed-in-sandbox",
        AiAgentPermissionMode::PowerUser => "always-proceed",
    }
}

fn ensure_workspace_mcp_config(request: &AgentStreamRequest) -> Result<(), String> {
    let mcp_server_path = crate::cli_agent_runtime::mcp_server_path_string()?;
    write_workspace_mcp_config(&request.vault_path, &request.vault_paths, &mcp_server_path)
}

fn write_workspace_mcp_config(
    vault_path: &str,
    vault_paths: &[String],
    mcp_server_path: &str,
) -> Result<(), String> {
    let config_dir = Path::new(vault_path).join(".agents");
    std::fs::create_dir_all(&config_dir)
        .map_err(|e| format!("Failed to create .agents directory: {e}"))?;

    let config_path = config_dir.join("mcp_config.json");
    let mut config = read_json_object(&config_path)?;
    let servers_value = config
        .as_object_mut()
        .ok_or("Invalid mcp_config.json: not an object")?
        .entry("mcpServers")
        .or_insert_with(|| serde_json::json!({}));
    let servers = servers_value
        .as_object_mut()
        .ok_or("Invalid mcp_config.json: mcpServers is not an object")?;
    servers.insert(
        "tolaria".to_string(),
        crate::cli_agent_runtime::tolaria_node_mcp_server(
            mcp_server_path,
            vault_path,
            vault_paths,
            true,
        ),
    );

    std::fs::write(
        &config_path,
        serde_json::to_string_pretty(&config)
            .map_err(|e| format!("Failed to serialize Antigravity MCP config: {e}"))?,
    )
    .map_err(|e| format!("Failed to write .agents/mcp_config.json: {e}"))?;

    Ok(())
}

fn read_json_object(config_path: &Path) -> Result<serde_json::Value, String> {
    if !config_path.exists() {
        return Ok(serde_json::json!({}));
    }

    let raw = std::fs::read_to_string(config_path)
        .map_err(|e| format!("Failed to read .agents/mcp_config.json: {e}"))?;
    serde_json::from_str(&raw).map_err(|e| format!("Invalid .agents/mcp_config.json: {e}"))
}

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

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

    #[test]
    fn command_uses_one_shot_prompt_and_workspace_mcp_config() {
        let vault = tempfile::tempdir().unwrap();
        let request = request(
            vault.path().to_string_lossy().into_owned(),
            AiAgentPermissionMode::Safe,
        );
        let command = build_command(&PathBuf::from("agy"), &request).unwrap();
        let args = command.get_args().collect::<Vec<_>>();

        assert_eq!(command.get_program(), OsStr::new("agy"));
        assert!(args
            .windows(2)
            .any(|pair| pair == [OsStr::new("-p"), OsStr::new("Rename the note")]));
        assert!(args
            .windows(2)
            .any(|pair| pair == [OsStr::new("--add-dir"), vault.path().as_os_str()]));
        assert!(!args.contains(&OsStr::new("--cwd")));
        assert!(args.contains(&OsStr::new("--sandbox=true")));
        assert!(args.contains(&OsStr::new("--toolPermission=proceed-in-sandbox")));
        assert_eq!(command.get_current_dir(), Some(vault.path()));
        assert!(vault
            .path()
            .join(".agents")
            .join("mcp_config.json")
            .exists());
    }

    #[test]
    fn power_user_command_allows_non_sandboxed_autonomy_without_skip_flag() {
        let vault = tempfile::tempdir().unwrap();
        let request = request(
            vault.path().to_string_lossy().into_owned(),
            AiAgentPermissionMode::PowerUser,
        );
        let command = build_command(&PathBuf::from("agy"), &request).unwrap();
        let args = command.get_args().collect::<Vec<_>>();

        assert!(args.contains(&OsStr::new("--sandbox=false")));
        assert!(args.contains(&OsStr::new("--toolPermission=always-proceed")));
        assert!(!args.contains(&OsStr::new("--dangerously-skip-permissions")));
    }

    #[test]
    fn workspace_mcp_config_preserves_other_servers() {
        let vault = tempfile::tempdir().unwrap();
        let config_path = vault.path().join(".agents").join("mcp_config.json");
        std::fs::create_dir_all(config_path.parent().unwrap()).unwrap();
        std::fs::write(
            &config_path,
            serde_json::to_string(&serde_json::json!({
                "mcpServers": {
                    "other": { "command": "example" }
                }
            }))
            .unwrap(),
        )
        .unwrap();

        write_workspace_mcp_config(vault.path().to_str().unwrap(), &[], "/mcp/index.js").unwrap();
        let config: serde_json::Value =
            serde_json::from_str(&std::fs::read_to_string(config_path).unwrap()).unwrap();

        assert_eq!(config["mcpServers"]["other"]["command"], "example");
        assert_eq!(config["mcpServers"]["tolaria"]["command"], "node");
        assert_eq!(config["mcpServers"]["tolaria"]["env"]["WS_UI_PORT"], "9711");
    }

    #[test]
    fn workspace_mcp_config_rejects_non_object_servers() {
        let vault = tempfile::tempdir().unwrap();
        let config_path = vault.path().join(".agents").join("mcp_config.json");
        std::fs::create_dir_all(config_path.parent().unwrap()).unwrap();
        std::fs::write(
            &config_path,
            serde_json::to_string(&serde_json::json!({ "mcpServers": [] })).unwrap(),
        )
        .unwrap();

        let error =
            write_workspace_mcp_config(vault.path().to_str().unwrap(), &[], "/mcp/index.js")
                .unwrap_err();

        assert!(error.contains("mcpServers is not an object"));
    }
}
