use std::collections::{HashMap, HashSet};

use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::{
    layout::{Constraint, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::{Cell, Paragraph, Row, Table, TableState},
    Frame,
};
use sqlx::sqlite::SqlitePool;
use tokio::sync::mpsc;

use crate::app::{AppMessage, NotifyLevel};
use crate::config::Settings;
use crate::db::models::{BatterCard, Lineup, Player};
use crate::widgets::selector::{SelectorEvent, SelectorWidget};
use crate::widgets::{format_rating, format_swar};

// =============================================================================
// Types
// =============================================================================

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineupFocus {
    AvailableTable,
    LineupTable,
    LineupName,
    LoadSelector,
}

#[derive(Debug, Clone)]
pub struct LineupSlot {
    pub order: usize,
    pub player_id: Option<i64>,
    pub player_name: Option<String>,
    pub position: Option<String>,
}

impl LineupSlot {
    pub fn empty(order: usize) -> Self {
        Self {
            order,
            player_id: None,
            player_name: None,
            position: None,
        }
    }

    pub fn is_empty(&self) -> bool {
        self.player_id.is_none()
    }
}

// =============================================================================
// State
// =============================================================================

pub struct LineupState {
    pub team_abbrev: String,
    pub season: i64,

    // Data
    pub available_batters: Vec<(Player, Option<BatterCard>)>,
    pub lineup_slots: [LineupSlot; 9],
    pub saved_lineups: Vec<Lineup>,
    pub is_loading: bool,

    // UI
    pub focus: LineupFocus,
    pub available_table_state: TableState,
    pub lineup_table_state: TableState,
    pub lineup_name: String,
    pub lineup_name_cursor: usize,
    pub load_selector: SelectorWidget<String>,
    pub confirm_delete: bool,
}

impl LineupState {
    pub fn new(team_abbrev: String, season: i64) -> Self {
        let lineup_slots = std::array::from_fn(|i| LineupSlot::empty(i + 1));
        Self {
            team_abbrev,
            season,
            available_batters: Vec::new(),
            lineup_slots,
            saved_lineups: Vec::new(),
            is_loading: true,
            focus: LineupFocus::AvailableTable,
            available_table_state: TableState::default(),
            lineup_table_state: TableState::default().with_selected(0),
            lineup_name: String::new(),
            lineup_name_cursor: 0,
            load_selector: SelectorWidget::new("Load Lineup"),
            confirm_delete: false,
        }
    }

    pub fn is_input_captured(&self) -> bool {
        matches!(self.focus, LineupFocus::LineupName)
            || self.load_selector.is_open
            || self.confirm_delete
    }

    pub fn mount(&mut self, pool: &SqlitePool, tx: &mpsc::UnboundedSender<AppMessage>) {
        self.is_loading = true;

        // Load my batters with cards
        let pool_c = pool.clone();
        let tx_c = tx.clone();
        let abbrev = self.team_abbrev.clone();
        let season = self.season;
        tokio::spawn(async move {
            let team = crate::db::queries::get_team_by_abbrev(&pool_c, &abbrev, season).await;
            if let Ok(Some(team)) = team
                && let Ok(batters) =
                    crate::db::queries::get_batters(&pool_c, Some(team.id), Some(season)).await
            {
                let mut with_cards = Vec::with_capacity(batters.len());
                for p in batters {
                    let card = crate::db::queries::get_batter_card(&pool_c, p.id)
                        .await
                        .ok()
                        .flatten();
                    with_cards.push((p, card));
                }
                let _ = tx_c.send(AppMessage::LineupBattersLoaded(with_cards));
            }
        });

        // Load saved lineups
        Self::reload_lineups(pool, tx);
    }

    pub fn handle_key(
        &mut self,
        key: KeyEvent,
        pool: &SqlitePool,
        _settings: &Settings,
        tx: &mpsc::UnboundedSender<AppMessage>,
    ) {
        // Delete confirmation
        if self.confirm_delete {
            self.confirm_delete = false;
            if key.code == KeyCode::Char('y') || key.code == KeyCode::Char('Y') {
                self.delete_lineup(pool, tx);
            } else {
                let _ = tx.send(AppMessage::Notify(
                    "Delete cancelled".to_string(),
                    NotifyLevel::Info,
                ));
            }
            return;
        }

        // Load selector
        if self.load_selector.is_open {
            if let SelectorEvent::Selected(_) = self.load_selector.handle_key(key) {
                self.on_lineup_load_selected();
            }
            return;
        }

        // Lineup name text input
        if self.focus == LineupFocus::LineupName {
            match key.code {
                KeyCode::Esc | KeyCode::Tab => {
                    self.focus = LineupFocus::AvailableTable;
                }
                KeyCode::Char(c) => {
                    self.lineup_name.insert(self.lineup_name_cursor, c);
                    self.lineup_name_cursor += 1;
                }
                KeyCode::Backspace => {
                    if self.lineup_name_cursor > 0 {
                        self.lineup_name_cursor -= 1;
                        self.lineup_name.remove(self.lineup_name_cursor);
                    }
                }
                KeyCode::Left => {
                    self.lineup_name_cursor = self.lineup_name_cursor.saturating_sub(1);
                }
                KeyCode::Right => {
                    if self.lineup_name_cursor < self.lineup_name.len() {
                        self.lineup_name_cursor += 1;
                    }
                }
                _ => {}
            }
            return;
        }

        // Ctrl-S: save lineup
        if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('s') {
            self.save_lineup(pool, tx);
            return;
        }

        // Tab: cycle focus
        if key.code == KeyCode::Tab {
            self.cycle_focus();
            return;
        }

        // Lineup-wide keys (work from any focus except text input)
        match key.code {
            KeyCode::Char('l') => {
                self.refresh_load_selector();
                self.focus = LineupFocus::LoadSelector;
                self.load_selector.is_focused = true;
                self.load_selector.is_open = true;
                return;
            }
            KeyCode::Char('D') => {
                if self.lineup_name.is_empty() {
                    let _ = tx.send(AppMessage::Notify(
                        "No lineup loaded to delete".to_string(),
                        NotifyLevel::Error,
                    ));
                } else {
                    self.confirm_delete = true;
                    let _ = tx.send(AppMessage::Notify(
                        format!("Delete '{}'? Press y to confirm", self.lineup_name),
                        NotifyLevel::Info,
                    ));
                }
                return;
            }
            KeyCode::Char('c') => {
                self.clear_slots();
                return;
            }
            _ => {}
        }

        // Focus-specific keys
        match self.focus {
            LineupFocus::AvailableTable => self.handle_available_key(key),
            LineupFocus::LineupTable => self.handle_lineup_table_key(key),
            LineupFocus::LoadSelector => match key.code {
                KeyCode::Enter | KeyCode::Char(' ') => {
                    self.refresh_load_selector();
                    self.load_selector.is_open = true;
                    self.load_selector.is_focused = true;
                }
                _ => {}
            },
            LineupFocus::LineupName => {} // handled above
        }
    }

    fn cycle_focus(&mut self) {
        self.load_selector.is_focused = false;
        self.focus = match self.focus {
            LineupFocus::AvailableTable => LineupFocus::LineupTable,
            LineupFocus::LineupTable => LineupFocus::LineupName,
            LineupFocus::LineupName => LineupFocus::AvailableTable,
            LineupFocus::LoadSelector => LineupFocus::AvailableTable,
        };
    }

    fn handle_available_key(&mut self, key: KeyEvent) {
        let available = self.filtered_available();
        let len = available.len();

        match key.code {
            KeyCode::Char('j') | KeyCode::Down => {
                if len > 0 {
                    let i = self.available_table_state.selected().unwrap_or(0);
                    self.available_table_state.select(Some((i + 1) % len));
                }
            }
            KeyCode::Char('k') | KeyCode::Up => {
                if len > 0 {
                    let i = self.available_table_state.selected().unwrap_or(0);
                    self.available_table_state
                        .select(Some(i.checked_sub(1).unwrap_or(len - 1)));
                }
            }
            KeyCode::Enter => {
                self.add_selected_to_lineup();
            }
            _ => {}
        }
    }

    fn handle_lineup_table_key(&mut self, key: KeyEvent) {
        let sel = self.lineup_table_state.selected().unwrap_or(0);
        match key.code {
            KeyCode::Char('j') | KeyCode::Down => {
                if sel < 8 {
                    self.lineup_table_state.select(Some(sel + 1));
                }
            }
            KeyCode::Char('k') | KeyCode::Up => {
                self.lineup_table_state
                    .select(Some(sel.saturating_sub(1)));
            }
            KeyCode::Char('x') => {
                self.lineup_slots[sel] = LineupSlot::empty(sel + 1);
            }
            KeyCode::Char('p') => {
                self.cycle_position(sel);
            }
            KeyCode::Char('J') => {
                if sel < 8 {
                    self.lineup_slots.swap(sel, sel + 1);
                    self.lineup_slots[sel].order = sel + 1;
                    self.lineup_slots[sel + 1].order = sel + 2;
                    self.lineup_table_state.select(Some(sel + 1));
                }
            }
            KeyCode::Char('K') => {
                if sel > 0 {
                    self.lineup_slots.swap(sel - 1, sel);
                    self.lineup_slots[sel - 1].order = sel;
                    self.lineup_slots[sel].order = sel + 1;
                    self.lineup_table_state.select(Some(sel - 1));
                }
            }
            _ => {}
        }
    }

    // =========================================================================
    // Lineup operations
    // =========================================================================

    fn clear_slots(&mut self) {
        for i in 0..9 {
            self.lineup_slots[i] = LineupSlot::empty(i + 1);
        }
    }

    fn filtered_available(&self) -> Vec<&(Player, Option<BatterCard>)> {
        let lineup_ids: HashSet<i64> = self
            .lineup_slots
            .iter()
            .filter_map(|s| s.player_id)
            .collect();
        self.available_batters
            .iter()
            .filter(|(p, _)| !lineup_ids.contains(&p.id))
            .collect()
    }

    fn add_selected_to_lineup(&mut self) {
        let available = self.filtered_available();
        let Some(idx) = self.available_table_state.selected() else {
            return;
        };
        let Some((player, _)) = available.get(idx) else {
            return;
        };

        // Find first empty slot
        let Some(slot_idx) = self.lineup_slots.iter().position(|s| s.is_empty()) else {
            return;
        };

        // Auto-suggest position: first eligible not already assigned
        let assigned_positions: HashSet<String> = self
            .lineup_slots
            .iter()
            .filter_map(|s| s.position.clone())
            .collect();
        let eligible = player.positions();
        let pos = eligible
            .iter()
            .find(|p| !assigned_positions.contains(**p as &str))
            .map(|p| p.to_string())
            .or_else(|| Some("DH".to_string()));

        // Compute new available length before mutating slots
        let new_available_len = available.len() - 1;

        self.lineup_slots[slot_idx] = LineupSlot {
            order: slot_idx + 1,
            player_id: Some(player.id),
            player_name: Some(player.name.clone()),
            position: pos,
        };

        // Adjust available table selection
        if new_available_len > 0 {
            let new_idx = idx.min(new_available_len - 1);
            self.available_table_state.select(Some(new_idx));
        } else {
            self.available_table_state.select(None);
        }
    }

    fn cycle_position(&mut self, slot_idx: usize) {
        let slot = &mut self.lineup_slots[slot_idx];
        let Some(pid) = slot.player_id else { return };

        let eligible: Vec<String> = self
            .available_batters
            .iter()
            .find(|(p, _)| p.id == pid)
            .map(|(p, _)| {
                let mut positions: Vec<String> =
                    p.positions().iter().map(|s| s.to_string()).collect();
                if !positions.contains(&"DH".to_string()) {
                    positions.push("DH".to_string());
                }
                positions
            })
            .unwrap_or_default();

        if eligible.is_empty() {
            return;
        }

        let current = slot.position.as_deref().unwrap_or("");
        let current_idx = eligible.iter().position(|p| p == current).unwrap_or(0);
        let next_idx = (current_idx + 1) % eligible.len();
        slot.position = Some(eligible[next_idx].clone());
    }

    fn refresh_load_selector(&mut self) {
        let items: Vec<(String, String)> = self
            .saved_lineups
            .iter()
            .map(|l| (l.name.clone(), l.name.clone()))
            .collect();
        self.load_selector.set_items(items);
    }

    fn on_lineup_load_selected(&mut self) {
        let Some(name) = self.load_selector.selected_value().cloned() else {
            return;
        };
        let Some(lineup) = self.saved_lineups.iter().find(|l| l.name == name) else {
            return;
        };

        self.lineup_name = lineup.name.clone();
        self.lineup_name_cursor = self.lineup_name.len();

        let order = lineup.batting_order_vec();
        let positions_map = lineup.positions_map();

        self.clear_slots();

        // Populate from saved lineup
        for (i, pid) in order.iter().enumerate() {
            if i >= 9 {
                break;
            }
            let player = self.available_batters.iter().find(|(p, _)| p.id == *pid);
            if let Some((p, _)) = player {
                let pos = positions_map
                    .iter()
                    .find(|(_, v)| **v == *pid)
                    .map(|(k, _)| k.clone())
                    .or_else(|| p.pos_1.clone());

                self.lineup_slots[i] = LineupSlot {
                    order: i + 1,
                    player_id: Some(*pid),
                    player_name: Some(p.name.clone()),
                    position: pos,
                };
            }
        }
        self.focus = LineupFocus::LineupTable;
    }

    fn save_lineup(&self, pool: &SqlitePool, tx: &mpsc::UnboundedSender<AppMessage>) {
        if self.lineup_name.is_empty() {
            let _ = tx.send(AppMessage::Notify(
                "Enter a lineup name first".to_string(),
                NotifyLevel::Error,
            ));
            return;
        }

        let batting_order: Vec<i64> = self
            .lineup_slots
            .iter()
            .filter_map(|s| s.player_id)
            .collect();

        let mut positions: HashMap<String, i64> = HashMap::new();
        for slot in &self.lineup_slots {
            if let (Some(pid), Some(pos)) = (slot.player_id, &slot.position) {
                positions.insert(pos.clone(), pid);
            }
        }

        let name = self.lineup_name.clone();
        let pool = pool.clone();
        let tx = tx.clone();

        tokio::spawn(async move {
            match crate::db::queries::save_lineup(
                &pool,
                &name,
                &batting_order,
                &positions,
                "lineup",
                None,
                None,
            )
            .await
            {
                Ok(_) => {
                    let _ = tx.send(AppMessage::LineupSaved(name));
                }
                Err(e) => {
                    let _ = tx.send(AppMessage::LineupSaveError(format!("{e}")));
                }
            }
        });
    }

    fn delete_lineup(&self, pool: &SqlitePool, tx: &mpsc::UnboundedSender<AppMessage>) {
        if self.lineup_name.is_empty() {
            let _ = tx.send(AppMessage::Notify(
                "No lineup loaded to delete".to_string(),
                NotifyLevel::Error,
            ));
            return;
        }

        let name = self.lineup_name.clone();
        let pool = pool.clone();
        let tx = tx.clone();

        tokio::spawn(async move {
            match crate::db::queries::delete_lineup(&pool, &name).await {
                Ok(true) => {
                    let _ = tx.send(AppMessage::LineupDeleted(name));
                }
                Ok(false) => {
                    let _ = tx.send(AppMessage::LineupDeleteError(
                        "Lineup not found".to_string(),
                    ));
                }
                Err(e) => {
                    let _ = tx.send(AppMessage::LineupDeleteError(format!("{e}")));
                }
            }
        });
    }

    fn reload_lineups(pool: &SqlitePool, tx: &mpsc::UnboundedSender<AppMessage>) {
        let pool = pool.clone();
        let tx = tx.clone();
        tokio::spawn(async move {
            if let Ok(lineups) = crate::db::queries::get_lineups(&pool).await {
                let _ = tx.send(AppMessage::LineupListLoaded(lineups));
            }
        });
    }

    pub fn handle_message(
        &mut self,
        msg: AppMessage,
        pool: &SqlitePool,
        tx: &mpsc::UnboundedSender<AppMessage>,
    ) {
        match msg {
            AppMessage::LineupBattersLoaded(batters) => {
                self.available_batters = batters;
                self.is_loading = false;
                if !self.available_batters.is_empty() {
                    self.available_table_state.select(Some(0));
                }
            }
            AppMessage::LineupListLoaded(lineups) => {
                self.saved_lineups = lineups;
            }
            AppMessage::LineupSaved(name) => {
                let _ = tx.send(AppMessage::Notify(
                    format!("Lineup '{name}' saved"),
                    NotifyLevel::Success,
                ));
                Self::reload_lineups(pool, tx);
            }
            AppMessage::LineupSaveError(e) => {
                let _ = tx.send(AppMessage::Notify(
                    format!("Save failed: {e}"),
                    NotifyLevel::Error,
                ));
            }
            AppMessage::LineupDeleted(name) => {
                let _ = tx.send(AppMessage::Notify(
                    format!("Lineup '{name}' deleted"),
                    NotifyLevel::Success,
                ));
                self.lineup_name.clear();
                self.lineup_name_cursor = 0;
                self.clear_slots();
                Self::reload_lineups(pool, tx);
            }
            AppMessage::LineupDeleteError(e) => {
                let _ = tx.send(AppMessage::Notify(
                    format!("Delete failed: {e}"),
                    NotifyLevel::Error,
                ));
            }
            _ => {}
        }
    }

    // =========================================================================
    // Rendering
    // =========================================================================

    pub fn render(&self, frame: &mut Frame, area: Rect) {
        // 50/50 horizontal split
        let panels = Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
            .split(area);

        self.render_available_panel(frame, panels[0]);
        self.render_lineup_panel(frame, panels[1]);

        // Load selector overlay (anchor at top of right panel so popup renders over it)
        if self.load_selector.is_open {
            let anchor = Rect {
                x: panels[1].x,
                y: panels[1].y,
                width: panels[1].width,
                height: 0,
            };
            self.load_selector.render_popup(frame, anchor);
        }
    }

    fn render_available_panel(&self, frame: &mut Frame, area: Rect) {
        let chunks = Layout::vertical([
            Constraint::Length(1), // title
            Constraint::Min(0),   // table
            Constraint::Length(1), // hints
        ])
        .split(area);

        let title_style = if self.focus == LineupFocus::AvailableTable {
            Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)
        } else {
            Style::default().fg(Color::DarkGray)
        };
        frame.render_widget(
            Paragraph::new(" Available Batters").style(title_style),
            chunks[0],
        );

        if self.is_loading {
            frame.render_widget(
                Paragraph::new(" Loading...").style(Style::default().fg(Color::DarkGray)),
                chunks[1],
            );
            return;
        }

        let available = self.filtered_available();
        if available.is_empty() {
            frame.render_widget(
                Paragraph::new(" No available batters")
                    .style(Style::default().fg(Color::DarkGray)),
                chunks[1],
            );
        } else {
            let header = Row::new(vec![
                Cell::from("Name"),
                Cell::from("Pos"),
                Cell::from("H"),
                Cell::from("vL"),
                Cell::from("vR"),
                Cell::from("sWAR"),
            ])
            .style(Style::default().add_modifier(Modifier::BOLD).fg(Color::White));

            let rows: Vec<Row> = available
                .iter()
                .map(|(p, card)| {
                    let (vl, vr) = card
                        .as_ref()
                        .map(|c| (format_rating(c.rating_vl), format_rating(c.rating_vr)))
                        .unwrap_or_else(|| ("\u{2014}".to_string(), "\u{2014}".to_string()));
                    Row::new(vec![
                        Cell::from(p.name.clone()),
                        Cell::from(p.positions().join("/")),
                        Cell::from(p.hand.clone().unwrap_or_default()),
                        Cell::from(vl),
                        Cell::from(vr),
                        Cell::from(format_swar(p.swar)),
                    ])
                })
                .collect();

            let widths = [
                Constraint::Length(18), // Name
                Constraint::Length(12), // Pos
                Constraint::Length(3),  // Hand
                Constraint::Length(5),  // vL
                Constraint::Length(5),  // vR
                Constraint::Min(0),    // sWAR
            ];

            let table = Table::new(rows, widths)
                .header(header)
                .row_highlight_style(Style::default().bg(Color::DarkGray));

            let mut table_state = self.available_table_state;
            frame.render_stateful_widget(table, chunks[1], &mut table_state);
        }

        frame.render_widget(
            Paragraph::new(" [Enter] Add  [j/k] Scroll")
                .style(Style::default().fg(Color::DarkGray)),
            chunks[2],
        );
    }

    fn render_lineup_panel(&self, frame: &mut Frame, area: Rect) {
        let chunks = Layout::vertical([
            Constraint::Length(1), // title + name input
            Constraint::Min(0),   // lineup table
            Constraint::Length(2), // hints
        ])
        .split(area);

        // Title with inline name
        let name_style = if self.focus == LineupFocus::LineupName {
            Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)
        } else {
            Style::default().fg(Color::White)
        };
        let title_style =
            if matches!(self.focus, LineupFocus::LineupTable | LineupFocus::LineupName) {
                Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)
            } else {
                Style::default().fg(Color::DarkGray)
            };

        let display_name = if self.lineup_name.is_empty() {
            "<unnamed>"
        } else {
            &self.lineup_name
        };
        let title_line = Line::from(vec![
            Span::styled(" Lineup: ", title_style),
            Span::styled(display_name, name_style),
        ]);
        frame.render_widget(Paragraph::new(title_line), chunks[0]);

        // Lineup table (always 9 rows)
        let header = Row::new(vec![
            Cell::from("#"),
            Cell::from("Name"),
            Cell::from("Pos"),
            Cell::from("Elig"),
            Cell::from("H"),
        ])
        .style(Style::default().add_modifier(Modifier::BOLD).fg(Color::White));

        let rows: Vec<Row> = self
            .lineup_slots
            .iter()
            .map(|slot| {
                if slot.is_empty() {
                    Row::new(vec![
                        Cell::from(slot.order.to_string()),
                        Cell::from("(empty)").style(Style::default().fg(Color::DarkGray)),
                        Cell::from(""),
                        Cell::from(""),
                        Cell::from(""),
                    ])
                } else {
                    let name = slot.player_name.as_deref().unwrap_or("?");
                    let pos = slot.position.as_deref().unwrap_or("\u{2014}");
                    let player = self
                        .available_batters
                        .iter()
                        .find(|(p, _)| Some(p.id) == slot.player_id)
                        .map(|(p, _)| p);
                    let eligible = player
                        .map(|p| p.positions().join("/"))
                        .unwrap_or_default();
                    let hand = player
                        .and_then(|p| p.hand.as_deref())
                        .unwrap_or_default();
                    Row::new(vec![
                        Cell::from(slot.order.to_string()),
                        Cell::from(name.to_string()),
                        Cell::from(pos.to_string()),
                        Cell::from(eligible),
                        Cell::from(hand.to_string()),
                    ])
                }
            })
            .collect();

        let widths = [
            Constraint::Length(3),  // #
            Constraint::Length(18), // Name
            Constraint::Length(5),  // Pos
            Constraint::Length(12), // Elig
            Constraint::Min(0),    // Hand
        ];

        let table = Table::new(rows, widths)
            .header(header)
            .row_highlight_style(Style::default().bg(Color::DarkGray));

        let mut table_state = self.lineup_table_state;
        frame.render_stateful_widget(table, chunks[1], &mut table_state);

        // Hints
        let hints = Paragraph::new(
            " [x] Remove  [p] Pos  [J/K] Move  [l] Load\n [Ctrl+S] Save  [D] Delete  [c] Clear  [Tab] Focus",
        )
        .style(Style::default().fg(Color::DarkGray));
        frame.render_widget(hints, chunks[2]);
    }
}
