use crossterm::event::{KeyCode, KeyEvent};
use ratatui::{
    layout::{Constraint, Layout, Rect},
    style::{Color, Modifier, Style},
    text::{Line, Span},
    widgets::Paragraph,
    Frame,
};
use sqlx::sqlite::SqlitePool;
use std::time::{Duration, Instant};
use tokio::sync::mpsc;

use crate::api::types::StandingsEntry;
use crate::calc::matchup::MatchupResult;
use crate::config::Settings;
use crate::db::models::{BatterCard, Lineup, Player, Roster, SyncStatus, Team};
use crate::screens::dashboard::DashboardState;
use crate::screens::gameday::GamedayState;
use crate::screens::lineup::LineupState;
use crate::screens::matchup::MatchupState;
use crate::screens::roster::{RosterRow, RosterState};
use crate::screens::settings::SettingsState;
use crate::screens::standings::StandingsState;

// =============================================================================
// Messages
// =============================================================================

#[derive(Debug)]
pub enum AppMessage {
    // Dashboard
    RosterLoaded(Roster),
    TeamInfoLoaded(String, Option<f64>), // (team_name, salary_cap)
    MissingCardsLoaded {
        batters: Vec<String>,
        pitchers: Vec<String>,
    },
    SyncStatusesLoaded(Vec<SyncStatus>),
    SyncStarted,
    SyncComplete(Result<crate::api::sync::SyncResult, anyhow::Error>),

    // Gameday + Matchup — init
    CacheReady,
    CacheError(String),
    TeamsLoaded(Vec<Team>),
    MyBattersLoaded(Vec<(Player, Option<BatterCard>)>),
    LineupsLoaded(Vec<Lineup>),

    // Gameday + Matchup — on team select
    PitchersLoaded(Vec<Player>),

    // Gameday + Matchup — on pitcher select
    MatchupsCalculated(Vec<MatchupResult>),

    // Gameday + Lineup — lineup ops
    LineupSaved(String),
    LineupSaveError(String),

    // Roster
    RosterPlayersLoaded {
        majors: Vec<RosterRow>,
        minors: Vec<RosterRow>,
        il: Vec<RosterRow>,
        swar_total: f64,
    },

    // Lineup (standalone)
    LineupBattersLoaded(Vec<(Player, Option<BatterCard>)>),
    LineupListLoaded(Vec<Lineup>),
    LineupDeleted(String),
    LineupDeleteError(String),

    // Settings
    SettingsTeamValidated(Option<(String, Option<f64>)>),

    // Standings
    StandingsCacheLoaded(Option<Vec<StandingsEntry>>, Option<chrono::NaiveDateTime>),
    StandingsRefreshed(Vec<StandingsEntry>),

    // General
    Notify(String, NotifyLevel),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NotifyLevel {
    Info,
    Success,
    Error,
}

// =============================================================================
// Notification
// =============================================================================

pub struct Notification {
    pub message: String,
    pub level: NotifyLevel,
    pub created_at: Instant,
    pub duration: Duration,
}

impl Notification {
    pub fn new(message: String, level: NotifyLevel) -> Self {
        Self {
            message,
            level,
            created_at: Instant::now(),
            duration: Duration::from_millis(3000),
        }
    }

    pub fn is_expired(&self) -> bool {
        self.created_at.elapsed() > self.duration
    }
}

// =============================================================================
// Screen enum
// =============================================================================

pub enum ActiveScreen {
    Dashboard(Box<DashboardState>),
    Gameday(Box<GamedayState>),
    Roster(Box<RosterState>),
    Matchup(Box<MatchupState>),
    Lineup(Box<LineupState>),
    Settings(Box<SettingsState>),
    Standings(Box<StandingsState>),
}

impl ActiveScreen {
    pub fn label(&self) -> &'static str {
        match self {
            ActiveScreen::Dashboard(_) => "Dashboard",
            ActiveScreen::Gameday(_) => "Gameday",
            ActiveScreen::Roster(_) => "Roster",
            ActiveScreen::Matchup(_) => "Matchup",
            ActiveScreen::Lineup(_) => "Lineup",
            ActiveScreen::Settings(_) => "Settings",
            ActiveScreen::Standings(_) => "Standings",
        }
    }
}

// =============================================================================
// App
// =============================================================================

pub struct App {
    pub screen: ActiveScreen,
    pub pool: SqlitePool,
    pub settings: Settings,
    pub tx: mpsc::UnboundedSender<AppMessage>,
    pub notification: Option<Notification>,
    pub tick_count: u64,
    /// Cached matchup state so navigating away and back preserves selections.
    cached_matchup: Option<Box<MatchupState>>,
}

impl App {
    pub fn new(
        settings: Settings,
        pool: SqlitePool,
        tx: mpsc::UnboundedSender<AppMessage>,
    ) -> Self {
        let screen = ActiveScreen::Dashboard(Box::new(DashboardState::new(
            settings.team.abbrev.clone(),
            settings.team.season as i64,
            &settings,
        )));
        Self {
            screen,
            pool,
            settings,
            tx,
            notification: None,
            tick_count: 0,
            cached_matchup: None,
        }
    }

    /// Fire initial data load for the starting screen.
    pub fn on_mount(&mut self) {
        if let ActiveScreen::Dashboard(s) = &mut self.screen {
            s.mount(&self.pool, &self.tx);
        }
    }

    pub fn on_tick(&mut self) {
        self.tick_count = self.tick_count.wrapping_add(1);
        self.notification = self.notification.take().filter(|n| !n.is_expired());
    }

    /// Returns false when a text input or selector popup is active.
    pub fn should_quit_on_q(&self) -> bool {
        !self.is_input_captured()
    }

    fn is_input_captured(&self) -> bool {
        match &self.screen {
            ActiveScreen::Gameday(s) => s.is_input_captured(),
            ActiveScreen::Matchup(s) => s.is_input_captured(),
            ActiveScreen::Lineup(s) => s.is_input_captured(),
            ActiveScreen::Settings(s) => s.is_input_captured(),
            _ => false,
        }
    }

    pub fn handle_key(&mut self, key: KeyEvent) {
        // Global navigation (only when not capturing input)
        if !self.is_input_captured() {
            match key.code {
                KeyCode::Char('d') => {
                    if !matches!(&self.screen, ActiveScreen::Dashboard(_)) {
                        self.switch_to_dashboard();
                        return;
                    }
                }
                KeyCode::Char('g') => {
                    if !matches!(&self.screen, ActiveScreen::Gameday(_)) {
                        self.switch_to_gameday();
                        return;
                    }
                }
                KeyCode::Char('r') => {
                    if !matches!(&self.screen, ActiveScreen::Roster(_)) {
                        self.switch_to_roster();
                        return;
                    }
                }
                KeyCode::Char('m') => {
                    if !matches!(&self.screen, ActiveScreen::Matchup(_)) {
                        self.switch_to_matchup();
                        return;
                    }
                }
                KeyCode::Char('l') => {
                    if !matches!(&self.screen, ActiveScreen::Lineup(_)) {
                        self.switch_to_lineup();
                        return;
                    }
                }
                KeyCode::Char('w') => {
                    if !matches!(&self.screen, ActiveScreen::Standings(_)) {
                        self.switch_to_standings();
                        return;
                    }
                }
                KeyCode::Char('S') => {
                    if !matches!(&self.screen, ActiveScreen::Settings(_)) {
                        self.switch_to_settings();
                        return;
                    }
                }
                _ => {}
            }
        }

        // Delegate to current screen
        match &mut self.screen {
            ActiveScreen::Dashboard(s) => s.handle_key(key, &self.pool, &self.settings, &self.tx),
            ActiveScreen::Gameday(s) => s.handle_key(key, &self.pool, &self.settings, &self.tx),
            ActiveScreen::Roster(s) => s.handle_key(key, &self.pool, &self.tx),
            ActiveScreen::Matchup(s) => s.handle_key(key, &self.pool, &self.settings, &self.tx),
            ActiveScreen::Lineup(s) => s.handle_key(key, &self.pool, &self.settings, &self.tx),
            ActiveScreen::Settings(s) => s.handle_key(key, &self.pool, &self.settings, &self.tx),
            ActiveScreen::Standings(s) => s.handle_key(key, &self.pool, &self.settings, &self.tx),
        }
    }

    pub fn handle_message(&mut self, msg: AppMessage) {
        match msg {
            AppMessage::Notify(text, level) => {
                self.notification = Some(Notification::new(text, level));
            }
            _ => match &mut self.screen {
                ActiveScreen::Dashboard(s) => s.handle_message(msg, &self.pool, &self.settings, &self.tx),
                ActiveScreen::Gameday(s) => s.handle_message(msg),
                ActiveScreen::Roster(s) => s.handle_message(msg),
                ActiveScreen::Matchup(s) => s.handle_message(msg, &self.pool, &self.settings, &self.tx),
                ActiveScreen::Lineup(s) => s.handle_message(msg, &self.pool, &self.tx),
                ActiveScreen::Settings(s) => s.handle_message(msg),
                ActiveScreen::Standings(s) => s.handle_message(msg),
            },
        }
    }

    /// If leaving the Matchup screen, save its state for later restoration.
    fn cache_matchup_if_active(&mut self) {
        if matches!(&self.screen, ActiveScreen::Matchup(_)) {
            let placeholder = ActiveScreen::Settings(Box::new(SettingsState::new(&self.settings)));
            let old = std::mem::replace(&mut self.screen, placeholder);
            if let ActiveScreen::Matchup(state) = old {
                self.cached_matchup = Some(state);
            }
        }
    }

    fn switch_to_dashboard(&mut self) {
        self.cache_matchup_if_active();
        let mut state = DashboardState::new(
            self.settings.team.abbrev.clone(),
            self.settings.team.season as i64,
            &self.settings,
        );
        state.mount(&self.pool, &self.tx);
        self.screen = ActiveScreen::Dashboard(Box::new(state));
    }

    fn switch_to_gameday(&mut self) {
        self.cache_matchup_if_active();
        let mut state = GamedayState::new(
            self.settings.team.abbrev.clone(),
            self.settings.team.season as i64,
        );
        state.mount(&self.pool, &self.tx);
        self.screen = ActiveScreen::Gameday(Box::new(state));
    }

    fn switch_to_roster(&mut self) {
        self.cache_matchup_if_active();
        let mut state = RosterState::new(
            self.settings.team.abbrev.clone(),
            self.settings.team.season as i64,
            &self.settings,
        );
        state.mount(&self.pool, &self.tx);
        self.screen = ActiveScreen::Roster(Box::new(state));
    }

    fn switch_to_matchup(&mut self) {
        // Restore cached state if available (preserves opponent/pitcher selection)
        if let Some(state) = self.cached_matchup.take() {
            self.screen = ActiveScreen::Matchup(state);
            return;
        }
        let mut state = MatchupState::new(
            self.settings.team.abbrev.clone(),
            self.settings.team.season as i64,
        );
        state.mount(&self.pool, &self.tx);
        self.screen = ActiveScreen::Matchup(Box::new(state));
    }

    fn switch_to_lineup(&mut self) {
        self.cache_matchup_if_active();
        let mut state = LineupState::new(
            self.settings.team.abbrev.clone(),
            self.settings.team.season as i64,
        );
        state.mount(&self.pool, &self.tx);
        self.screen = ActiveScreen::Lineup(Box::new(state));
    }

    fn switch_to_standings(&mut self) {
        self.cache_matchup_if_active();
        let mut state = StandingsState::new(
            self.settings.team.abbrev.clone(),
            self.settings.team.season as i64,
        );
        state.mount(&self.pool, &self.settings, &self.tx);
        self.screen = ActiveScreen::Standings(Box::new(state));
    }

    fn switch_to_settings(&mut self) {
        self.cache_matchup_if_active();
        let state = SettingsState::new(&self.settings);
        self.screen = ActiveScreen::Settings(Box::new(state));
    }

    pub fn render(&self, frame: &mut Frame) {
        let area = frame.area();
        let chunks = Layout::vertical([
            Constraint::Length(1), // nav bar
            Constraint::Min(0),   // screen content
            Constraint::Length(1), // status bar
        ])
        .split(area);

        self.render_nav_bar(frame, chunks[0]);

        match &self.screen {
            ActiveScreen::Dashboard(s) => s.render(frame, chunks[1], self.tick_count),
            ActiveScreen::Gameday(s) => s.render(frame, chunks[1], self.tick_count),
            ActiveScreen::Roster(s) => s.render(frame, chunks[1]),
            ActiveScreen::Matchup(s) => s.render(frame, chunks[1], self.tick_count),
            ActiveScreen::Lineup(s) => s.render(frame, chunks[1]),
            ActiveScreen::Settings(s) => s.render(frame, chunks[1]),
            ActiveScreen::Standings(s) => s.render(frame, chunks[1], self.tick_count),
        }

        self.render_status_bar(frame, chunks[2]);
    }

    fn render_nav_bar(&self, frame: &mut Frame, area: Rect) {
        let active = self.screen.label();
        let items = [
            ("d", "Dashboard"),
            ("g", "Gameday"),
            ("r", "Roster"),
            ("m", "Matchup"),
            ("l", "Lineup"),
            ("w", "Standings"),
            ("S", "Settings"),
        ];

        let spans: Vec<Span> = items
            .iter()
            .flat_map(|(key, label)| {
                let is_active = *label == active;
                let style = if is_active {
                    Style::default()
                        .fg(Color::Yellow)
                        .add_modifier(Modifier::BOLD)
                } else {
                    Style::default().fg(Color::DarkGray)
                };
                vec![
                    Span::styled(format!(" [{}] ", key), Style::default().fg(Color::DarkGray)),
                    Span::styled(*label, style),
                ]
            })
            .collect();

        let nav = Paragraph::new(Line::from(spans));
        frame.render_widget(nav, area);
    }

    fn render_status_bar(&self, frame: &mut Frame, area: Rect) {
        let (text, style) = match &self.notification {
            Some(n) => {
                let color = match n.level {
                    NotifyLevel::Info => Color::White,
                    NotifyLevel::Success => Color::Green,
                    NotifyLevel::Error => Color::Red,
                };
                (n.message.as_str(), Style::default().fg(color))
            }
            None => ("", Style::default()),
        };
        let bar = Paragraph::new(text).style(style);
        frame.render_widget(bar, area);
    }
}
