use super::*;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
use std::path::PathBuf;

#[test]
fn deserialize_stdio_command_server_config() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
        "#,
    )
    .expect("should deserialize command config");

    assert_eq!(
        cfg.transport,
        McpServerTransportConfig::Stdio {
            command: "echo".to_string(),
            args: vec![],
            env: None,
            env_vars: Vec::new(),
            cwd: None,
        }
    );
    assert!(cfg.enabled);
    assert!(!cfg.required);
    assert!(cfg.enabled_tools.is_none());
    assert!(cfg.disabled_tools.is_none());
}

#[test]
fn deserialize_stdio_command_server_config_with_args() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            args = ["hello", "world"]
        "#,
    )
    .expect("should deserialize command config");

    assert_eq!(
        cfg.transport,
        McpServerTransportConfig::Stdio {
            command: "echo".to_string(),
            args: vec!["hello".to_string(), "world".to_string()],
            env: None,
            env_vars: Vec::new(),
            cwd: None,
        }
    );
    assert!(cfg.enabled);
}

#[test]
fn deserialize_remote_stdio_server_requires_absolute_cwd() {
    let missing_cwd = toml::from_str::<McpServerConfig>(
        r#"
            command = "echo"
            environment_id = "remote"
        "#,
    )
    .expect_err("remote stdio MCP should require cwd");
    assert!(
        missing_cwd
            .to_string()
            .contains("remote stdio MCP servers require an absolute cwd"),
        "unexpected error: {missing_cwd}"
    );

    let relative_cwd = toml::from_str::<McpServerConfig>(
        r#"
            command = "echo"
            environment_id = "remote"
            cwd = "relative"
        "#,
    )
    .expect_err("remote stdio MCP should require absolute cwd");
    assert!(
        relative_cwd.to_string().contains("got `relative`"),
        "unexpected error: {relative_cwd}"
    );
}

#[test]
fn deserialize_remote_stdio_server_accepts_absolute_cwd() {
    let cwd = std::env::temp_dir();
    let cfg: McpServerConfig = match toml::from_str(&format!(
        r#"
            command = "echo"
            environment_id = "remote"
            cwd = {cwd:?}
        "#
    )) {
        Ok(cfg) => cfg,
        Err(error) => panic!("remote stdio MCP should accept absolute cwd: {error}"),
    };

    assert_eq!(
        cfg.transport,
        McpServerTransportConfig::Stdio {
            command: "echo".to_string(),
            args: vec![],
            env: None,
            env_vars: Vec::new(),
            cwd: Some(cwd),
        }
    );
}

#[test]
fn deserialize_stdio_command_server_config_with_arg_with_args_and_env() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            args = ["hello", "world"]
            env = { "FOO" = "BAR" }
        "#,
    )
    .expect("should deserialize command config");

    assert_eq!(
        cfg.transport,
        McpServerTransportConfig::Stdio {
            command: "echo".to_string(),
            args: vec!["hello".to_string(), "world".to_string()],
            env: Some(HashMap::from([("FOO".to_string(), "BAR".to_string())])),
            env_vars: Vec::new(),
            cwd: None,
        }
    );
    assert!(cfg.enabled);
}

#[test]
fn deserialize_stdio_command_server_config_with_env_vars() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            env_vars = ["FOO", "BAR"]
        "#,
    )
    .expect("should deserialize command config with env_vars");

    assert_eq!(
        cfg.transport,
        McpServerTransportConfig::Stdio {
            command: "echo".to_string(),
            args: vec![],
            env: None,
            env_vars: vec!["FOO".into(), "BAR".into()],
            cwd: None,
        }
    );
}

#[test]
fn deserialize_stdio_command_server_config_with_env_var_sources() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            env_vars = [
                "LEGACY_TOKEN",
                { name = "LOCAL_TOKEN", source = "local" },
                { name = "REMOTE_TOKEN", source = "remote" },
            ]
        "#,
    )
    .expect("should deserialize command config with sourced env_vars");

    assert_eq!(
        cfg.transport,
        McpServerTransportConfig::Stdio {
            command: "echo".to_string(),
            args: vec![],
            env: None,
            env_vars: vec![
                McpServerEnvVar::Name("LEGACY_TOKEN".to_string()),
                McpServerEnvVar::Config {
                    name: "LOCAL_TOKEN".to_string(),
                    source: Some("local".to_string()),
                },
                McpServerEnvVar::Config {
                    name: "REMOTE_TOKEN".to_string(),
                    source: Some("remote".to_string()),
                },
            ],
            cwd: None,
        }
    );
}

#[test]
fn deserialize_stdio_command_server_config_rejects_unknown_env_var_source() {
    let err = toml::from_str::<McpServerConfig>(
        r#"
            command = "echo"
            env_vars = [{ name = "TOKEN", source = "elsewhere" }]
        "#,
    )
    .expect_err("unsupported env var source should be rejected");

    assert!(
        err.to_string()
            .contains("unsupported env_vars source `elsewhere`"),
        "unexpected error: {err}"
    );
}

#[test]
fn deserialize_stdio_command_server_config_with_cwd() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            cwd = "/tmp"
        "#,
    )
    .expect("should deserialize command config with cwd");

    assert_eq!(
        cfg.transport,
        McpServerTransportConfig::Stdio {
            command: "echo".to_string(),
            args: vec![],
            env: None,
            env_vars: Vec::new(),
            cwd: Some(PathBuf::from("/tmp")),
        }
    );
}

#[test]
fn deserialize_disabled_server_config() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            enabled = false
        "#,
    )
    .expect("should deserialize disabled server config");

    assert!(!cfg.enabled);
    assert!(!cfg.required);
}

#[test]
fn deserialize_required_server_config() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            required = true
        "#,
    )
    .expect("should deserialize required server config");

    assert!(cfg.required);
}

#[test]
fn deserialize_streamable_http_server_config() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            url = "https://example.com/mcp"
        "#,
    )
    .expect("should deserialize http config");

    assert_eq!(
        cfg.transport,
        McpServerTransportConfig::StreamableHttp {
            url: "https://example.com/mcp".to_string(),
            bearer_token_env_var: None,
            http_headers: None,
            env_http_headers: None,
        }
    );
    assert!(cfg.enabled);
}

#[test]
fn deserialize_streamable_http_server_config_with_env_var() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            url = "https://example.com/mcp"
            bearer_token_env_var = "GITHUB_TOKEN"
        "#,
    )
    .expect("should deserialize http config");

    assert_eq!(
        cfg.transport,
        McpServerTransportConfig::StreamableHttp {
            url: "https://example.com/mcp".to_string(),
            bearer_token_env_var: Some("GITHUB_TOKEN".to_string()),
            http_headers: None,
            env_http_headers: None,
        }
    );
    assert!(cfg.enabled);
}

#[test]
fn deserialize_streamable_http_server_config_with_headers() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            url = "https://example.com/mcp"
            http_headers = { "X-Foo" = "bar" }
            env_http_headers = { "X-Token" = "TOKEN_ENV" }
        "#,
    )
    .expect("should deserialize http config with headers");

    assert_eq!(
        cfg.transport,
        McpServerTransportConfig::StreamableHttp {
            url: "https://example.com/mcp".to_string(),
            bearer_token_env_var: None,
            http_headers: Some(HashMap::from([("X-Foo".to_string(), "bar".to_string())])),
            env_http_headers: Some(HashMap::from([(
                "X-Token".to_string(),
                "TOKEN_ENV".to_string()
            )])),
        }
    );
}

#[test]
fn deserialize_streamable_http_server_config_with_oauth_resource() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            url = "https://example.com/mcp"
            oauth_resource = "https://api.example.com"
        "#,
    )
    .expect("should deserialize http config with oauth_resource");

    assert_eq!(
        cfg.oauth_resource,
        Some("https://api.example.com".to_string())
    );
}

#[test]
fn deserialize_streamable_http_server_config_with_oauth_client_id() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            url = "https://example.com/mcp"

            [oauth]
            client_id = "eci-prd-pub-codex-123"
        "#,
    )
    .expect("should deserialize http config with oauth client id");

    assert_eq!(
        cfg.oauth,
        Some(McpServerOAuthConfig {
            client_id: Some("eci-prd-pub-codex-123".to_string()),
        })
    );
}

#[test]
fn deserialize_server_config_with_tool_filters() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            enabled_tools = ["allowed"]
            disabled_tools = ["blocked"]
        "#,
    )
    .expect("should deserialize tool filters");

    assert_eq!(cfg.enabled_tools, Some(vec!["allowed".to_string()]));
    assert_eq!(cfg.disabled_tools, Some(vec!["blocked".to_string()]));
}

#[test]
fn deserialize_server_config_with_parallel_tool_calls() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            supports_parallel_tool_calls = true
        "#,
    )
    .expect("should deserialize supports_parallel_tool_calls");

    assert!(cfg.supports_parallel_tool_calls);
}

#[test]
fn deserialize_server_config_with_default_tool_approval_mode() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            default_tools_approval_mode = "approve"

            [tools.search]
            approval_mode = "prompt"
        "#,
    )
    .expect("should deserialize default tool approval mode");

    assert_eq!(
        cfg.default_tools_approval_mode,
        Some(AppToolApproval::Approve)
    );
    assert_eq!(
        cfg.tools.get("search"),
        Some(&McpServerToolConfig {
            approval_mode: Some(AppToolApproval::Prompt),
        })
    );

    let serialized = toml::to_string(&cfg).expect("should serialize MCP config");
    assert!(serialized.contains("default_tools_approval_mode = \"approve\""));

    let round_tripped: McpServerConfig =
        toml::from_str(&serialized).expect("should deserialize serialized MCP config");
    assert_eq!(round_tripped, cfg);
}

#[test]
fn serialize_round_trips_server_config_with_parallel_tool_calls() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            supports_parallel_tool_calls = true
            tool_timeout_sec = 2.0
        "#,
    )
    .expect("should deserialize supports_parallel_tool_calls");

    let serialized = toml::to_string(&cfg).expect("should serialize MCP config");
    assert!(serialized.contains("supports_parallel_tool_calls = true"));

    let round_tripped: McpServerConfig =
        toml::from_str(&serialized).expect("should deserialize serialized MCP config");
    assert_eq!(round_tripped, cfg);
}

#[test]
fn deserialize_ignores_unknown_server_fields() {
    let cfg: McpServerConfig = toml::from_str(
        r#"
            command = "echo"
            trust_level = "trusted"
        "#,
    )
    .expect("should ignore unknown server fields");

    assert_eq!(
        cfg,
        McpServerConfig {
            transport: McpServerTransportConfig::Stdio {
                command: "echo".to_string(),
                args: vec![],
                env: None,
                env_vars: Vec::new(),
                cwd: None,
            },
            environment_id: crate::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: None,
            oauth_resource: None,
            tools: HashMap::new(),
        }
    );
}

#[test]
fn deserialize_rejects_command_and_url() {
    toml::from_str::<McpServerConfig>(
        r#"
            command = "echo"
            url = "https://example.com"
        "#,
    )
    .expect_err("should reject command+url");
}

#[test]
fn deserialize_rejects_env_for_http_transport() {
    toml::from_str::<McpServerConfig>(
        r#"
            url = "https://example.com"
            env = { "FOO" = "BAR" }
        "#,
    )
    .expect_err("should reject env for http transport");
}

#[test]
fn deserialize_rejects_headers_for_stdio() {
    toml::from_str::<McpServerConfig>(
        r#"
            command = "echo"
            http_headers = { "X-Foo" = "bar" }
        "#,
    )
    .expect_err("should reject http_headers for stdio transport");

    toml::from_str::<McpServerConfig>(
        r#"
            command = "echo"
            env_http_headers = { "X-Foo" = "BAR_ENV" }
        "#,
    )
    .expect_err("should reject env_http_headers for stdio transport");

    let err = toml::from_str::<McpServerConfig>(
        r#"
            command = "echo"
            oauth = { client_id = "eci-prd-pub-codex-123" }
        "#,
    )
    .expect_err("should reject oauth for stdio transport");

    assert!(
        err.to_string().contains("oauth is not supported for stdio"),
        "unexpected error: {err}"
    );

    let err = toml::from_str::<McpServerConfig>(
        r#"
            command = "echo"
            oauth_resource = "https://api.example.com"
        "#,
    )
    .expect_err("should reject oauth_resource for stdio transport");

    assert!(
        err.to_string()
            .contains("oauth_resource is not supported for stdio"),
        "unexpected error: {err}"
    );
}

#[test]
fn deserialize_rejects_inline_bearer_token_field() {
    let err = toml::from_str::<McpServerConfig>(
        r#"
            url = "https://example.com"
            bearer_token = "secret"
        "#,
    )
    .expect_err("should reject bearer_token field");

    assert!(
        err.to_string().contains("bearer_token is not supported"),
        "unexpected error: {err}"
    );
}
