#![allow(dead_code)]
use serde::Deserialize;
use ratatui::style::Color;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use tracing::warn;

/// Theme configuration with color definitions.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ThemeConfig {
    pub name: String,
    // Base text
    pub fg: String,
    pub bg: String,
    pub muted: String,
    pub accent: String,
    // UI colors
    pub border: String,
    pub border_focused: String,
    pub selection_bg: String,
    pub selection_fg: String,
    // Diff colors
    pub diff_add: String,
    pub diff_remove: String,
    pub diff_context: String,
    pub diff_hunk: String,
    // Status colors
    pub staged: String,
    pub unstaged: String,
    pub untracked: String,
    // General
    pub header: String,
    pub footer: String,
    pub error: String,
    pub warning: String,
    pub success: String,
}

/// Partial theme used for user overrides; every field is optional so we only overwrite
/// what the user explicitly set in `[colors]`.
#[derive(Debug, Clone, Deserialize, Default, serde::Serialize)]
pub struct PartialThemeConfig {
    pub fg: Option<String>,
    pub bg: Option<String>,
    pub muted: Option<String>,
    pub accent: Option<String>,
    pub border: Option<String>,
    pub border_focused: Option<String>,
    pub selection_bg: Option<String>,
    pub selection_fg: Option<String>,
    pub diff_add: Option<String>,
    pub diff_remove: Option<String>,
    pub diff_context: Option<String>,
    pub diff_hunk: Option<String>,
    pub staged: Option<String>,
    pub unstaged: Option<String>,
    pub untracked: Option<String>,
    pub header: Option<String>,
    pub footer: Option<String>,
    pub error: Option<String>,
    pub warning: Option<String>,
    pub success: Option<String>,
}

impl Default for ThemeConfig {
    fn default() -> Self {
        Self {
            name: "dark".to_string(),
            fg: "white".to_string(),
            bg: "black".to_string(),
            muted: "darkgray".to_string(),
            accent: "cyan".to_string(),
            border: "gray".to_string(),
            border_focused: "cyan".to_string(),
            selection_bg: "white".to_string(),
            selection_fg: "black".to_string(),
            diff_add: "green".to_string(),
            diff_remove: "red".to_string(),
            diff_context: "gray".to_string(),
            diff_hunk: "cyan".to_string(),
            staged: "green".to_string(),
            unstaged: "yellow".to_string(),
            untracked: "gray".to_string(),
            header: "blue".to_string(),
            footer: "gray".to_string(),
            error: "red".to_string(),
            warning: "yellow".to_string(),
            success: "green".to_string(),
        }
    }
}

impl ThemeConfig {
    pub fn builtins() -> &'static [&'static str] {
        &["dark", "light", "nord", "dracula", "gruvbox", "everforest", "onedark"]
    }
    /// Parse a color string into a ratatui Color.
    pub fn parse_color(s: &str) -> Color {
        match s.to_lowercase().as_str() {
            "black" => Color::Black,
            "red" => Color::Red,
            "green" => Color::Green,
            "yellow" => Color::Yellow,
            "blue" => Color::Blue,
            "magenta" => Color::Magenta,
            "cyan" => Color::Cyan,
            "gray" | "grey" => Color::Gray,
            "darkgray" | "darkgrey" => Color::DarkGray,
            "lightred" => Color::LightRed,
            "lightgreen" => Color::LightGreen,
            "lightyellow" => Color::LightYellow,
            "lightblue" => Color::LightBlue,
            "lightmagenta" => Color::LightMagenta,
            "lightcyan" => Color::LightCyan,
            "white" => Color::White,
            _ => {
                // Try to parse as hex #RRGGBB
                if s.starts_with('#') && s.len() == 7 {
                    if let (Ok(r), Ok(g), Ok(b)) = (
                        u8::from_str_radix(&s[1..3], 16),
                        u8::from_str_radix(&s[3..5], 16),
                        u8::from_str_radix(&s[5..7], 16),
                    ) {
                        return Color::Rgb(r, g, b);
                    }
                }
                Color::Reset
            }
        }
    }

    pub fn is_valid_color_str(s: &str) -> bool {
        matches!(
            s.to_lowercase().as_str(),
            "black"
                | "red"
                | "green"
                | "yellow"
                | "blue"
                | "magenta"
                | "cyan"
                | "gray"
                | "grey"
                | "darkgray"
                | "darkgrey"
                | "lightred"
                | "lightgreen"
                | "lightyellow"
                | "lightblue"
                | "lightmagenta"
                | "lightcyan"
                | "white"
        ) || (s.starts_with('#')
            && s.len() == 7
            && u8::from_str_radix(&s[1..3], 16).is_ok()
            && u8::from_str_radix(&s[3..5], 16).is_ok()
            && u8::from_str_radix(&s[5..7], 16).is_ok())
    }

    pub fn border_color(&self) -> Color {
        Self::parse_color(&self.border)
    }

    pub fn fg_color(&self) -> Color {
        Self::parse_color(&self.fg)
    }

    pub fn bg_color(&self) -> Color {
        Self::parse_color(&self.bg)
    }

    pub fn muted_color(&self) -> Color {
        Self::parse_color(&self.muted)
    }

    pub fn accent_color(&self) -> Color {
        Self::parse_color(&self.accent)
    }

    pub fn border_focused_color(&self) -> Color {
        Self::parse_color(&self.border_focused)
    }

    pub fn selection_bg_color(&self) -> Color {
        Self::parse_color(&self.selection_bg)
    }

    pub fn selection_fg_color(&self) -> Color {
        Self::parse_color(&self.selection_fg)
    }

    pub fn diff_add_color(&self) -> Color {
        Self::parse_color(&self.diff_add)
    }

    pub fn diff_remove_color(&self) -> Color {
        Self::parse_color(&self.diff_remove)
    }

    pub fn diff_hunk_color(&self) -> Color {
        Self::parse_color(&self.diff_hunk)
    }

    pub fn error_color(&self) -> Color {
        Self::parse_color(&self.error)
    }

    pub fn success_color(&self) -> Color {
        Self::parse_color(&self.success)
    }

    pub fn diff_context_color(&self) -> Color {
        Self::parse_color(&self.diff_context)
    }

    pub fn warning_color(&self) -> Color {
        Self::parse_color(&self.warning)
    }

    pub fn footer_color(&self) -> Color {
        Self::parse_color(&self.footer)
    }

    pub fn header_color(&self) -> Color {
        Self::parse_color(&self.header)
    }

    pub fn staged_color(&self) -> Color {
        Self::parse_color(&self.staged)
    }

    pub fn unstaged_color(&self) -> Color {
        Self::parse_color(&self.unstaged)
    }

    pub fn untracked_color(&self) -> Color {
        Self::parse_color(&self.untracked)
    }

    /// Load a named theme or return default.
    pub fn load_named(name: &str) -> Self {
        match name {
            "light" => Self {
                name: "light".to_string(),
                fg: "#2E3440".to_string(),
                bg: "#ECEFF4".to_string(),
                muted: "#4C566A".to_string(),
                accent: "blue".to_string(),
                border: "darkgray".to_string(),
                border_focused: "blue".to_string(),
                selection_bg: "blue".to_string(),
                selection_fg: "white".to_string(),
                diff_add: "green".to_string(),
                diff_remove: "red".to_string(),
                diff_context: "darkgray".to_string(),
                diff_hunk: "blue".to_string(),
                staged: "green".to_string(),
                unstaged: "yellow".to_string(),
                untracked: "darkgray".to_string(),
                header: "blue".to_string(),
                footer: "darkgray".to_string(),
                error: "red".to_string(),
                warning: "yellow".to_string(),
                success: "green".to_string(),
            },
            "nord" => Self {
                name: "nord".to_string(),
                fg: "#ECEFF4".to_string(),
                bg: "#2E3440".to_string(),
                muted: "#D8DEE9".to_string(),
                accent: "#88C0D0".to_string(),
                border: "#4C566A".to_string(),
                border_focused: "#88C0D0".to_string(),
                selection_bg: "#3B4252".to_string(),
                selection_fg: "#ECEFF4".to_string(),
                diff_add: "#A3BE8C".to_string(),
                diff_remove: "#BF616A".to_string(),
                diff_context: "#4C566A".to_string(),
                diff_hunk: "#81A1C1".to_string(),
                staged: "#A3BE8C".to_string(),
                unstaged: "#EBCB8B".to_string(),
                untracked: "#4C566A".to_string(),
                header: "#5E81AC".to_string(),
                footer: "#4C566A".to_string(),
                error: "#BF616A".to_string(),
                warning: "#EBCB8B".to_string(),
                success: "#A3BE8C".to_string(),
            },
            "dracula" => Self {
                name: "dracula".to_string(),
                fg: "#F8F8F2".to_string(),
                bg: "#282A36".to_string(),
                muted: "#6272A4".to_string(),
                accent: "#BD93F9".to_string(),
                border: "#6272A4".to_string(),
                border_focused: "#BD93F9".to_string(),
                selection_bg: "#44475A".to_string(),
                selection_fg: "#F8F8F2".to_string(),
                diff_add: "#50FA7B".to_string(),
                diff_remove: "#FF5555".to_string(),
                diff_context: "#6272A4".to_string(),
                diff_hunk: "#8BE9FD".to_string(),
                staged: "#50FA7B".to_string(),
                unstaged: "#FFB86C".to_string(),
                untracked: "#6272A4".to_string(),
                header: "#BD93F9".to_string(),
                footer: "#6272A4".to_string(),
                error: "#FF5555".to_string(),
                warning: "#FFB86C".to_string(),
                success: "#50FA7B".to_string(),
            },
            "gruvbox" => Self {
                name: "gruvbox".to_string(),
                fg: "#ebdbb2".to_string(),
                bg: "#282828".to_string(),
                muted: "#928374".to_string(),
                accent: "#fe8019".to_string(),
                border: "#665c54".to_string(),
                border_focused: "#fe8019".to_string(),
                selection_bg: "#3c3836".to_string(),
                selection_fg: "#fbf1c7".to_string(),
                diff_add: "#b8bb26".to_string(),
                diff_remove: "#fb4934".to_string(),
                diff_context: "#928374".to_string(),
                diff_hunk: "#83a598".to_string(),
                staged: "#b8bb26".to_string(),
                unstaged: "#fe8019".to_string(),
                untracked: "#928374".to_string(),
                header: "#d3869b".to_string(),
                footer: "#928374".to_string(),
                error: "#fb4934".to_string(),
                warning: "#fabd2f".to_string(),
                success: "#b8bb26".to_string(),
            },
            "everforest" => Self {
                name: "everforest".to_string(),
                fg: "#d3c6aa".to_string(),
                bg: "#2b3339".to_string(),
                muted: "#859289".to_string(),
                accent: "#a7c080".to_string(),
                border: "#4b565c".to_string(),
                border_focused: "#a7c080".to_string(),
                selection_bg: "#3b444b".to_string(),
                selection_fg: "#d3c6aa".to_string(),
                diff_add: "#a7c080".to_string(),
                diff_remove: "#e67e80".to_string(),
                diff_context: "#4b565c".to_string(),
                diff_hunk: "#7fbbb3".to_string(),
                staged: "#a7c080".to_string(),
                unstaged: "#e69875".to_string(),
                untracked: "#859289".to_string(),
                header: "#7fbbb3".to_string(),
                footer: "#4b565c".to_string(),
                error: "#e67e80".to_string(),
                warning: "#e69875".to_string(),
                success: "#a7c080".to_string(),
            },
            "onedark" => Self {
                name: "onedark".to_string(),
                fg: "#abb2bf".to_string(),
                bg: "#282c34".to_string(),
                muted: "#5c6370".to_string(),
                accent: "#61afef".to_string(),
                border: "#4b5263".to_string(),
                border_focused: "#61afef".to_string(),
                selection_bg: "#2c323c".to_string(),
                selection_fg: "#abb2bf".to_string(),
                diff_add: "#98c379".to_string(),
                diff_remove: "#e06c75".to_string(),
                diff_context: "#5c6370".to_string(),
                diff_hunk: "#61afef".to_string(),
                staged: "#98c379".to_string(),
                unstaged: "#e5c07b".to_string(),
                untracked: "#5c6370".to_string(),
                header: "#61afef".to_string(),
                footer: "#4b5263".to_string(),
                error: "#e06c75".to_string(),
                warning: "#e5c07b".to_string(),
                success: "#98c379".to_string(),
            },
            _ => Self::default(),
        }
    }

    /// Merge user overrides from `[colors]` into an existing theme without clobbering
    /// unspecified fields.
    pub fn merge_partial(&mut self, other: PartialThemeConfig) {
        if let Some(v) = other.fg { self.fg = v; }
        if let Some(v) = other.bg { self.bg = v; }
        if let Some(v) = other.muted { self.muted = v; }
        if let Some(v) = other.accent { self.accent = v; }
        if let Some(v) = other.border { self.border = v; }
        if let Some(v) = other.border_focused { self.border_focused = v; }
        if let Some(v) = other.selection_bg { self.selection_bg = v; }
        if let Some(v) = other.selection_fg { self.selection_fg = v; }
        if let Some(v) = other.diff_add { self.diff_add = v; }
        if let Some(v) = other.diff_remove { self.diff_remove = v; }
        if let Some(v) = other.diff_context { self.diff_context = v; }
        if let Some(v) = other.diff_hunk { self.diff_hunk = v; }
        if let Some(v) = other.staged { self.staged = v; }
        if let Some(v) = other.unstaged { self.unstaged = v; }
        if let Some(v) = other.untracked { self.untracked = v; }
        if let Some(v) = other.header { self.header = v; }
        if let Some(v) = other.footer { self.footer = v; }
        if let Some(v) = other.error { self.error = v; }
        if let Some(v) = other.warning { self.warning = v; }
        if let Some(v) = other.success { self.success = v; }
    }

    /// Load an external TOML theme file with optional `[extend] base = "<name>"`
    /// and optional `[ui]` mapping of role -> palette key.
    pub fn load_external(path: &Path, name: &str) -> Option<Self> {
        let raw = fs::read_to_string(path).ok()?;
        let parsed: ExternalTheme = match toml::from_str(&raw) {
            Ok(v) => v,
            Err(e) => {
                warn!("theme {} parse error: {}", name, e);
                return None;
            }
        };

        let base = if let Some(extend) = parsed.extend {
            Self::load_named(&extend.base)
        } else {
            Self::default()
        };

        let palette = parsed.palette;
        if palette.is_empty() {
            warn!("theme {} has empty palette", name);
        }
        let ui_map = parsed.ui;

        let mut cfg = base.clone();
        for role in ROLES {
            let palette_key = ui_map.get(*role).cloned().unwrap_or_else(|| role.to_string());
            if let Some(val) = palette.get(&palette_key) {
                if Self::is_valid_color_str(val) {
                    cfg.set_role(role, val.clone());
                } else {
                    warn!("theme {} invalid color '{}' for {}", name, val, role);
                }
            }
        }
        Some(cfg)
    }

    fn set_role(&mut self, role: &str, value: String) {
        match role {
            "fg" => self.fg = value,
            "bg" => self.bg = value,
            "muted" => self.muted = value,
            "accent" => self.accent = value,
            "border" => self.border = value,
            "border_focused" => self.border_focused = value,
            "selection_bg" => self.selection_bg = value,
            "selection_fg" => self.selection_fg = value,
            "diff_add" => self.diff_add = value,
            "diff_remove" => self.diff_remove = value,
            "diff_context" => self.diff_context = value,
            "diff_hunk" => self.diff_hunk = value,
            "staged" => self.staged = value,
            "unstaged" => self.unstaged = value,
            "untracked" => self.untracked = value,
            "header" => self.header = value,
            "footer" => self.footer = value,
            "error" => self.error = value,
            "warning" => self.warning = value,
            "success" => self.success = value,
            _ => {}
        }
    }
}

const ROLES: &[&str] = &[
    "fg",
    "bg",
    "muted",
    "accent",
    "border",
    "border_focused",
    "selection_bg",
    "selection_fg",
    "diff_add",
    "diff_remove",
    "diff_context",
    "diff_hunk",
    "staged",
    "unstaged",
    "untracked",
    "header",
    "footer",
    "error",
    "warning",
    "success",
];

#[derive(Debug, Clone, Deserialize, Default)]
struct ExternalTheme {
    #[serde(default)]
    extend: Option<ExtendBlock>,
    #[serde(default)]
    palette: HashMap<String, String>,
    #[serde(default)]
    ui: HashMap<String, String>,
}

#[derive(Debug, Clone, Deserialize)]
struct ExtendBlock {
    base: String,
}

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

    #[test]
    fn loads_external_nord_theme() {
        let path = Path::new("themes/nord.toml");
        let theme = ThemeConfig::load_external(path, "nord").expect("load nord");
        assert_eq!(theme.selection_bg, "#3B4252");
        assert_eq!(theme.fg, "#ECEFF4");
        assert_eq!(theme.bg, "#2E3440");
    }

    #[test]
    fn skips_invalid_color_and_keeps_base() {
        let tmp = std::env::temp_dir().join("eazygit_invalid_theme.toml");
        let content = r##"
[extend]
base = "nord"

[palette]
selection_bg = "#GGGGGG"  # invalid
header = "#123456"
"##;
        fs::write(&tmp, content).expect("write temp theme");
        let theme = ThemeConfig::load_external(&tmp, "temp").expect("load temp");
        // Invalid selection_bg should be skipped, so stays as base Nord value.
        assert_eq!(theme.selection_bg, "#3B4252");
        // Valid header should apply.
        assert_eq!(theme.header, "#123456");
        let _ = fs::remove_file(&tmp);
    }

    #[test]
    fn validates_bundled_themes() {
        let dir = Path::new("themes");
        if !dir.exists() {
            return;
        }
        for entry in fs::read_dir(dir).expect("read themes dir") {
            let entry = entry.expect("dir entry");
            if entry.file_type().map(|t| t.is_file()).unwrap_or(false) {
                let path = entry.path();
                if let Some(ext) = path.extension() {
                    if ext == "toml" {
                        let name = path.file_stem().and_then(|s| s.to_str()).unwrap_or("unknown");
                        let raw = fs::read_to_string(&path).expect("read theme file");
                        let parsed: ExternalTheme = toml::from_str(&raw).expect("parse theme");
                        for (k, v) in parsed.palette.iter() {
                            assert!(
                                ThemeConfig::is_valid_color_str(v),
                                "theme {} key {} invalid color {}",
                                name,
                                k,
                                v
                            );
                        }
                    }
                }
            }
        }
    }

    #[test]
    fn everforest_builtin_loads() {
        let theme = ThemeConfig::load_named("everforest");
        assert_eq!(theme.name, "everforest");
        assert_eq!(theme.bg, "#2b3339");
        assert_eq!(theme.fg, "#d3c6aa");
    }
}
