use color_eyre::eyre::{Result, WrapErr};
use std::path::{Path, PathBuf};

use super::types::{PluginOptions, Vst2Location, YabridgeConfig};
use toml::map::Map;

/// Raw TOML structure for yabridgectl config.toml
#[derive(serde::Deserialize, Debug)]
struct RawConfig {
    yabridge_home: Option<String>,
    #[serde(default)]
    plugin_dirs: Vec<String>,
    vst2_location: Option<String>,
    #[serde(default)]
    no_verify: bool,
    #[serde(default)]
    blacklist: Vec<String>,
}

/// Get the path to the yabridgectl config file
pub fn config_path() -> PathBuf {
    let xdg_config = std::env::var("XDG_CONFIG_HOME")
        .map(PathBuf::from)
        .unwrap_or_else(|_| {
            dirs::home_dir()
                .unwrap_or_else(|| PathBuf::from("/root"))
                .join(".config")
        });
    xdg_config.join("yabridgectl").join("config.toml")
}

/// Parse the yabridgectl config.toml file
pub fn load_config() -> Result<YabridgeConfig> {
    let path = config_path();
    load_config_from(&path)
}

/// Parse a yabridgectl config.toml from a specific path
pub fn load_config_from(path: &Path) -> Result<YabridgeConfig> {
    if !path.exists() {
        return Ok(YabridgeConfig::default());
    }

    let content = std::fs::read_to_string(path)
        .wrap_err_with(|| format!("Failed to read config file: {}", path.display()))?;

    let raw: RawConfig = toml::from_str(&content)
        .wrap_err_with(|| format!("Failed to parse config file: {}", path.display()))?;

    let vst2_location = match raw.vst2_location.as_deref() {
        Some("inline") => Vst2Location::Inline,
        _ => Vst2Location::Centralized,
    };

    Ok(YabridgeConfig {
        yabridge_home: raw.yabridge_home.map(PathBuf::from),
        plugin_dirs: raw.plugin_dirs.into_iter().map(PathBuf::from).collect(),
        vst2_location,
        no_verify: raw.no_verify,
        blacklist: raw.blacklist.into_iter().map(PathBuf::from).collect(),
    })
}

/// Parse yabridge.toml plugin options
pub fn load_plugin_options(path: &Path) -> Result<Vec<PluginOptions>> {
    if !path.exists() {
        return Ok(Vec::new());
    }

    let content = std::fs::read_to_string(path)
        .wrap_err_with(|| format!("Failed to read plugin config: {}", path.display()))?;

    let table: toml::Table = toml::from_str(&content)
        .wrap_err_with(|| format!("Failed to parse plugin config: {}", path.display()))?;

    let mut options = Vec::new();

    use super::types::KNOWN_KEYS;

    for (pattern, value) in &table {
        if let toml::Value::Table(opts) = value {
            // Collect unknown keys for round-trip preservation
            let extra_keys: Vec<(String, toml::Value)> = opts
                .iter()
                .filter(|(k, _)| !KNOWN_KEYS.contains(&k.as_str()))
                .map(|(k, v)| (k.clone(), v.clone()))
                .collect();

            let plugin_opts = PluginOptions {
                pattern: pattern.clone(),
                group: opts.get("group").and_then(|v| v.as_str()).map(String::from),
                disable_pipes: opts.get("disable_pipes").and_then(|v| v.as_bool()),
                editor_coordinate_hack: opts
                    .get("editor_coordinate_hack")
                    .and_then(|v| v.as_bool()),
                editor_disable_host_scaling: opts
                    .get("editor_disable_host_scaling")
                    .and_then(|v| v.as_bool()),
                editor_force_dnd: opts.get("editor_force_dnd").and_then(|v| v.as_bool()),
                editor_xembed: opts.get("editor_xembed").and_then(|v| v.as_bool()),
                frame_rate: opts
                    .get("frame_rate")
                    .and_then(|v| v.as_integer())
                    .and_then(|v| u32::try_from(v).ok()),
                hide_daw: opts.get("hide_daw").and_then(|v| v.as_bool()),
                vst3_prefer_32bit: opts.get("vst3_prefer_32bit").and_then(|v| v.as_bool()),
                extra_keys,
            };
            options.push(plugin_opts);
        }
    }

    Ok(options)
}

/// Serialize plugin options back to yabridge.toml format and write to disk
pub fn save_plugin_options(path: &Path, options: &[PluginOptions]) -> Result<()> {
    let mut root = Map::new();

    for opt in options {
        let mut table = Map::new();

        if let Some(ref group) = opt.group {
            table.insert("group".to_string(), toml::Value::String(group.clone()));
        }
        if let Some(v) = opt.disable_pipes {
            table.insert("disable_pipes".to_string(), toml::Value::Boolean(v));
        }
        if let Some(v) = opt.editor_coordinate_hack {
            table.insert(
                "editor_coordinate_hack".to_string(),
                toml::Value::Boolean(v),
            );
        }
        if let Some(v) = opt.editor_disable_host_scaling {
            table.insert(
                "editor_disable_host_scaling".to_string(),
                toml::Value::Boolean(v),
            );
        }
        if let Some(v) = opt.editor_force_dnd {
            table.insert("editor_force_dnd".to_string(), toml::Value::Boolean(v));
        }
        if let Some(v) = opt.editor_xembed {
            table.insert("editor_xembed".to_string(), toml::Value::Boolean(v));
        }
        if let Some(v) = opt.frame_rate {
            table.insert("frame_rate".to_string(), toml::Value::Integer(v as i64));
        }
        if let Some(v) = opt.hide_daw {
            table.insert("hide_daw".to_string(), toml::Value::Boolean(v));
        }
        if let Some(v) = opt.vst3_prefer_32bit {
            table.insert("vst3_prefer_32bit".to_string(), toml::Value::Boolean(v));
        }

        // Preserve unknown keys for round-trip fidelity
        for (key, value) in &opt.extra_keys {
            table.insert(key.clone(), value.clone());
        }

        root.insert(opt.pattern.clone(), toml::Value::Table(table));
    }

    let content = toml::to_string_pretty(&toml::Value::Table(root))
        .wrap_err("Failed to serialize plugin options")?;

    // Ensure parent directory exists
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)
            .wrap_err_with(|| format!("Failed to create directory: {}", parent.display()))?;
    }

    std::fs::write(path, content)
        .wrap_err_with(|| format!("Failed to write plugin config: {}", path.display()))?;

    Ok(())
}
