use std::collections::HashMap;

use crossterm::event::{KeyCode, KeyModifiers};

/// An action that can be triggered by a keybinding.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Action {
    Quit,
    ToggleHelp,
    SymbolSearch,
    ScrollDown,
    ScrollUp,
    ScrollToTop,
    ScrollToBottom,
    CancelOrder,
    ClosePosition,
    OpenOrderForm,
    ChartIntervalPrev,
    ChartIntervalNext,
    NextPanel,
    PrevPanel,
    JumpPanel(u8),
    CommandBar,
    RunCommand(String),
    ToggleLatency,
    TogglePnl,
    ToggleCollateral,
}

/// Maps key combinations to actions. Supports default bindings and user overrides.
pub struct Keymap {
    bindings: HashMap<(KeyCode, KeyModifiers), Action>,
    /// User overrides (key combo string → action string). Persisted to config.toml.
    user_overrides: HashMap<String, String>,
}

impl Keymap {
    /// Build the default keymap matching all hardcoded bindings.
    pub fn default_map() -> Self {
        let mut bindings = HashMap::new();
        let none = KeyModifiers::NONE;

        // System
        bindings.insert((KeyCode::Char('q'), none), Action::Quit);
        bindings.insert((KeyCode::Char('?'), none), Action::ToggleHelp);
        bindings.insert((KeyCode::Char('/'), none), Action::SymbolSearch);
        bindings.insert((KeyCode::Char(':'), none), Action::CommandBar);

        // Scrolling
        bindings.insert((KeyCode::Char('j'), none), Action::ScrollDown);
        bindings.insert((KeyCode::Down, none), Action::ScrollDown);
        bindings.insert((KeyCode::Char('k'), none), Action::ScrollUp);
        bindings.insert((KeyCode::Up, none), Action::ScrollUp);
        bindings.insert((KeyCode::Char('g'), none), Action::ScrollToTop);
        bindings.insert(
            (KeyCode::Char('G'), KeyModifiers::SHIFT),
            Action::ScrollToBottom,
        );

        // Trading
        bindings.insert((KeyCode::Char('d'), none), Action::CancelOrder);
        bindings.insert((KeyCode::Char('x'), none), Action::ClosePosition);
        bindings.insert((KeyCode::Char('o'), none), Action::OpenOrderForm);

        // Toggles
        bindings.insert(
            (KeyCode::Char('L'), KeyModifiers::SHIFT),
            Action::ToggleLatency,
        );
        bindings.insert((KeyCode::Char('P'), KeyModifiers::SHIFT), Action::TogglePnl);
        bindings.insert(
            (KeyCode::Char('C'), KeyModifiers::SHIFT),
            Action::ToggleCollateral,
        );

        // Chart interval
        bindings.insert((KeyCode::Char('['), none), Action::ChartIntervalPrev);
        bindings.insert((KeyCode::Char(']'), none), Action::ChartIntervalNext);

        // Panel navigation
        bindings.insert((KeyCode::Tab, none), Action::NextPanel);
        bindings.insert((KeyCode::BackTab, KeyModifiers::SHIFT), Action::PrevPanel);
        bindings.insert((KeyCode::BackTab, none), Action::PrevPanel);
        bindings.insert((KeyCode::Char('1'), none), Action::JumpPanel(1));
        bindings.insert((KeyCode::Char('2'), none), Action::JumpPanel(2));
        bindings.insert((KeyCode::Char('3'), none), Action::JumpPanel(3));
        bindings.insert((KeyCode::Char('4'), none), Action::JumpPanel(4));
        bindings.insert((KeyCode::Char('5'), none), Action::JumpPanel(5));
        bindings.insert((KeyCode::Char('6'), none), Action::JumpPanel(6));

        Keymap {
            bindings,
            user_overrides: HashMap::new(),
        }
    }

    /// Build a keymap with user overrides merged on top of defaults.
    pub fn with_overrides(overrides: &HashMap<String, String>) -> Self {
        let mut keymap = Self::default_map();
        for (key_str, action_str) in overrides {
            if let Some((code, mods)) = parse_key_combo(key_str) {
                if let Some(action) = parse_action(action_str) {
                    keymap.bindings.insert((code, mods), action);
                }
            }
        }
        keymap.user_overrides = overrides.clone();
        keymap
    }

    /// Look up the action bound to a key combination.
    pub fn lookup(&self, code: KeyCode, mods: KeyModifiers) -> Option<&Action> {
        self.bindings.get(&(code, mods))
    }

    /// Bind a key combo to an action at runtime. Returns Ok(()) on success.
    pub fn bind(&mut self, key_str: &str, action_str: &str) -> Result<(), String> {
        let (code, mods) =
            parse_key_combo(key_str).ok_or_else(|| format!("Unknown key: {key_str}"))?;
        let action =
            parse_action(action_str).ok_or_else(|| format!("Unknown action: {action_str}"))?;
        self.bindings.insert((code, mods), action);
        self.user_overrides
            .insert(key_str.to_string(), action_str.to_string());
        Ok(())
    }

    /// Remove a key binding. Returns Ok(()) if found, Err if not bound.
    pub fn unbind(&mut self, key_str: &str) -> Result<(), String> {
        let (code, mods) =
            parse_key_combo(key_str).ok_or_else(|| format!("Unknown key: {key_str}"))?;
        self.bindings.remove(&(code, mods));
        self.user_overrides.remove(key_str);
        Ok(())
    }

    /// Remove all user-defined bindings, restoring defaults.
    pub fn unbind_all(&mut self) {
        self.user_overrides.clear();
        *self = Self::default_map();
    }

    /// Get a reference to the current user overrides for persistence.
    pub fn user_overrides(&self) -> &HashMap<String, String> {
        &self.user_overrides
    }

    /// Format all current bindings as a displayable string.
    pub fn list_bindings(&self) -> String {
        let mut lines: Vec<String> = Vec::new();

        // Show user overrides first
        if !self.user_overrides.is_empty() {
            lines.push("Custom bindings:".to_string());
            let mut sorted: Vec<_> = self.user_overrides.iter().collect();
            sorted.sort_by_key(|(k, _)| (*k).clone());
            for (key, action) in &sorted {
                lines.push(format!("  {key:>14}  {action}"));
            }
            lines.push(String::new());
        }

        // Show defaults
        lines.push("Default bindings:".to_string());
        let default_pairs: Vec<(&str, &str)> = vec![
            ("q", "quit"),
            ("?", "toggle_help"),
            ("/", "symbol_search"),
            (":", "command_bar"),
            ("j / Down", "scroll_down"),
            ("k / Up", "scroll_up"),
            ("g", "scroll_to_top"),
            ("G", "scroll_to_bottom"),
            ("d", "cancel_order"),
            ("x", "close_position"),
            ("o", "order_form"),
            ("[", "chart_prev"),
            ("]", "chart_next"),
            ("Tab", "next_panel"),
            ("Shift+Tab", "prev_panel"),
            ("1-6", "panel_1..6"),
            ("P", "toggle_pnl"),
            ("C", "toggle_collateral"),
        ];
        for (key, action) in &default_pairs {
            lines.push(format!("  {key:>14}  {action}"));
        }

        // Show available action names
        lines.push(String::new());
        lines.push("Actions: quit, toggle_help, symbol_search, command_bar,".to_string());
        lines.push("  scroll_down, scroll_up, scroll_to_top, scroll_to_bottom,".to_string());
        lines.push("  cancel_order, close_position, order_form, chart_prev,".to_string());
        lines.push(
            "  chart_next, next_panel, prev_panel, panel_1..5, toggle_pnl, toggle_collateral"
                .to_string(),
        );
        lines.push("  :<command>  (run bpx CLI command)".to_string());

        lines.join("\n")
    }
}

/// Parse a key combo string like "Ctrl+d", "Shift+Tab", "F1", "q" into (KeyCode, KeyModifiers).
pub fn parse_key_combo(s: &str) -> Option<(KeyCode, KeyModifiers)> {
    let parts: Vec<&str> = s.split('+').collect();
    let mut mods = KeyModifiers::NONE;
    let key_part = parts.last()?;

    for part in &parts[..parts.len() - 1] {
        match part.trim().to_lowercase().as_str() {
            "ctrl" => mods |= KeyModifiers::CONTROL,
            "shift" => mods |= KeyModifiers::SHIFT,
            "alt" => mods |= KeyModifiers::ALT,
            _ => return None,
        }
    }

    let code = parse_key_code(key_part.trim())?;

    // Shift+Tab is reported as BackTab by crossterm
    if code == KeyCode::Tab && mods.contains(KeyModifiers::SHIFT) {
        return Some((KeyCode::BackTab, mods));
    }

    Some((code, mods))
}

/// Parse a single key name into a KeyCode.
pub fn parse_key_code(s: &str) -> Option<KeyCode> {
    // Try single character first (case-sensitive)
    if s.len() == 1 {
        let ch = s.chars().next()?;
        return Some(KeyCode::Char(ch));
    }

    match s.to_lowercase().as_str() {
        "tab" => Some(KeyCode::Tab),
        "enter" | "return" => Some(KeyCode::Enter),
        "esc" | "escape" => Some(KeyCode::Esc),
        "backspace" => Some(KeyCode::Backspace),
        "delete" | "del" => Some(KeyCode::Delete),
        "up" => Some(KeyCode::Up),
        "down" => Some(KeyCode::Down),
        "left" => Some(KeyCode::Left),
        "right" => Some(KeyCode::Right),
        "home" => Some(KeyCode::Home),
        "end" => Some(KeyCode::End),
        "pageup" => Some(KeyCode::PageUp),
        "pagedown" => Some(KeyCode::PageDown),
        "space" => Some(KeyCode::Char(' ')),
        "f1" => Some(KeyCode::F(1)),
        "f2" => Some(KeyCode::F(2)),
        "f3" => Some(KeyCode::F(3)),
        "f4" => Some(KeyCode::F(4)),
        "f5" => Some(KeyCode::F(5)),
        "f6" => Some(KeyCode::F(6)),
        "f7" => Some(KeyCode::F(7)),
        "f8" => Some(KeyCode::F(8)),
        "f9" => Some(KeyCode::F(9)),
        "f10" => Some(KeyCode::F(10)),
        "f11" => Some(KeyCode::F(11)),
        "f12" => Some(KeyCode::F(12)),
        _ => None,
    }
}

/// Parse an action name string into an Action. Strings prefixed with ":" become RunCommand.
pub fn parse_action(s: &str) -> Option<Action> {
    if let Some(cmd) = s.strip_prefix(':') {
        return Some(Action::RunCommand(cmd.to_string()));
    }
    match s.to_lowercase().as_str() {
        "quit" => Some(Action::Quit),
        "toggle_help" | "help" => Some(Action::ToggleHelp),
        "toggle_latency" | "latency" => Some(Action::ToggleLatency),
        "symbol_search" | "search" => Some(Action::SymbolSearch),
        "scroll_down" => Some(Action::ScrollDown),
        "scroll_up" => Some(Action::ScrollUp),
        "scroll_to_top" => Some(Action::ScrollToTop),
        "scroll_to_bottom" => Some(Action::ScrollToBottom),
        "cancel_order" => Some(Action::CancelOrder),
        "close_position" => Some(Action::ClosePosition),
        "order_form" | "open_order_form" => Some(Action::OpenOrderForm),
        "chart_interval_prev" | "chart_prev" => Some(Action::ChartIntervalPrev),
        "chart_interval_next" | "chart_next" => Some(Action::ChartIntervalNext),
        "next_panel" => Some(Action::NextPanel),
        "prev_panel" => Some(Action::PrevPanel),
        "toggle_pnl" | "pnl" => Some(Action::TogglePnl),
        "toggle_collateral" | "collateral" => Some(Action::ToggleCollateral),
        "command_bar" | "command" => Some(Action::CommandBar),
        "panel_1" => Some(Action::JumpPanel(1)),
        "panel_2" => Some(Action::JumpPanel(2)),
        "panel_3" => Some(Action::JumpPanel(3)),
        "panel_4" => Some(Action::JumpPanel(4)),
        "panel_5" => Some(Action::JumpPanel(5)),
        "panel_6" => Some(Action::JumpPanel(6)),
        _ => None,
    }
}

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

    #[test]
    fn test_parse_key_combo_simple() {
        assert_eq!(
            parse_key_combo("q"),
            Some((KeyCode::Char('q'), KeyModifiers::NONE))
        );
        assert_eq!(
            parse_key_combo("Tab"),
            Some((KeyCode::Tab, KeyModifiers::NONE))
        );
        assert_eq!(
            parse_key_combo("F1"),
            Some((KeyCode::F(1), KeyModifiers::NONE))
        );
    }

    #[test]
    fn test_parse_key_combo_modifiers() {
        assert_eq!(
            parse_key_combo("Ctrl+d"),
            Some((KeyCode::Char('d'), KeyModifiers::CONTROL))
        );
        assert_eq!(
            parse_key_combo("Shift+Tab"),
            Some((KeyCode::BackTab, KeyModifiers::SHIFT))
        );
    }

    #[test]
    fn test_parse_action_names() {
        assert_eq!(parse_action("quit"), Some(Action::Quit));
        assert_eq!(parse_action("cancel_order"), Some(Action::CancelOrder));
        assert_eq!(parse_action("panel_3"), Some(Action::JumpPanel(3)));
    }

    #[test]
    fn test_parse_action_command() {
        assert_eq!(
            parse_action(":fills summary"),
            Some(Action::RunCommand("fills summary".to_string()))
        );
    }

    #[test]
    fn test_default_keymap_lookup() {
        let keymap = Keymap::default_map();
        assert_eq!(
            keymap.lookup(KeyCode::Char('q'), KeyModifiers::NONE),
            Some(&Action::Quit)
        );
        assert_eq!(
            keymap.lookup(KeyCode::Char('?'), KeyModifiers::NONE),
            Some(&Action::ToggleHelp)
        );
        assert_eq!(
            keymap.lookup(KeyCode::Tab, KeyModifiers::NONE),
            Some(&Action::NextPanel)
        );
    }

    #[test]
    fn test_keymap_with_overrides() {
        let mut overrides = HashMap::new();
        overrides.insert("Ctrl+q".to_string(), "quit".to_string());
        overrides.insert("Ctrl+f".to_string(), ":fills summary".to_string());

        let keymap = Keymap::with_overrides(&overrides);
        assert_eq!(
            keymap.lookup(KeyCode::Char('q'), KeyModifiers::CONTROL),
            Some(&Action::Quit)
        );
        assert_eq!(
            keymap.lookup(KeyCode::Char('f'), KeyModifiers::CONTROL),
            Some(&Action::RunCommand("fills summary".to_string()))
        );
        // Original 'q' still works
        assert_eq!(
            keymap.lookup(KeyCode::Char('q'), KeyModifiers::NONE),
            Some(&Action::Quit)
        );
    }
}
