use std::{env, path::PathBuf};

use anyhow::{Result, bail};

#[derive(Debug, Clone, Default)]
pub struct SshResolvedBinPaths {
    pub ssh: Option<PathBuf>,
    pub sshfs: Option<PathBuf>,
    pub umount: Option<PathBuf>,
    pub diskutil: Option<PathBuf>,
}

#[derive(Debug, Clone)]
pub struct SshConfig {
    pub ssh_bin_path: Option<PathBuf>,
    pub sshfs_bin_path: Option<PathBuf>,
    pub umount_bin_path: Option<PathBuf>,
    pub diskutil_bin_path: Option<PathBuf>,
    pub macos_block_apple_metadata: bool,
    pub managed_mount_root: Option<PathBuf>,
    pub allowed_hosts: Vec<String>,
    pub denied_hosts: Vec<String>,
    pub allowed_users: Vec<String>,
    pub allowed_auth_kinds: Vec<String>,
    pub allow_explicit_mount_paths: bool,
    pub allowed_mount_roots: Vec<PathBuf>,
    pub port_min: u16,
    pub port_max: u16,
}

impl Default for SshConfig {
    fn default() -> Self {
        Self {
            ssh_bin_path: resolve_bin_path(None, "ssh", ssh_default_paths()),
            sshfs_bin_path: resolve_bin_path(None, "sshfs", sshfs_default_paths()),
            umount_bin_path: resolve_bin_path(None, "umount", umount_default_paths()),
            diskutil_bin_path: resolve_bin_path(None, "diskutil", diskutil_default_paths()),
            macos_block_apple_metadata: default_macos_block_apple_metadata(),
            managed_mount_root: None,
            allowed_hosts: Vec::new(),
            denied_hosts: Vec::new(),
            allowed_users: Vec::new(),
            allowed_auth_kinds: Vec::new(),
            allow_explicit_mount_paths: true,
            allowed_mount_roots: Vec::new(),
            port_min: 1,
            port_max: u16::MAX,
        }
    }
}

impl SshConfig {
    pub fn resolved_ssh_bin_path(&self) -> Option<PathBuf> {
        resolve_bin_path(self.ssh_bin_path.clone(), "ssh", ssh_default_paths())
    }

    pub fn resolved_sshfs_bin_path(&self) -> Option<PathBuf> {
        resolve_bin_path(self.sshfs_bin_path.clone(), "sshfs", sshfs_default_paths())
    }

    pub fn resolved_umount_bin_path(&self) -> Option<PathBuf> {
        resolve_bin_path(
            self.umount_bin_path.clone(),
            "umount",
            umount_default_paths(),
        )
    }

    pub fn resolved_diskutil_bin_path(&self) -> Option<PathBuf> {
        resolve_bin_path(
            self.diskutil_bin_path.clone(),
            "diskutil",
            diskutil_default_paths(),
        )
    }

    pub fn resolved_bin_paths(&self) -> SshResolvedBinPaths {
        SshResolvedBinPaths {
            ssh: self.resolved_ssh_bin_path(),
            sshfs: self.resolved_sshfs_bin_path(),
            umount: self.resolved_umount_bin_path(),
            diskutil: self.resolved_diskutil_bin_path(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct Config {
    pub session_limit: usize,
    pub default_read_limit: usize,
    pub max_buffer_lines: usize,
    pub allowed_cwd_roots: Vec<PathBuf>,
    pub allowed_commands: Vec<String>,
    pub denied_commands: Vec<String>,
    pub allowed_env_vars: Vec<String>,
    pub denied_env_vars: Vec<String>,
    pub ssh: SshConfig,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            session_limit: 32,
            default_read_limit: 200,
            max_buffer_lines: 50_000,
            allowed_cwd_roots: vec![env::current_dir().unwrap_or_else(|_| PathBuf::from("."))],
            allowed_commands: Vec::new(),
            denied_commands: Vec::new(),
            allowed_env_vars: Vec::new(),
            denied_env_vars: vec![
                "LD_PRELOAD".to_string(),
                "LD_LIBRARY_PATH".to_string(),
                "DYLD_INSERT_LIBRARIES".to_string(),
                "DYLD_LIBRARY_PATH".to_string(),
            ],
            ssh: SshConfig::default(),
        }
    }
}

impl Config {
    pub fn from_env() -> Result<Self> {
        let mut config = Self::default();

        if let Ok(value) = env::var("PTY_MCP_SESSION_LIMIT") {
            config.session_limit = parse_usize("PTY_MCP_SESSION_LIMIT", &value)?;
        }

        if let Ok(value) = env::var("PTY_MCP_DEFAULT_READ_LIMIT") {
            config.default_read_limit = parse_usize("PTY_MCP_DEFAULT_READ_LIMIT", &value)?;
        }

        if let Ok(value) = env::var("PTY_MCP_MAX_BUFFER_LINES") {
            config.max_buffer_lines = parse_usize("PTY_MCP_MAX_BUFFER_LINES", &value)?;
        }

        if let Ok(value) = env::var("PTY_MCP_ALLOWED_CWD_ROOTS") {
            config.allowed_cwd_roots = value
                .split(':')
                .filter(|segment| !segment.trim().is_empty())
                .map(PathBuf::from)
                .collect();
        }

        if let Ok(value) = env::var("PTY_MCP_ALLOWED_COMMANDS") {
            config.allowed_commands = parse_csv(&value);
        }

        if let Ok(value) = env::var("PTY_MCP_DENIED_COMMANDS") {
            config.denied_commands = parse_csv(&value);
        }

        if let Ok(value) = env::var("PTY_MCP_ALLOWED_ENV_VARS") {
            config.allowed_env_vars = parse_csv(&value);
        }

        if let Ok(value) = env::var("PTY_MCP_DENIED_ENV_VARS") {
            config.denied_env_vars = parse_csv(&value);
        }

        if let Ok(value) = env::var("PTY_MCP_SSH_BIN_PATH") {
            config.ssh.ssh_bin_path =
                resolve_bin_path(Some(PathBuf::from(value)), "ssh", ssh_default_paths());
        }

        if let Ok(value) = env::var("PTY_MCP_SSHFS_BIN_PATH") {
            config.ssh.sshfs_bin_path =
                resolve_bin_path(Some(PathBuf::from(value)), "sshfs", sshfs_default_paths());
        }

        if let Ok(value) = env::var("PTY_MCP_UMOUNT_BIN_PATH") {
            config.ssh.umount_bin_path =
                resolve_bin_path(Some(PathBuf::from(value)), "umount", umount_default_paths());
        }

        if let Ok(value) = env::var("PTY_MCP_DISKUTIL_BIN_PATH") {
            config.ssh.diskutil_bin_path = resolve_bin_path(
                Some(PathBuf::from(value)),
                "diskutil",
                diskutil_default_paths(),
            );
        }

        if let Ok(value) = env::var("PTY_MCP_SSH_MACOS_BLOCK_APPLE_METADATA") {
            config.ssh.macos_block_apple_metadata =
                parse_bool("PTY_MCP_SSH_MACOS_BLOCK_APPLE_METADATA", &value)?;
        }

        if let Ok(value) = env::var("PTY_MCP_SSH_MANAGED_MOUNT_ROOT") {
            let trimmed = value.trim();
            if !trimmed.is_empty() {
                let managed_mount_root = PathBuf::from(trimmed);
                if !config.allowed_cwd_roots.contains(&managed_mount_root) {
                    config.allowed_cwd_roots.push(managed_mount_root.clone());
                }
                config.ssh.managed_mount_root = Some(managed_mount_root);
            }
        }

        if let Ok(value) = env::var("PTY_MCP_SSH_ALLOWED_HOSTS") {
            config.ssh.allowed_hosts = parse_csv(&value);
        }

        if let Ok(value) = env::var("PTY_MCP_SSH_DENIED_HOSTS") {
            config.ssh.denied_hosts = parse_csv(&value);
        }

        if let Ok(value) = env::var("PTY_MCP_SSH_ALLOWED_USERS") {
            config.ssh.allowed_users = parse_csv(&value);
        }

        if let Ok(value) = env::var("PTY_MCP_SSH_ALLOWED_AUTH_KINDS") {
            config.ssh.allowed_auth_kinds =
                parse_auth_kinds("PTY_MCP_SSH_ALLOWED_AUTH_KINDS", &value)?;
        }

        if let Ok(value) = env::var("PTY_MCP_SSH_ALLOW_EXPLICIT_MOUNT_PATHS") {
            config.ssh.allow_explicit_mount_paths =
                parse_bool("PTY_MCP_SSH_ALLOW_EXPLICIT_MOUNT_PATHS", &value)?;
        }

        if let Ok(value) = env::var("PTY_MCP_SSH_ALLOWED_MOUNT_ROOTS") {
            config.ssh.allowed_mount_roots = parse_path_list(&value);
        }

        if let Ok(value) = env::var("PTY_MCP_SSH_PORT_MIN") {
            config.ssh.port_min = parse_u16("PTY_MCP_SSH_PORT_MIN", &value)?;
        }

        if let Ok(value) = env::var("PTY_MCP_SSH_PORT_MAX") {
            config.ssh.port_max = parse_u16("PTY_MCP_SSH_PORT_MAX", &value)?;
        }

        if config.ssh.port_min > config.ssh.port_max {
            bail!(
                "invalid SSH port range: PTY_MCP_SSH_PORT_MIN={} is greater than PTY_MCP_SSH_PORT_MAX={}",
                config.ssh.port_min,
                config.ssh.port_max
            );
        }

        if config.ssh.allowed_mount_roots.is_empty() {
            config.ssh.allowed_mount_roots = config.allowed_cwd_roots.clone();
        }

        Ok(config)
    }
}

fn parse_csv(value: &str) -> Vec<String> {
    value
        .split(',')
        .map(str::trim)
        .filter(|segment| !segment.is_empty())
        .map(ToString::to_string)
        .collect()
}

fn parse_path_list(value: &str) -> Vec<PathBuf> {
    value
        .split(':')
        .map(str::trim)
        .filter(|segment| !segment.is_empty())
        .map(PathBuf::from)
        .collect()
}

fn parse_auth_kinds(key: &'static str, value: &str) -> Result<Vec<String>> {
    let mut parsed = Vec::new();
    for segment in value
        .split(',')
        .map(str::trim)
        .filter(|segment| !segment.is_empty())
    {
        let normalized = normalize_auth_kind(segment)
            .ok_or_else(|| anyhow::anyhow!("invalid ssh auth kind for {key}: value={segment}"))?;
        if !parsed.contains(&normalized) {
            parsed.push(normalized);
        }
    }
    Ok(parsed)
}

fn normalize_auth_kind(value: &str) -> Option<String> {
    match value.trim().to_ascii_lowercase().as_str() {
        "host_alias" | "config_alias" => Some("host_alias".to_string()),
        "ssh_agent" | "agent" => Some("ssh_agent".to_string()),
        "identity_path" | "identity_file" => Some("identity_path".to_string()),
        _ => None,
    }
}

fn parse_usize(key: &'static str, value: &str) -> Result<usize> {
    value
        .parse::<usize>()
        .map_err(|source| anyhow::anyhow!("invalid usize for {key}: value={value}: {source}"))
}

fn parse_u16(key: &'static str, value: &str) -> Result<u16> {
    value
        .parse::<u16>()
        .map_err(|source| anyhow::anyhow!("invalid u16 for {key}: value={value}: {source}"))
}

fn parse_bool(key: &'static str, value: &str) -> Result<bool> {
    match value.trim().to_ascii_lowercase().as_str() {
        "1" | "true" | "yes" | "on" => Ok(true),
        "0" | "false" | "no" | "off" => Ok(false),
        _ => bail!("invalid bool for {key}: value={value}"),
    }
}

#[cfg(target_os = "macos")]
const fn default_macos_block_apple_metadata() -> bool {
    true
}

#[cfg(not(target_os = "macos"))]
const fn default_macos_block_apple_metadata() -> bool {
    false
}

fn resolve_bin_path(
    explicit: Option<PathBuf>,
    command_name: &str,
    default_candidates: &'static [&'static str],
) -> Option<PathBuf> {
    if let Some(path) = explicit {
        let trimmed = path.to_string_lossy().trim().to_string();
        if !trimmed.is_empty() {
            return Some(PathBuf::from(trimmed));
        }
    }

    for candidate in default_candidates {
        let path = PathBuf::from(candidate);
        if path.is_file() {
            return Some(path);
        }
    }

    find_in_path(command_name)
}

fn find_in_path(command_name: &str) -> Option<PathBuf> {
    let path_var = env::var_os("PATH")?;
    for entry in env::split_paths(&path_var) {
        let candidate = entry.join(command_name);
        if candidate.is_file() {
            return Some(candidate);
        }
    }
    None
}

#[cfg(target_os = "macos")]
fn ssh_default_paths() -> &'static [&'static str] {
    &[
        "/usr/bin/ssh",
        "/opt/homebrew/bin/ssh",
        "/usr/local/bin/ssh",
    ]
}

#[cfg(not(target_os = "macos"))]
fn ssh_default_paths() -> &'static [&'static str] {
    &["/usr/bin/ssh", "/usr/local/bin/ssh"]
}

#[cfg(target_os = "macos")]
fn sshfs_default_paths() -> &'static [&'static str] {
    &[
        "/opt/homebrew/bin/sshfs",
        "/usr/local/bin/sshfs",
        "/usr/bin/sshfs",
    ]
}

#[cfg(not(target_os = "macos"))]
fn sshfs_default_paths() -> &'static [&'static str] {
    &["/usr/bin/sshfs", "/usr/local/bin/sshfs"]
}

#[cfg(target_os = "macos")]
fn umount_default_paths() -> &'static [&'static str] {
    &["/sbin/umount", "/usr/sbin/umount", "/usr/bin/umount"]
}

#[cfg(not(target_os = "macos"))]
fn umount_default_paths() -> &'static [&'static str] {
    &["/usr/bin/umount", "/bin/umount", "/usr/local/bin/umount"]
}

#[cfg(target_os = "macos")]
fn diskutil_default_paths() -> &'static [&'static str] {
    &["/usr/sbin/diskutil", "/usr/bin/diskutil"]
}

#[cfg(not(target_os = "macos"))]
fn diskutil_default_paths() -> &'static [&'static str] {
    &[]
}

#[cfg(test)]
mod tests {
    use std::sync::{Mutex, OnceLock};

    use super::{
        Config, default_macos_block_apple_metadata, parse_auth_kinds, parse_bool, parse_usize,
    };

    fn env_lock() -> &'static Mutex<()> {
        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
        LOCK.get_or_init(|| Mutex::new(()))
    }

    struct EnvGuard {
        key: &'static str,
        original: Option<String>,
    }

    impl EnvGuard {
        fn set(key: &'static str, value: Option<&str>) -> Self {
            let original = std::env::var(key).ok();
            match value {
                Some(value) => unsafe { std::env::set_var(key, value) },
                None => unsafe { std::env::remove_var(key) },
            }
            Self { key, original }
        }
    }

    impl Drop for EnvGuard {
        fn drop(&mut self) {
            match self.original.as_deref() {
                Some(value) => unsafe { std::env::set_var(self.key, value) },
                None => unsafe { std::env::remove_var(self.key) },
            }
        }
    }

    #[test]
    fn parse_usize_error_contains_key_and_value() {
        let error = parse_usize("PTY_MCP_SESSION_LIMIT", "abc").expect_err("parse should fail");
        let text = format!("{error:#}");
        assert!(text.contains("PTY_MCP_SESSION_LIMIT"));
        assert!(text.contains("abc"));
    }

    #[test]
    fn parse_bool_error_contains_key_and_value() {
        let error = parse_bool("PTY_MCP_SSH_ALLOW_EXPLICIT_MOUNT_PATHS", "maybe")
            .expect_err("parse should fail");
        let text = format!("{error:#}");
        assert!(text.contains("PTY_MCP_SSH_ALLOW_EXPLICIT_MOUNT_PATHS"));
        assert!(text.contains("maybe"));
    }

    #[test]
    fn parse_auth_kind_error_contains_key_and_value() {
        let error = parse_auth_kinds("PTY_MCP_SSH_ALLOWED_AUTH_KINDS", "magic")
            .expect_err("parse should fail");
        let text = format!("{error:#}");
        assert!(text.contains("PTY_MCP_SSH_ALLOWED_AUTH_KINDS"));
        assert!(text.contains("magic"));
    }

    #[test]
    fn config_from_env_uses_platform_default_for_macos_metadata_blocking() {
        let _lock = env_lock().lock().expect("env lock poisoned");
        let _guard = EnvGuard::set("PTY_MCP_SSH_MACOS_BLOCK_APPLE_METADATA", None);

        let config = Config::from_env().expect("config should load");

        assert_eq!(
            config.ssh.macos_block_apple_metadata,
            default_macos_block_apple_metadata()
        );
    }

    #[test]
    fn config_from_env_allows_overriding_macos_metadata_blocking() {
        let _lock = env_lock().lock().expect("env lock poisoned");
        let _guard = EnvGuard::set("PTY_MCP_SSH_MACOS_BLOCK_APPLE_METADATA", Some("false"));

        let config = Config::from_env().expect("config should load");

        assert!(!config.ssh.macos_block_apple_metadata);
    }
}
