use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};

const DEFAULT_BASE_URL: &str = "https://api.backpack.exchange";
const DEFAULT_WS_URL: &str = "wss://ws.backpack.exchange";

#[derive(Clone, Serialize, Deserialize, Default)]
pub struct Profile {
    pub api_secret: Option<String>,
    pub base_url: Option<String>,
    pub ws_url: Option<String>,
}

impl std::fmt::Debug for Profile {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Profile")
            .field(
                "api_secret",
                &self.api_secret.as_ref().map(|_| "[REDACTED]"),
            )
            .field("base_url", &self.base_url)
            .field("ws_url", &self.ws_url)
            .finish()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StrategyConfig {
    pub symbol: String,
    pub side: String,
    pub size: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stop_loss: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub take_profit: Option<String>,
    #[serde(default)]
    pub auto_lend: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ConfigFile {
    #[serde(default)]
    pub keybindings: Option<HashMap<String, String>>,
    #[serde(default)]
    pub strategies: Option<HashMap<String, StrategyConfig>>,
    #[serde(flatten)]
    pub profiles: HashMap<String, Profile>,
}

#[derive(Clone)]
pub struct ResolvedConfig {
    pub api_secret: Option<String>,
    pub base_url: String,
    pub ws_url: String,
}

impl std::fmt::Debug for ResolvedConfig {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ResolvedConfig")
            .field(
                "api_secret",
                &self.api_secret.as_ref().map(|_| "[REDACTED]"),
            )
            .field("base_url", &self.base_url)
            .field("ws_url", &self.ws_url)
            .finish()
    }
}

pub fn config_path() -> Result<PathBuf> {
    dirs::config_dir()
        .map(|dir| dir.join("backpack").join("config.toml"))
        .ok_or_else(|| anyhow::anyhow!("Could not determine config directory"))
}

pub fn load_config(profile_name: &str) -> Result<ResolvedConfig> {
    let path = config_path()?;

    let file_profile = if path.exists() {
        warn_if_permissive(&path);
        let content = fs::read_to_string(&path)
            .with_context(|| format!("Failed to read config file: {}", path.display()))?;
        let config: ConfigFile =
            toml::from_str(&content).with_context(|| "Failed to parse config file")?;
        config
            .profiles
            .get(profile_name)
            .cloned()
            .or_else(|| {
                // If the requested profile doesn't exist, fall back to the first
                // profile (sorted by name) that has an API secret configured. This
                // avoids the confusing case where `bpx setup` creates a named profile
                // but the user runs commands without `--profile`.
                let mut sorted: Vec<_> = config.profiles.iter().collect();
                sorted.sort_by_key(|(name, _)| (*name).clone());
                sorted
                    .into_iter()
                    .find(|(_, p)| p.api_secret.is_some())
                    .map(|(_, p)| p.clone())
            })
            .unwrap_or_default()
    } else {
        Profile::default()
    };

    Ok(ResolvedConfig {
        api_secret: file_profile.api_secret,
        base_url: file_profile
            .base_url
            .unwrap_or_else(|| DEFAULT_BASE_URL.to_string()),
        ws_url: file_profile
            .ws_url
            .unwrap_or_else(|| DEFAULT_WS_URL.to_string()),
    })
}

/// Check if a config file exists (for first-run detection)
pub fn config_exists() -> bool {
    config_path().map(|p| p.exists()).unwrap_or(false)
}

/// List all profile names in the config file
pub fn list_profiles() -> Result<Vec<String>> {
    let path = config_path()?;
    if !path.exists() {
        return Ok(vec![]);
    }
    let content = fs::read_to_string(&path)
        .with_context(|| format!("Failed to read config file: {}", path.display()))?;
    let config: ConfigFile =
        toml::from_str(&content).with_context(|| "Failed to parse config file")?;
    let mut names: Vec<String> = config.profiles.keys().cloned().collect();
    names.sort();
    Ok(names)
}

pub fn save_config(profile_name: &str, profile: &Profile) -> Result<()> {
    let path = config_path()?;
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }

    let mut config = if path.exists() {
        let content = fs::read_to_string(&path)?;
        toml::from_str::<ConfigFile>(&content)
            .with_context(|| "Failed to parse existing config file")?
    } else {
        ConfigFile::default()
    };

    config
        .profiles
        .insert(profile_name.to_string(), profile.clone());

    let content = toml::to_string_pretty(&config)?;

    // Atomic write: write to a temp file then rename, so a crash can't corrupt config.
    let tmp_path = path.with_extension("toml.tmp");
    #[cfg(unix)]
    {
        use std::io::Write;
        use std::os::unix::fs::OpenOptionsExt;
        let mut file = fs::OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .mode(0o600)
            .open(&tmp_path)?;
        file.write_all(content.as_bytes())?;
        file.sync_all()?;
    }
    #[cfg(not(unix))]
    {
        fs::write(&tmp_path, &content)?;
    }
    fs::rename(&tmp_path, &path).with_context(|| "Failed to save config file")?;

    Ok(())
}

/// Warn to stderr if the config file has group/other-readable permissions on Unix.
#[cfg(unix)]
fn warn_if_permissive(path: &std::path::Path) {
    use std::os::unix::fs::MetadataExt;
    if let Ok(meta) = fs::metadata(path) {
        let mode = meta.mode();
        // Warn if group or other have any access (should be 0o600)
        if mode & 0o077 != 0 {
            eprintln!(
                "warning: {} has permissions {:o}, expected 600. Run: chmod 600 {}",
                path.display(),
                mode & 0o777,
                path.display(),
            );
        }
    }
}

#[cfg(not(unix))]
fn warn_if_permissive(_path: &std::path::Path) {}

/// Save keybinding overrides to the `[keybindings]` section of config.toml.
/// Merges with existing config, preserving profiles.
pub fn save_keybindings(bindings: &HashMap<String, String>) -> Result<()> {
    let path = config_path()?;
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }

    let mut config = if path.exists() {
        let content = fs::read_to_string(&path)?;
        toml::from_str::<ConfigFile>(&content)
            .with_context(|| "Failed to parse existing config file")?
    } else {
        ConfigFile::default()
    };

    if bindings.is_empty() {
        config.keybindings = None;
    } else {
        config.keybindings = Some(bindings.clone());
    }

    let content = toml::to_string_pretty(&config)?;

    let tmp_path = path.with_extension("toml.tmp");
    #[cfg(unix)]
    {
        use std::io::Write;
        use std::os::unix::fs::OpenOptionsExt;
        let mut file = fs::OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .mode(0o600)
            .open(&tmp_path)?;
        file.write_all(content.as_bytes())?;
        file.sync_all()?;
    }
    #[cfg(not(unix))]
    {
        fs::write(&tmp_path, &content)?;
    }
    fs::rename(&tmp_path, &path).with_context(|| "Failed to save config file")?;

    Ok(())
}

/// Load all strategies from the config file.
pub fn load_strategies() -> Result<HashMap<String, StrategyConfig>> {
    let path = config_path()?;
    if !path.exists() {
        return Ok(HashMap::new());
    }
    let content = fs::read_to_string(&path)
        .with_context(|| format!("Failed to read config file: {}", path.display()))?;
    let config: ConfigFile =
        toml::from_str(&content).with_context(|| "Failed to parse config file")?;
    Ok(config.strategies.unwrap_or_default())
}

/// Save a strategy to the config file (creates or updates).
pub fn save_strategy(name: &str, strategy: &StrategyConfig) -> Result<()> {
    let path = config_path()?;
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }

    let mut config = if path.exists() {
        let content = fs::read_to_string(&path)?;
        toml::from_str::<ConfigFile>(&content)
            .with_context(|| "Failed to parse existing config file")?
    } else {
        ConfigFile::default()
    };

    let strategies = config.strategies.get_or_insert_with(HashMap::new);
    strategies.insert(name.to_string(), strategy.clone());

    let content = toml::to_string_pretty(&config)?;

    let tmp_path = path.with_extension("toml.tmp");
    #[cfg(unix)]
    {
        use std::io::Write;
        use std::os::unix::fs::OpenOptionsExt;
        let mut file = fs::OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .mode(0o600)
            .open(&tmp_path)?;
        file.write_all(content.as_bytes())?;
        file.sync_all()?;
    }
    #[cfg(not(unix))]
    {
        fs::write(&tmp_path, &content)?;
    }
    fs::rename(&tmp_path, &path).with_context(|| "Failed to save config file")?;
    Ok(())
}

/// Delete a strategy from the config file. Returns true if it existed.
pub fn delete_strategy(name: &str) -> Result<bool> {
    let path = config_path()?;
    if !path.exists() {
        return Ok(false);
    }

    let content = fs::read_to_string(&path)?;
    let mut config: ConfigFile =
        toml::from_str(&content).with_context(|| "Failed to parse existing config file")?;

    let removed = config
        .strategies
        .as_mut()
        .map(|s| s.remove(name).is_some())
        .unwrap_or(false);

    if removed {
        let content = toml::to_string_pretty(&config)?;
        let tmp_path = path.with_extension("toml.tmp");
        #[cfg(unix)]
        {
            use std::io::Write;
            use std::os::unix::fs::OpenOptionsExt;
            let mut file = fs::OpenOptions::new()
                .write(true)
                .create(true)
                .truncate(true)
                .mode(0o600)
                .open(&tmp_path)?;
            file.write_all(content.as_bytes())?;
            file.sync_all()?;
        }
        #[cfg(not(unix))]
        {
            fs::write(&tmp_path, &content)?;
        }
        fs::rename(&tmp_path, &path).with_context(|| "Failed to save config file")?;
    }

    Ok(removed)
}

/// Load keybinding overrides from the `[keybindings]` section of config.toml.
/// Returns an empty map if no keybindings are configured or on any error.
pub fn load_keybindings() -> HashMap<String, String> {
    let path = match config_path() {
        Ok(p) => p,
        Err(_) => return HashMap::new(),
    };
    if !path.exists() {
        return HashMap::new();
    }
    let content = match fs::read_to_string(&path) {
        Ok(c) => c,
        Err(_) => return HashMap::new(),
    };
    let config: ConfigFile = match toml::from_str(&content) {
        Ok(c) => c,
        Err(_) => return HashMap::new(),
    };
    config.keybindings.unwrap_or_default()
}
