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

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;

// =============================================================================
// 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 — init
    CacheReady,
    CacheError(String),
    TeamsLoaded(Vec<Team>),
    MyBattersLoaded(Vec<(Player, Option<BatterCard>)>),
    LineupsLoaded(Vec<Lineup>),

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

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

    // Gameday — lineup
    LineupSaved(String),
    LineupSaveError(String),

    // 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_ms: u64,
}

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

    pub fn is_expired(&self) -> bool {
        self.created_at.elapsed().as_millis() > self.duration_ms as u128
    }
}

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

/// Stub screen identifier for screens not yet implemented.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StubScreen {
    Roster,
    Matchup,
    Lineup,
    Settings,
}

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

pub enum ActiveScreen {
    Dashboard(DashboardState),
    Gameday(GamedayState),
    Stub(StubScreen),
}

impl ActiveScreen {
    pub fn label(&self) -> &'static str {
        match self {
            ActiveScreen::Dashboard(_) => "Dashboard",
            ActiveScreen::Gameday(_) => "Gameday",
            ActiveScreen::Stub(s) => s.label(),
        }
    }
}

// =============================================================================
// 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,
}

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

    /// 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);
        if let Some(n) = &self.notification {
            if n.is_expired() {
                self.notification = None;
            }
        }
    }

    /// Returns false when a text input or selector popup is active.
    pub fn should_quit_on_q(&self) -> bool {
        match &self.screen {
            ActiveScreen::Gameday(s) => !s.is_input_captured(),
            _ => true,
        }
    }

    fn is_input_captured(&self) -> bool {
        match &self.screen {
            ActiveScreen::Gameday(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;
                    }
                }
                _ => {}
            }
        }

        // 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::Stub(_) => {}
        }
    }

    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.tx),
                ActiveScreen::Gameday(s) => s.handle_message(msg),
                ActiveScreen::Stub(_) => {}
            },
        }
    }

    fn switch_to_dashboard(&mut self) {
        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(state);
    }

    fn switch_to_gameday(&mut self) {
        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(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::Stub(scr) => render_stub(frame, chunks[1], scr),
        }

        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"),
        ];

        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);
    }
}

fn render_stub(frame: &mut Frame, area: Rect, screen: &StubScreen) {
    let text = format!("{} (not yet implemented)", screen.label());
    let widget = Paragraph::new(text)
        .block(Block::default().borders(Borders::ALL).title(screen.label()))
        .alignment(Alignment::Center);
    frame.render_widget(widget, area);
}
