use crate::registry::{self, OptionDef, REGISTRY};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct KeyBinding {
    pub key: String,
    #[serde(default)]
    pub flags: String,
    pub command: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PluginEntry {
    pub name: String,
    #[serde(default = "default_true")]
    pub enabled: bool,
}

fn default_true() -> bool {
    true
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FeatureFlags {
    #[serde(default = "default_true")]
    pub vim_navigation: bool,
    #[serde(default = "default_true")]
    pub aggressive_resize: bool,
    #[serde(default = "default_true")]
    pub automatic_rename: bool,
    #[serde(default)]
    pub system_monitor: bool,
    #[serde(default = "default_true")]
    pub rebind_prefix: bool,
    #[serde(default = "default_true")]
    pub reload_config_key: bool,
}

impl Default for FeatureFlags {
    fn default() -> Self {
        Self {
            vim_navigation: true,
            aggressive_resize: true,
            automatic_rename: true,
            system_monitor: false,
            rebind_prefix: true,
            reload_config_key: true,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WorkspacePane {
    #[serde(default)]
    pub commands: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WorkspaceWindow {
    pub name: String,
    #[serde(default)]
    pub layout: Option<String>,
    #[serde(default)]
    pub panes: Vec<WorkspacePane>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct WorkspaceSession {
    pub name: String,
    #[serde(default)]
    pub windows: Vec<WorkspaceWindow>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Workspace {
    pub name: String,
    #[serde(default)]
    pub sessions: Vec<WorkspaceSession>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CommandSequence {
    pub name: String,
    #[serde(default)]
    pub commands: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TmuxConfig {
    #[serde(default)]
    pub options: BTreeMap<String, String>,
    #[serde(default)]
    pub bindings: Vec<KeyBinding>,
    #[serde(default)]
    pub plugins: Vec<PluginEntry>,
    #[serde(default)]
    pub features: FeatureFlags,
    #[serde(default)]
    pub extra_lines: Vec<String>,
    #[serde(default)]
    pub workspaces: Vec<Workspace>,
    #[serde(default)]
    pub command_sequences: Vec<CommandSequence>,
}

impl Default for TmuxConfig {
    fn default() -> Self {
        let mut options = BTreeMap::new();
        for def in REGISTRY {
            options.insert(def.name.to_string(), def.default.to_string());
        }
        // Sensible starter theme (Tokyo Night inspired)
        options.insert("prefix".into(), "C-a".into());
        options.insert("base-index".into(), "1".into());
        options.insert("pane-base-index".into(), "1".into());
        options.insert("escape-time".into(), "0".into());
        options.insert("focus-events".into(), "on".into());
        options.insert("mouse".into(), "on".into());
        options.insert("status-position".into(), "top".into());
        options.insert(
            "status-style".into(),
            "bg=#1a1b26 fg=#c0caf5".into(),
        );
        options.insert(
            "window-status-current-style".into(),
            "bg=#7aa2f7 fg=#1a1b26".into(),
        );
        options.insert(
            "window-status-style".into(),
            "bg=#24283b fg=#565f89".into(),
        );

        Self {
            options,
            bindings: Vec::new(),
            plugins: vec![
                PluginEntry {
                    name: "tmux-plugins/tpm".into(),
                    enabled: true,
                },
                PluginEntry {
                    name: "tmux-plugins/tmux-resurrect".into(),
                    enabled: true,
                },
                PluginEntry {
                    name: "tmux-plugins/tmux-continuum".into(),
                    enabled: true,
                },
                PluginEntry {
                    name: "tmux-plugins/tmux-yank".into(),
                    enabled: true,
                },
                PluginEntry {
                    name: "catppuccin/tmux".into(),
                    enabled: true,
                },
            ],
            features: FeatureFlags::default(),
            extra_lines: Vec::new(),
            workspaces: Vec::new(),
            command_sequences: Vec::new(),
        }
    }
}

impl TmuxConfig {
    pub fn load_or_create<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path_ref = path.as_ref();
        if !path_ref.exists() {
            let config = Self::default();
            config.save_to_file(path_ref)?;
            return Ok(config);
        }

        let content = fs::read_to_string(path_ref).context("Failed to read config file")?;
        if let Ok(config) = toml::from_str::<TmuxConfig>(&content) {
            return Ok(config.merge_defaults());
        }

        // Migrate legacy tumix format
        if let Ok(legacy) = toml::from_str::<LegacyConfig>(&content) {
            let migrated = legacy.into_modern();
            migrated.save_to_file(path_ref)?;
            return Ok(migrated);
        }

        Err(anyhow::anyhow!("Failed to parse config TOML"))
    }

    pub fn merge_defaults(self) -> Self {
        let mut defaults = Self::default();
        for (k, v) in self.options {
            defaults.options.insert(k, v);
        }
        if !self.bindings.is_empty() {
            defaults.bindings = self.bindings;
        }
        if !self.plugins.is_empty() {
            defaults.plugins = self.plugins;
        }
        defaults.features = self.features;
        if !self.extra_lines.is_empty() {
            defaults.extra_lines = self.extra_lines;
        }
        if !self.workspaces.is_empty() {
            defaults.workspaces = self.workspaces;
        }
        if !self.command_sequences.is_empty() {
            defaults.command_sequences = self.command_sequences;
        }
        defaults
    }

    pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let toml_string = toml::to_string_pretty(self).context("Failed to serialize config")?;
        fs::write(path, toml_string).context("Failed to write config file")?;
        Ok(())
    }

    pub fn get_option(&self, name: &str) -> String {
        self.options
            .get(name)
            .cloned()
            .unwrap_or_else(|| {
                registry::find_option(name)
                    .map(|d| d.default.to_string())
                    .unwrap_or_default()
            })
    }

    pub fn set_option(&mut self, name: &str, value: String) {
        self.options.insert(name.to_string(), value);
    }

    pub fn option_entries(&self) -> Vec<OptionEntry<'_>> {
        let mut entries: Vec<OptionEntry<'_>> = REGISTRY
            .iter()
            .map(|def| OptionEntry::Known {
                def,
                value: self.get_option(def.name),
            })
            .collect();

        for (name, value) in &self.options {
            if registry::find_option(name).is_none() {
                entries.push(OptionEntry::Custom {
                    name: name.as_str(),
                    value: value.clone(),
                });
            }
        }
        entries.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
        entries
    }

    pub fn entries_in_category<'a>(
        entries: &'a [OptionEntry<'_>],
        category: &str,
    ) -> Vec<&'a OptionEntry<'a>> {
        entries
            .iter()
            .filter(|e| e.category() == category)
            .collect()
    }
}

#[derive(Debug, Clone)]
pub enum OptionEntry<'a> {
    Known {
        def: &'a OptionDef,
        value: String,
    },
    Custom {
        name: &'a str,
        value: String,
    },
}

impl OptionEntry<'_> {
    pub fn name(&self) -> &str {
        match self {
            OptionEntry::Known { def, .. } => def.name,
            OptionEntry::Custom { name, .. } => name,
        }
    }

    pub fn value(&self) -> &str {
        match self {
            OptionEntry::Known { value, .. } | OptionEntry::Custom { value, .. } => value,
        }
    }

    pub fn category(&self) -> &str {
        match self {
            OptionEntry::Known { def, .. } => def.category,
            OptionEntry::Custom { .. } => "Custom",
        }
    }

    pub fn description(&self) -> &str {
        match self {
            OptionEntry::Known { def, .. } => def.description,
            OptionEntry::Custom { .. } => "User-defined tmux option",
        }
    }

    pub fn kind(&self) -> registry::ValueKind {
        match self {
            OptionEntry::Known { def, .. } => def.kind,
            OptionEntry::Custom { .. } => registry::ValueKind::Text,
        }
    }

    pub fn choices(&self) -> &'static [&'static str] {
        match self {
            OptionEntry::Known { def, .. } => def.choices,
            OptionEntry::Custom { .. } => &[],
        }
    }

    pub fn sort_key(&self) -> (&str, &str) {
        (self.category(), self.name())
    }
}

// Legacy tumix 0.1 config format
#[derive(Deserialize)]
struct LegacyConfig {
    general: LegacyGeneral,
    appearance: LegacyAppearance,
    modules: LegacyModules,
    plugins: LegacyPlugins,
}

#[derive(Deserialize)]
struct LegacyGeneral {
    prefix: String,
    base_index: u32,
    pane_base_index: u32,
    escape_time: u32,
    focus_events: String,
    mouse: String,
}

#[derive(Deserialize)]
struct LegacyAppearance {
    status_position: String,
    status_bg: String,
    status_fg: String,
    window_active_bg: String,
    window_active_fg: String,
    window_inactive_bg: String,
    window_inactive_fg: String,
}

#[derive(Deserialize)]
struct LegacyModules {
    vim_navigation: bool,
    aggressive_resize: bool,
    automatic_rename: bool,
    system_monitor: bool,
}

#[derive(Deserialize)]
struct LegacyPlugins {
    tpm: bool,
    tmux_resurrect: bool,
    tmux_continuum: bool,
    tmux_yank: bool,
    catppuccin: bool,
}

impl LegacyConfig {
    fn into_modern(self) -> TmuxConfig {
        let mut config = TmuxConfig::default();
        config.set_option("prefix".into(), self.general.prefix);
        config.set_option("base-index".into(), self.general.base_index.to_string());
        config.set_option(
            "pane-base-index".into(),
            self.general.pane_base_index.to_string(),
        );
        config.set_option("escape-time".into(), self.general.escape_time.to_string());
        config.set_option("focus-events".into(), self.general.focus_events);
        config.set_option("mouse".into(), self.general.mouse);
        config.set_option("status-position".into(), self.appearance.status_position);
        config.set_option(
            "status-style".into(),
            format!(
                "bg={} fg={}",
                self.appearance.status_bg, self.appearance.status_fg
            ),
        );
        config.set_option(
            "window-status-current-style".into(),
            format!(
                "bg={} fg={}",
                self.appearance.window_active_bg, self.appearance.window_active_fg
            ),
        );
        config.set_option(
            "window-status-style".into(),
            format!(
                "bg={} fg={}",
                self.appearance.window_inactive_bg, self.appearance.window_inactive_fg
            ),
        );
        config.features.vim_navigation = self.modules.vim_navigation;
        config.features.aggressive_resize = self.modules.aggressive_resize;
        config.features.automatic_rename = self.modules.automatic_rename;
        config.features.system_monitor = self.modules.system_monitor;

        if self.plugins.tpm {
            config.plugins = vec![
                PluginEntry {
                    name: "tmux-plugins/tpm".into(),
                    enabled: true,
                },
                PluginEntry {
                    name: "tmux-plugins/tmux-resurrect".into(),
                    enabled: self.plugins.tmux_resurrect,
                },
                PluginEntry {
                    name: "tmux-plugins/tmux-continuum".into(),
                    enabled: self.plugins.tmux_continuum,
                },
                PluginEntry {
                    name: "tmux-plugins/tmux-yank".into(),
                    enabled: self.plugins.tmux_yank,
                },
                PluginEntry {
                    name: "catppuccin/tmux".into(),
                    enabled: self.plugins.catppuccin,
                },
            ];
        }
        config
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn default_has_all_registry_keys() {
        let config = TmuxConfig::default();
        for def in REGISTRY {
            assert!(config.options.contains_key(def.name));
        }
    }
}
