use super::PluginMcpConfigParseOutcome;
use super::PluginMcpServerParseError;
use super::PluginMcpServerPlacement;
use super::parse_plugin_mcp_config;
use codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID;
use codex_config::McpServerConfig;
use codex_config::McpServerEnvVar;
use codex_config::McpServerOAuthConfig;
use codex_config::McpServerTransportConfig;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;

fn plugin_root() -> PathBuf {
    std::env::current_dir()
        .expect("current directory")
        .join("plugin-root")
}

fn stdio_server(
    command: &str,
    environment_id: &str,
    cwd: &Path,
    env_vars: Vec<McpServerEnvVar>,
) -> McpServerConfig {
    McpServerConfig {
        transport: McpServerTransportConfig::Stdio {
            command: command.to_string(),
            args: Vec::new(),
            env: None,
            env_vars,
            cwd: Some(cwd.to_path_buf()),
        },
        environment_id: environment_id.to_string(),
        enabled: true,
        required: false,
        supports_parallel_tool_calls: false,
        disabled_reason: None,
        startup_timeout_sec: None,
        tool_timeout_sec: None,
        default_tools_approval_mode: None,
        enabled_tools: None,
        disabled_tools: None,
        scopes: None,
        oauth: None,
        oauth_resource: None,
        tools: HashMap::new(),
    }
}

#[test]
fn declared_placement_preserves_local_plugin_normalization() {
    let plugin_root = plugin_root();
    let expected_stdio = stdio_server(
        "demo-mcp",
        "configured-environment",
        &plugin_root.join("scripts"),
        Vec::new(),
    );
    let expected_http = McpServerConfig {
        transport: McpServerTransportConfig::StreamableHttp {
            url: "https://example.com/mcp".to_string(),
            bearer_token_env_var: None,
            http_headers: None,
            env_http_headers: None,
        },
        environment_id: DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string(),
        enabled: true,
        required: false,
        supports_parallel_tool_calls: false,
        disabled_reason: None,
        startup_timeout_sec: None,
        tool_timeout_sec: None,
        default_tools_approval_mode: None,
        enabled_tools: None,
        disabled_tools: None,
        scopes: None,
        oauth: Some(McpServerOAuthConfig {
            client_id: Some("client-id".to_string()),
        }),
        oauth_resource: None,
        tools: HashMap::new(),
    };

    let outcome = parse_plugin_mcp_config(
        &plugin_root,
        r#"{
            "demo": {
                "type": "stdio",
                "command": "demo-mcp",
                "environment_id": "configured-environment",
                "cwd": "scripts"
            },
            "hosted": {
                "type": "http",
                "url": "https://example.com/mcp",
                "oauth": {"clientId": "client-id", "callbackPort": 9876}
            }
        }"#,
        PluginMcpServerPlacement::Declared,
    )
    .expect("parse plugin MCP config");

    assert_eq!(
        outcome,
        PluginMcpConfigParseOutcome {
            servers: BTreeMap::from([
                ("demo".to_string(), expected_stdio),
                ("hosted".to_string(), expected_http),
            ]),
            errors: Vec::new(),
        }
    );
}

#[test]
fn environment_placement_forces_authority_and_defaults_null_cwd() {
    let plugin_root = plugin_root();
    let outcome = parse_plugin_mcp_config(
        &plugin_root,
        r#"{
            "$schema":"https://example.com/plugin-mcp.schema.json",
            "mcpServers":{"demo":{
                "command":"demo-mcp",
                "environment_id":"local",
                "cwd":null,
                "env_vars":["EXECUTOR_TOKEN", {"name":"OTHER_TOKEN"}]
            }}
        }"#,
        PluginMcpServerPlacement::Environment {
            environment_id: "executor-1",
        },
    )
    .expect("parse plugin MCP config");

    assert_eq!(
        outcome,
        PluginMcpConfigParseOutcome {
            servers: BTreeMap::from([(
                "demo".to_string(),
                stdio_server(
                    "demo-mcp",
                    "executor-1",
                    &plugin_root,
                    vec![
                        McpServerEnvVar::Config {
                            name: "EXECUTOR_TOKEN".to_string(),
                            source: Some("remote".to_string()),
                        },
                        McpServerEnvVar::Config {
                            name: "OTHER_TOKEN".to_string(),
                            source: Some("remote".to_string()),
                        },
                    ],
                ),
            )]),
            errors: Vec::new(),
        }
    );
}

#[test]
fn environment_placement_resolves_relative_cwd_beneath_plugin_root() {
    let plugin_root = plugin_root();
    let outcome = parse_plugin_mcp_config(
        &plugin_root,
        r#"{"demo":{"command":"demo-mcp","cwd":"scripts"}}"#,
        PluginMcpServerPlacement::Environment {
            environment_id: "executor-1",
        },
    )
    .expect("parse plugin MCP config");

    assert_eq!(
        outcome,
        PluginMcpConfigParseOutcome {
            servers: BTreeMap::from([(
                "demo".to_string(),
                stdio_server(
                    "demo-mcp",
                    "executor-1",
                    &plugin_root.join("scripts"),
                    Vec::new(),
                ),
            )]),
            errors: Vec::new(),
        }
    );
}

#[test]
fn environment_placement_rejects_relative_cwd_that_escapes_package() {
    let plugin_root = plugin_root();
    let outcome = parse_plugin_mcp_config(
        &plugin_root,
        r#"{"demo":{"command":"demo-mcp","cwd":"../outside"}}"#,
        PluginMcpServerPlacement::Environment {
            environment_id: "executor-1",
        },
    )
    .expect("parse plugin MCP config");

    assert_eq!(
        outcome,
        PluginMcpConfigParseOutcome {
            servers: BTreeMap::new(),
            errors: vec![PluginMcpServerParseError {
                name: "demo".to_string(),
                message: format!(
                    "relative cwd `../outside` must remain within plugin root `{}`",
                    plugin_root.display()
                ),
            }],
        }
    );
}

#[test]
fn environment_placement_rejects_orchestrator_env_vars() {
    let plugin_root = plugin_root();
    let outcome = parse_plugin_mcp_config(
        &plugin_root,
        r#"{"demo":{"command":"demo-mcp","env_vars":[{"name":"TOKEN","source":"local"}]}}"#,
        PluginMcpServerPlacement::Environment {
            environment_id: "executor-1",
        },
    )
    .expect("parse plugin MCP config");

    assert_eq!(
        outcome,
        PluginMcpConfigParseOutcome {
            servers: BTreeMap::new(),
            errors: vec![PluginMcpServerParseError {
                name: "demo".to_string(),
                message:
                    "env_vars entry `TOKEN` cannot use source `local` in an executor-owned plugin"
                        .to_string(),
            }],
        }
    );
}

#[test]
fn local_environment_placement_preserves_local_env_vars() {
    let plugin_root = plugin_root();
    let outcome = parse_plugin_mcp_config(
        &plugin_root,
        r#"{"demo":{"command":"demo-mcp","env_vars":["TOKEN",{"name":"OTHER","source":"local"}]}}"#,
        PluginMcpServerPlacement::Environment {
            environment_id: DEFAULT_MCP_SERVER_ENVIRONMENT_ID,
        },
    )
    .expect("parse plugin MCP config");

    assert_eq!(
        outcome,
        PluginMcpConfigParseOutcome {
            servers: BTreeMap::from([(
                "demo".to_string(),
                stdio_server(
                    "demo-mcp",
                    DEFAULT_MCP_SERVER_ENVIRONMENT_ID,
                    &plugin_root,
                    vec![
                        McpServerEnvVar::Name("TOKEN".to_string()),
                        McpServerEnvVar::Config {
                            name: "OTHER".to_string(),
                            source: Some("local".to_string()),
                        },
                    ],
                ),
            )]),
            errors: Vec::new(),
        }
    );
}

#[test]
fn local_environment_placement_rejects_remote_env_vars() {
    let plugin_root = plugin_root();
    let outcome = parse_plugin_mcp_config(
        &plugin_root,
        r#"{"demo":{"command":"demo-mcp","env_vars":[{"name":"TOKEN","source":"remote"}]}}"#,
        PluginMcpServerPlacement::Environment {
            environment_id: DEFAULT_MCP_SERVER_ENVIRONMENT_ID,
        },
    )
    .expect("parse plugin MCP config");

    assert_eq!(
        outcome,
        PluginMcpConfigParseOutcome {
            servers: BTreeMap::new(),
            errors: vec![PluginMcpServerParseError {
                name: "demo".to_string(),
                message: "env_vars entry `TOKEN` cannot use source `remote` in a local environment"
                    .to_string(),
            }],
        }
    );
}
