use ratatui::crossterm::event::{
    self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent,
};
use std::io;
use std::time::Duration;

use super::app_state::{App, Focus};
use crate::clipboard;
use crate::editor;
use crate::editor::EditorMode;
use crate::help::HelpTab;
use crate::history;
use crate::results;
use crate::snippets;

mod global;
pub mod paste_recovery;
pub mod source_picker;

/// Determine the default help tab based on current app context
///
/// Context-aware auto-selection:
/// - Input box focus -> Input tab
/// - Results box focus -> Result tab
/// - Search box focus -> Search tab
/// - Snippet manager focus -> Snippet tab
/// - Otherwise -> Global tab
///
/// Note: History and AI tabs never auto-focus - users navigate to them manually.
fn get_default_help_tab(app: &App) -> HelpTab {
    // Priority order: more specific contexts first

    // Snippet manager visible
    if app.snippets.is_visible() {
        return HelpTab::Snippet;
    }

    // Search mode active
    if app.search.is_visible() {
        return HelpTab::Search;
    }

    // Results pane focused
    if app.focus == Focus::ResultsPane {
        return HelpTab::Result;
    }

    // Input field focused (covers Insert and Normal modes)
    if app.focus == Focus::InputField {
        return HelpTab::Input;
    }

    // Fallback - Global tab
    // Note: History and AI tabs never auto-focus
    HelpTab::Global
}

fn handle_truly_global_keys(app: &mut App, key: KeyEvent) -> bool {
    match key.code {
        KeyCode::F(1) => {
            if app.help.visible {
                app.help.reset();
            } else {
                // Auto-select tab based on current context
                app.help.active_tab = get_default_help_tab(app);
                app.help.visible = true;
            }
            true
        }
        KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            app.should_quit = true;
            true
        }
        _ => false,
    }
}

fn handle_help_keys(app: &mut App, key: KeyEvent) -> bool {
    match key.code {
        // Close help
        KeyCode::Esc | KeyCode::F(1) => {
            app.help.reset();
            true
        }
        KeyCode::Char('q') if !key.modifiers.contains(KeyModifiers::CONTROL) => {
            app.help.reset();
            true
        }
        KeyCode::Char('?') => {
            app.help.reset();
            true
        }

        // Tab navigation with Tab key
        KeyCode::Tab if key.modifiers.contains(KeyModifiers::SHIFT) => {
            app.help.active_tab = app.help.active_tab.prev();
            true
        }
        KeyCode::Tab => {
            app.help.active_tab = app.help.active_tab.next();
            true
        }
        KeyCode::BackTab => {
            app.help.active_tab = app.help.active_tab.prev();
            true
        }

        // Tab navigation with h/l keys
        KeyCode::Char('h') | KeyCode::Left => {
            app.help.active_tab = app.help.active_tab.prev();
            true
        }
        KeyCode::Char('l') | KeyCode::Right => {
            app.help.active_tab = app.help.active_tab.next();
            true
        }

        // Jump to tab by number (1-7)
        KeyCode::Char(c) if ('1'..='7').contains(&c) => {
            let index = (c as usize) - ('1' as usize);
            app.help.active_tab = HelpTab::from_index(index);
            true
        }

        // Scrolling (per-tab scroll state)
        KeyCode::Char('j') | KeyCode::Down => {
            app.help.current_scroll_mut().scroll_down(1);
            true
        }
        KeyCode::Char('J') => {
            app.help.current_scroll_mut().scroll_down(10);
            true
        }
        KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            app.help.current_scroll_mut().scroll_down(10);
            true
        }
        KeyCode::PageDown => {
            app.help.current_scroll_mut().scroll_down(10);
            true
        }
        KeyCode::Char('k') | KeyCode::Up => {
            app.help.current_scroll_mut().scroll_up(1);
            true
        }
        KeyCode::Char('K') => {
            app.help.current_scroll_mut().scroll_up(10);
            true
        }
        KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            app.help.current_scroll_mut().scroll_up(10);
            true
        }
        KeyCode::PageUp => {
            app.help.current_scroll_mut().scroll_up(10);
            true
        }
        KeyCode::Char('g') | KeyCode::Home => {
            app.help.current_scroll_mut().jump_to_top();
            true
        }
        KeyCode::Char('G') | KeyCode::End => {
            app.help.current_scroll_mut().jump_to_bottom();
            true
        }

        _ => {
            // Consume all other keys when help is visible
            true
        }
    }
}

/// Handle keys that should pass through even when snippets/history popups are visible
fn handle_popup_passthrough_keys(app: &mut App, key: KeyEvent) -> bool {
    // ? toggles help when snippets is visible (and not editing) or in normal mode/results pane
    if key.code == KeyCode::Char('?') {
        let snippets_allows = app.snippets.is_visible() && !app.snippets.is_editing();
        if snippets_allows
            || app.input.editor_mode == EditorMode::Normal
            || app.focus == Focus::ResultsPane
        {
            if app.help.visible {
                app.help.reset();
            } else {
                // Auto-select tab based on current context
                app.help.active_tab = get_default_help_tab(app);
                app.help.visible = true;
            }
            return true;
        }
    }

    // BackTab closes history and switches focus
    if key.code == KeyCode::BackTab && app.history.is_visible() {
        app.history.close();
        app.focus = match app.focus {
            Focus::InputField => Focus::ResultsPane,
            Focus::ResultsPane => Focus::InputField,
        };
        return true;
    }

    // Ctrl+S opens snippets (closes history)
    if key.code == KeyCode::Char('s')
        && key.modifiers.contains(KeyModifiers::CONTROL)
        && app.history.is_visible()
    {
        app.snippets.open();
        app.autocomplete.hide();
        app.history.close();
        return true;
    }

    false
}

const EVENT_POLL_TIMEOUT: Duration = Duration::from_millis(100);

impl App {
    pub fn handle_events(&mut self) -> io::Result<()> {
        if self.debouncer.should_execute() {
            editor::editor_events::execute_query_with_auto_show(self);
            self.debouncer.mark_executed();
            self.mark_dirty();
        }

        // Poll for query responses
        if self.poll_query_response() {
            self.mark_dirty();
        }

        if crate::ai::ai_events::poll_response_channel(&mut self.ai) {
            self.mark_dirty();
        }

        // Check notification expiry
        if self.notification.clear_if_expired() {
            self.mark_dirty();
        }

        if event::poll(EVENT_POLL_TIMEOUT)? {
            match event::read()? {
                Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
                    if self.source_picker.is_some() {
                        self.handle_source_picker_key_event(key_event);
                    } else if self.paste_recovery.is_some() {
                        self.handle_paste_recovery_key_event(key_event);
                    } else {
                        self.handle_key_event(key_event);
                    }
                    self.mark_dirty();
                }
                Event::Paste(text) => {
                    if self.source_picker.is_some() {
                        // Pasting while the picker is open is treated
                        // as the user explicitly wanting Paste mode.
                        // Mouse-and-click hookup follows in a later
                        // step; for now we just drop the bytes — the
                        // user can press `p`.
                    } else if self.paste_recovery.is_some() {
                        log::debug!(
                            "paste-recovery: bracketed paste event, {} bytes, {} lines",
                            text.len(),
                            text.matches('\n').count() + 1
                        );
                        self.handle_paste_recovery_paste(text);
                    } else {
                        self.handle_paste_event(text);
                    }
                    self.mark_dirty();
                }
                Event::Resize(_, _) => {
                    self.mark_dirty();
                }
                Event::Mouse(mouse_event) => {
                    if self.source_picker.is_some() || self.paste_recovery.is_some() {
                        // Picker / recovery occupy the screen; no
                        // input/results split to act on. Mouse hookup
                        // for the picker follows in a later step.
                    } else {
                        self.handle_mouse_event(mouse_event);
                    }
                    self.mark_dirty();
                }
                _ => {}
            }
        }
        Ok(())
    }

    /// Route a key while the source picker is active. Truly global keys
    /// (Ctrl+C quit, F1 help) still take precedence; everything else
    /// goes to the picker handler.
    fn handle_source_picker_key_event(&mut self, key: KeyEvent) {
        if handle_truly_global_keys(self, key) {
            return;
        }
        if self.help.visible && handle_help_keys(self, key) {
            return;
        }
        let _ = source_picker::handle_key(self, key);
    }

    /// Route a key while paste-recovery is active. Truly global keys
    /// (Ctrl+C quit, F1 help) still take precedence, then the recovery
    /// handler swallows everything else.
    fn handle_paste_recovery_key_event(&mut self, key: KeyEvent) {
        if handle_truly_global_keys(self, key) {
            return;
        }
        // F1 still works during recovery (renders help on top).
        if self.help.visible && handle_help_keys(self, key) {
            return;
        }
        let _ = paste_recovery::handle_key(self, key);
    }

    /// Handle a paste event while in recovery: normalise CRLF endings,
    /// enforce the soft size cap, then forward to the standard
    /// `handle_paste_event` which inserts into `app.input.textarea`.
    /// `execute_query` is a no-op while `app.query` is None.
    fn handle_paste_recovery_paste(&mut self, text: String) {
        let normalised = crate::input::paste_recovery::normalise_newlines(&text);
        let projected = self
            .input
            .textarea
            .lines()
            .iter()
            .map(|l| l.len())
            .sum::<usize>()
            .saturating_add(normalised.len());
        if projected > crate::input::paste_recovery::PASTE_RECOVERY_MAX_BYTES {
            let msg = format!(
                "Pasted content too large (max {} MB). Reduce or load via file argument.",
                crate::input::paste_recovery::PASTE_RECOVERY_MAX_BYTES / (1024 * 1024)
            );
            self.notification.show_error(&msg);
            return;
        }
        self.handle_paste_event(normalised);
    }

    fn handle_paste_event(&mut self, text: String) {
        self.input.textarea.insert_str(&text);

        self.input
            .brace_tracker
            .rebuild(self.input.textarea.lines()[0].as_ref());

        // Execute immediately for instant feedback (like old behavior)
        // Uses async execution to prevent race conditions
        editor::editor_events::execute_query(self);

        self.update_autocomplete();

        self.update_tooltip();
    }

    fn handle_mouse_event(&mut self, mouse: MouseEvent) {
        super::mouse_events::handle_mouse_event(self, mouse);
    }

    pub fn handle_key_event(&mut self, key: KeyEvent) {
        // STEP 1: Truly global keys - ALWAYS work regardless of any popup
        if handle_truly_global_keys(self, key) {
            return;
        }

        // STEP 2: Popup stack (topmost first) - each handles its own Esc
        if self.help.visible && handle_help_keys(self, key) {
            return;
        }

        if self.search.is_visible() && crate::search::search_events::handle_search_key(self, key) {
            return;
        }

        // STEP 3: Keys that should pass through even when snippets/history are visible
        if (self.snippets.is_visible() || self.history.is_visible())
            && handle_popup_passthrough_keys(self, key)
        {
            return;
        }

        if self.snippets.is_visible() {
            snippets::snippet_events::handle_snippet_popup_key(self, key);
            return;
        }

        if self.history.is_visible() {
            history::history_events::handle_history_popup_key(self, key);
            return;
        }

        if self.save.is_visible() {
            crate::save::save_events::handle_save_popup_key(self, key);
            return;
        }

        // STEP 3: Other global keys (when no popup is active)
        if global::handle_global_keys(self, key) {
            return;
        }

        // STEP 4: Clipboard
        if clipboard::clipboard_events::handle_clipboard_key(self, key, self.clipboard_backend) {
            return;
        }

        // STEP 5: Focus-based routing
        match self.focus {
            Focus::InputField => self.handle_input_field_key(key),
            Focus::ResultsPane => results::results_events::handle_results_pane_key(self, key),
        }
    }

    pub(super) fn handle_input_field_key(&mut self, key: KeyEvent) {
        if key.code == KeyCode::Esc {
            if self.autocomplete.is_visible() {
                self.autocomplete.hide();
            }
            self.input.editor_mode = EditorMode::Normal;
            return;
        }

        if key.code == KeyCode::Char('d') && key.modifiers.contains(KeyModifiers::CONTROL) {
            self.results_scroll.page_down();
            return;
        }

        if key.code == KeyCode::Char('u') && key.modifiers.contains(KeyModifiers::CONTROL) {
            self.results_scroll.page_up();
            return;
        }

        if self.input.editor_mode == EditorMode::Insert && self.autocomplete.is_visible() {
            match key.code {
                KeyCode::Down => {
                    self.autocomplete.select_next();
                    return;
                }
                KeyCode::Up => {
                    self.autocomplete.select_previous();
                    return;
                }
                _ => {}
            }
        }

        if self.input.editor_mode == EditorMode::Insert {
            if key.code == KeyCode::Char('p') && key.modifiers.contains(KeyModifiers::CONTROL) {
                if let Some(entry) = self.history.cycle_previous() {
                    self.replace_query_with(&entry);
                }
                return;
            }

            if key.code == KeyCode::Char('n') && key.modifiers.contains(KeyModifiers::CONTROL) {
                if let Some(entry) = self.history.cycle_next() {
                    self.replace_query_with(&entry);
                } else {
                    self.input.textarea.delete_line_by_head();
                    self.input.textarea.delete_line_by_end();
                    editor::editor_events::execute_query(self);
                }
                return;
            }

            if key.code == KeyCode::Char('r') && key.modifiers.contains(KeyModifiers::CONTROL) {
                self.open_history_popup();
                return;
            }

            if key.code == KeyCode::Up {
                self.open_history_popup();
                return;
            }
        }

        match self.input.editor_mode {
            EditorMode::Insert => editor::editor_events::handle_insert_mode_key(self, key),
            EditorMode::Normal => editor::editor_events::handle_normal_mode_key(self, key),
            EditorMode::Operator(_) => editor::editor_events::handle_operator_mode_key(self, key),
            EditorMode::CharSearch(_, _) => {
                editor::editor_events::handle_char_search_mode_key(self, key)
            }
            EditorMode::OperatorCharSearch(_, _, _, _) => {
                editor::editor_events::handle_operator_char_search_mode_key(self, key)
            }
            EditorMode::TextObject(_, _) => {
                editor::editor_events::handle_text_object_mode_key(self, key)
            }
        }
    }

    fn replace_query_with(&mut self, text: &str) {
        self.input.textarea.delete_line_by_head();
        self.input.textarea.delete_line_by_end();
        self.input.textarea.insert_str(text);
        editor::editor_events::execute_query(self);
    }

    fn open_history_popup(&mut self) {
        if self.history.total_count() == 0 {
            return;
        }

        let query = self.query().to_string();
        let initial_query = if query.is_empty() {
            None
        } else {
            Some(query.as_str())
        };
        self.history.open(initial_query);
        self.autocomplete.hide();
    }

    /// Poll for query responses and update state
    ///
    /// Checks for completed async queries and triggers AI updates when needed.
    /// Uses the query returned from poll_response() to ensure AI gets correct context.
    /// Returns true if state changed (query completed).
    fn poll_query_response(&mut self) -> bool {
        let completed_query = if let Some(query_state) = &mut self.query {
            query_state.poll_response()
        } else {
            None
        };

        if let Some(completed_query) = completed_query {
            // Result changed - update stats once (not on every frame)
            self.update_stats();

            // Apply any pending viewport restore from a `<` (drill-back).
            // Has to happen *after* update_stats so cached_line_count
            // reflects the freshly-loaded result; otherwise the cursor
            // gets clamped against the prior (drilled) result's row count.
            crate::path_at_cursor_apply::apply_pending_viewport_restore(self);

            // State changed - trigger AI update if visible and query is not empty
            if self.ai.visible && !completed_query.is_empty() {
                let query_state = self.query.as_ref().unwrap();
                let cursor_pos = self.input.textarea.cursor().1;

                let ai_result: Result<String, String> = match &query_state.result {
                    Ok(_) => query_state
                        .last_successful_result_unformatted
                        .as_ref()
                        .map(|s| Ok(s.as_ref().clone()))
                        .unwrap_or_else(|| Ok(String::new())),
                    Err(e) => Err(e.clone()),
                };

                crate::ai::ai_events::handle_query_result(
                    &mut self.ai,
                    &ai_result,
                    &completed_query, // Use query from response, not current input!
                    cursor_pos,
                    crate::ai::context::ContextParams {
                        input_schema: self.input_json_schema.as_deref(),
                        base_query: query_state.base_query_for_suggestions.as_deref(),
                        base_query_result: query_state
                            .last_successful_result_for_context
                            .as_deref()
                            .map(|s| s.as_ref()),
                        is_empty_result: query_state.is_empty_result,
                    },
                );
            }
            return true;
        }
        false
    }
}

#[cfg(test)]
#[path = "app_events_tests.rs"]
mod app_events_tests;
