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

use crate::api::types::StandingsEntry;
use crate::app::{AppMessage, NotifyLevel};
use crate::config::Settings;
use crate::screens::format_relative_time;

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

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StandingsTab {
    Division,
    Wildcard,
}

#[derive(Debug, Clone)]
pub struct StandingsRow {
    pub abbrev: String,
    pub team_name: String,
    pub wins: i64,
    pub losses: i64,
    pub pct: f64,
    pub gb: String,
    pub run_diff: i64,
    pub home: String,
    pub away: String,
    pub last8: String,
    pub streak: String,
    pub division_name: String,
    pub is_my_team: bool,
}

#[derive(Debug, Clone)]
pub struct DivisionGroup {
    pub name: String,
    pub teams: Vec<StandingsRow>,
}

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

pub struct StandingsState {
    pub team_abbrev: String,
    pub season: i64,
    pub is_loading: bool,
    pub is_refreshing: bool,
    pub last_updated: Option<chrono::NaiveDateTime>,

    // Data
    pub divisions: Vec<DivisionGroup>,
    pub wildcard: Vec<StandingsRow>,

    // UI
    pub active_tab: StandingsTab,
    pub scroll_offset: usize,
}

impl StandingsState {
    pub fn new(team_abbrev: String, season: i64) -> Self {
        Self {
            team_abbrev,
            season,
            is_loading: true,
            is_refreshing: false,
            last_updated: None,
            divisions: Vec::new(),
            wildcard: Vec::new(),
            active_tab: StandingsTab::Division,
            scroll_offset: 0,
        }
    }

    pub fn mount(
        &mut self,
        pool: &SqlitePool,
        settings: &Settings,
        tx: &mpsc::UnboundedSender<AppMessage>,
    ) {
        // Phase 1: Load from DB cache (instant)
        {
            let pool = pool.clone();
            let season = self.season;
            let tx = tx.clone();
            tokio::spawn(async move {
                let cached = crate::db::queries::get_cached_standings(&pool, season).await;
                match cached {
                    Ok(Some((json_data, fetched_at))) => {
                        let entries: Option<Vec<StandingsEntry>> =
                            serde_json::from_str(&json_data).ok();
                        let _ = tx.send(AppMessage::StandingsCacheLoaded(entries, Some(fetched_at)));
                    }
                    _ => {
                        let _ = tx.send(AppMessage::StandingsCacheLoaded(None, None));
                    }
                }
            });
        }

        // Phase 2: Fetch fresh from API (background)
        self.is_refreshing = true;
        {
            let pool = pool.clone();
            let base_url = settings.api.base_url.clone();
            let api_key = settings.api.api_key.clone();
            let timeout = settings.api.timeout;
            let season = self.season;
            let tx = tx.clone();
            tokio::spawn(async move {
                let client =
                    match crate::api::client::LeagueApiClient::new(&base_url, &api_key, timeout) {
                        Ok(c) => c,
                        Err(e) => {
                            let _ = tx.send(AppMessage::Notify(
                                format!("API client error: {e}"),
                                NotifyLevel::Error,
                            ));
                            return;
                        }
                    };

                match client.get_standings(season).await {
                    Ok(entries) => {
                        if let Ok(json) = serde_json::to_string(&entries) {
                            let _ = crate::db::queries::upsert_standings_cache(
                                &pool, season, &json,
                            )
                            .await;
                        }
                        let _ = tx.send(AppMessage::StandingsRefreshed(entries));
                    }
                    Err(e) => {
                        tracing::error!("Failed to fetch standings: {e}");
                        let _ = tx.send(AppMessage::Notify(
                            format!("Standings refresh failed: {e}"),
                            NotifyLevel::Error,
                        ));
                        let _ = tx.send(AppMessage::StandingsRefreshed(Vec::new()));
                    }
                }
            });
        }
    }

    pub fn handle_key(
        &mut self,
        key: KeyEvent,
        pool: &SqlitePool,
        settings: &Settings,
        tx: &mpsc::UnboundedSender<AppMessage>,
    ) {
        match key.code {
            KeyCode::Char('1') => {
                self.active_tab = StandingsTab::Division;
                self.scroll_offset = 0;
            }
            KeyCode::Char('2') => {
                self.active_tab = StandingsTab::Wildcard;
                self.scroll_offset = 0;
            }
            KeyCode::Char('j') | KeyCode::Down => {
                let max = self.active_row_count().saturating_sub(1);
                self.scroll_offset = (self.scroll_offset + 1).min(max);
            }
            KeyCode::Char('k') | KeyCode::Up => {
                self.scroll_offset = self.scroll_offset.saturating_sub(1);
            }
            KeyCode::Char('f') => {
                self.mount(pool, settings, tx);
            }
            _ => {}
        }
    }

    pub fn handle_message(&mut self, msg: AppMessage) {
        match msg {
            AppMessage::StandingsCacheLoaded(entries, fetched_at) => {
                if let Some(entries) = entries {
                    self.process_standings(&entries);
                    self.last_updated = fetched_at;
                }
                if !self.divisions.is_empty() {
                    self.is_loading = false;
                }
            }
            AppMessage::StandingsRefreshed(entries) => {
                self.is_refreshing = false;
                if !entries.is_empty() {
                    self.process_standings(&entries);
                    self.last_updated = Some(chrono::Utc::now().naive_utc());
                }
                self.is_loading = false;
            }
            _ => {}
        }
    }

    fn process_standings(&mut self, entries: &[StandingsEntry]) {
        let team_abbrev = &self.team_abbrev;
        let rows: Vec<StandingsRow> =
            entries.iter().map(|e| entry_to_row(e, team_abbrev)).collect();

        let mut div_map: std::collections::BTreeMap<String, Vec<StandingsRow>> =
            std::collections::BTreeMap::new();
        for row in &rows {
            let key = if row.division_name.is_empty() {
                "Unknown".to_string()
            } else {
                row.division_name.clone()
            };
            div_map.entry(key).or_default().push(row.clone());
        }

        self.divisions = div_map
            .into_iter()
            .map(|(name, mut teams)| {
                teams.sort_by(|a, b| {
                    b.pct.partial_cmp(&a.pct).unwrap_or(std::cmp::Ordering::Equal)
                });
                DivisionGroup { name, teams }
            })
            .collect();

        // Wildcard: non-division-leaders sorted by record
        let div_leaders: std::collections::HashSet<&str> = self
            .divisions
            .iter()
            .filter_map(|d| d.teams.first())
            .map(|t| t.abbrev.as_str())
            .collect();

        let mut wc: Vec<StandingsRow> = rows
            .into_iter()
            .filter(|r| !div_leaders.contains(r.abbrev.as_str()))
            .collect();
        wc.sort_by(|a, b| b.pct.partial_cmp(&a.pct).unwrap_or(std::cmp::Ordering::Equal));
        self.wildcard = wc;
    }

    fn active_row_count(&self) -> usize {
        match self.active_tab {
            StandingsTab::Division => self.divisions.iter().map(|d| d.teams.len()).sum(),
            StandingsTab::Wildcard => self.wildcard.len(),
        }
    }

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

    pub fn render(&self, frame: &mut Frame, area: Rect, tick_count: u64) {
        let chunks = Layout::vertical([
            Constraint::Length(1),
            Constraint::Length(1),
            Constraint::Min(0),
            Constraint::Length(1),
        ])
        .split(area);

        self.render_title(frame, chunks[0], tick_count);
        self.render_tabs(frame, chunks[1]);

        if self.is_loading && self.divisions.is_empty() {
            let loading = Paragraph::new(" Loading standings...")
                .style(Style::default().fg(Color::DarkGray));
            frame.render_widget(loading, chunks[2]);
        } else {
            match self.active_tab {
                StandingsTab::Division => self.render_division_view(frame, chunks[2]),
                StandingsTab::Wildcard => self.render_wildcard_view(frame, chunks[2]),
            }
        }

        self.render_hints(frame, chunks[3]);
    }

    fn render_title(&self, frame: &mut Frame, area: Rect, tick_count: u64) {
        let mut spans = vec![Span::styled(
            format!(" Standings — Season {}", self.season),
            Style::default()
                .fg(Color::Cyan)
                .add_modifier(Modifier::BOLD),
        )];

        if let Some(ts) = &self.last_updated {
            spans.push(Span::styled(
                format!("  (updated {})", format_relative_time(ts)),
                Style::default().fg(Color::DarkGray),
            ));
        }

        if self.is_refreshing {
            let spinner = ['|', '/', '-', '\\'][(tick_count as usize / 2) % 4];
            spans.push(Span::styled(
                format!("  {} refreshing...", spinner),
                Style::default().fg(Color::Yellow),
            ));
        }

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

    fn render_tabs(&self, frame: &mut Frame, area: Rect) {
        let tabs = [
            (StandingsTab::Division, "1", "Divisions"),
            (StandingsTab::Wildcard, "2", "Wild Card"),
        ];

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

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

    fn render_division_view(&self, frame: &mut Frame, area: Rect) {
        if self.divisions.is_empty() {
            let empty = Paragraph::new(" No standings data")
                .style(Style::default().fg(Color::DarkGray));
            frame.render_widget(empty, area);
            return;
        }

        let mut all_rows: Vec<Row> = Vec::new();
        let mut team_index = 0usize;

        for div in &self.divisions {
            all_rows.push(Row::new(vec![Cell::from(Span::styled(
                format!(" {}", div.name),
                Style::default()
                    .fg(Color::Cyan)
                    .add_modifier(Modifier::BOLD),
            ))]));

            for team in &div.teams {
                all_rows.push(build_standings_row(team, self.scroll_offset == team_index));
                team_index += 1;
            }
        }

        let table = Table::new(all_rows, standings_widths()).header(standings_header());
        frame.render_widget(table, area);
    }

    fn render_wildcard_view(&self, frame: &mut Frame, area: Rect) {
        if self.wildcard.is_empty() {
            let empty = Paragraph::new(" No wildcard data")
                .style(Style::default().fg(Color::DarkGray));
            frame.render_widget(empty, area);
            return;
        }

        let rows: Vec<Row> = self
            .wildcard
            .iter()
            .enumerate()
            .map(|(i, team)| build_standings_row(team, self.scroll_offset == i))
            .collect();

        let table = Table::new(rows, standings_widths()).header(standings_header());
        frame.render_widget(table, area);
    }

    fn render_hints(&self, frame: &mut Frame, area: Rect) {
        let hints = Paragraph::new(" [1/2] Tab  [j/k] Scroll  [f] Refresh")
            .style(Style::default().fg(Color::DarkGray));
        frame.render_widget(hints, area);
    }
}

// =============================================================================
// Helpers
// =============================================================================

fn entry_to_row(e: &StandingsEntry, team_abbrev: &str) -> StandingsRow {
    let abbrev = e.team.abbrev.clone().unwrap_or_default();
    let team_name = e
        .team
        .short_name
        .clone()
        .unwrap_or_else(|| abbrev.clone());
    let total = e.wins + e.losses;
    let pct = if total > 0 {
        e.wins as f64 / total as f64
    } else {
        0.0
    };
    let gb = match e.div_gb {
        Some(0.0) | None => "—".to_string(),
        Some(gb) => {
            if gb == gb.floor() {
                format!("{:.0}", gb)
            } else {
                format!("{:.1}", gb)
            }
        }
    };
    let streak = match &e.streak_wl {
        Some(wl) => format!("{}{}", wl.to_uppercase(), e.streak_num),
        None => "—".to_string(),
    };
    let division_name = e
        .team
        .division
        .as_ref()
        .and_then(|d| d.division_name.clone())
        .unwrap_or_default();

    StandingsRow {
        is_my_team: abbrev == team_abbrev,
        abbrev,
        team_name,
        wins: e.wins,
        losses: e.losses,
        pct,
        gb,
        run_diff: e.run_diff,
        home: format!("{}-{}", e.home_wins, e.home_losses),
        away: format!("{}-{}", e.away_wins, e.away_losses),
        last8: format!("{}-{}", e.last8_wins, e.last8_losses),
        streak,
        division_name,
    }
}

fn build_standings_row(team: &StandingsRow, is_selected: bool) -> Row<'static> {
    let row_style = {
        let mut s = Style::default();
        if team.is_my_team {
            s = s.fg(Color::Yellow).add_modifier(Modifier::BOLD);
        }
        if is_selected {
            s = s.bg(Color::DarkGray);
        }
        s
    };

    let diff_str = format!("{:+}", team.run_diff);

    let diff_style = if team.run_diff > 0 {
        row_style.fg(if team.is_my_team {
            Color::Yellow
        } else {
            Color::Green
        })
    } else if team.run_diff < 0 {
        row_style.fg(Color::Red)
    } else {
        row_style
    };

    Row::new(vec![
        Cell::from(format!(" {} {}", team.abbrev, team.team_name)).style(row_style),
        Cell::from(format!("{:>2}", team.wins)).style(row_style),
        Cell::from(format!("{:>2}", team.losses)).style(row_style),
        Cell::from(format!("{:>5.3}", team.pct)).style(row_style),
        Cell::from(format!("{:>5}", team.gb)).style(row_style),
        Cell::from(format!("{:>5}", diff_str)).style(diff_style),
        Cell::from(format!("{:>5}", team.home)).style(row_style),
        Cell::from(format!("{:>5}", team.away)).style(row_style),
        Cell::from(format!("{:>4}", team.last8)).style(row_style),
        Cell::from(format!("{:>4}", team.streak)).style(row_style),
    ])
}

fn standings_header() -> Row<'static> {
    Row::new(vec![
        Cell::from("Team"),
        Cell::from(" W"),
        Cell::from(" L"),
        Cell::from("  Pct"),
        Cell::from("   GB"),
        Cell::from("  Diff"),
        Cell::from("  Home"),
        Cell::from("  Away"),
        Cell::from("  L8"),
        Cell::from("  Strk"),
    ])
    .style(
        Style::default()
            .add_modifier(Modifier::BOLD)
            .fg(Color::White),
    )
}

fn standings_widths() -> [Constraint; 10] {
    [
        Constraint::Length(24),
        Constraint::Length(3),
        Constraint::Length(3),
        Constraint::Length(6),
        Constraint::Length(6),
        Constraint::Length(6),
        Constraint::Length(6),
        Constraint::Length(6),
        Constraint::Length(5),
        Constraint::Length(5),
    ]
}
