use std::collections::HashMap;

use chrono::NaiveDateTime;
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 tokio::sync::mpsc;

use crate::app::{AppMessage, NotifyLevel};
use crate::config::Settings;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncState {
    Never,
    Syncing,
    Success,
    Error,
}

pub struct SyncStatusInfo {
    pub entity_type: String,
    pub last_sync: Option<NaiveDateTime>,
    pub count: Option<i64>,
}

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

    // Roster counts
    pub majors_count: usize,
    pub minors_count: usize,
    pub il_count: usize,
    pub swar_total: f64,

    // Limits
    pub swar_cap: Option<f64>,    // from Team.salary_cap (DB)
    pub major_slots: usize,
    pub minor_slots: usize,

    // Roster health
    pub team_name: String,
    pub position_counts: HashMap<String, usize>,
    pub il_players: Vec<(String, Option<String>, Option<String>)>, // (name, pos, il_return)

    // Card coverage
    pub batters_missing_cards: Vec<String>,
    pub pitchers_missing_cards: Vec<String>,

    // Sync
    pub sync_state: SyncState,
    pub sync_message: String,
    pub sync_statuses: Vec<SyncStatusInfo>,
}

impl DashboardState {
    pub fn new(team_abbrev: String, season: i64, settings: &Settings) -> Self {
        Self {
            team_abbrev,
            season,
            majors_count: 0,
            minors_count: 0,
            il_count: 0,
            swar_total: 0.0,
            swar_cap: None,
            major_slots: settings.team.major_league_slots,
            minor_slots: settings.team.minor_league_slots,
            team_name: String::new(),
            position_counts: HashMap::new(),
            il_players: Vec::new(),
            batters_missing_cards: Vec::new(),
            pitchers_missing_cards: Vec::new(),
            sync_state: SyncState::Never,
            sync_message: String::new(),
            sync_statuses: Vec::new(),
        }
    }

    // =========================================================================
    // Mount
    // =========================================================================

    pub fn mount(&mut self, pool: &SqlitePool, tx: &mpsc::UnboundedSender<AppMessage>) {
        // Load roster
        let pool_c = pool.clone();
        let tx_c = tx.clone();
        let abbrev = self.team_abbrev.clone();
        let season = self.season;
        tokio::spawn(async move {
            match crate::db::queries::get_my_roster(&pool_c, &abbrev, season).await {
                Ok(roster) => {
                    let _ = tx_c.send(AppMessage::RosterLoaded(roster));
                }
                Err(e) => {
                    tracing::error!("Failed to load roster: {e}");
                    let _ = tx_c.send(AppMessage::Notify(
                        format!("Failed to load roster: {e}"),
                        NotifyLevel::Error,
                    ));
                }
            }
        });

        // Load team info (full name)
        let pool_c = pool.clone();
        let tx_c = tx.clone();
        let abbrev = self.team_abbrev.clone();
        let season = self.season;
        tokio::spawn(async move {
            if let Ok(Some(team)) =
                crate::db::queries::get_team_by_abbrev(&pool_c, &abbrev, season).await
            {
                let _ = tx_c.send(AppMessage::TeamInfoLoaded(team.short_name, team.salary_cap));
            }
        });

        // Load missing 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 {
            if let Ok(Some(team)) =
                crate::db::queries::get_team_by_abbrev(&pool_c, &abbrev, season).await
            {
                let batters = crate::db::queries::get_players_missing_batter_cards(
                    &pool_c, team.id, season,
                )
                .await
                .unwrap_or_default()
                .into_iter()
                .map(|p| {
                    let pos = p.pos_1.as_deref().unwrap_or("?");
                    format!("{} ({})", p.name, pos)
                })
                .collect();

                let pitchers = crate::db::queries::get_players_missing_pitcher_cards(
                    &pool_c, team.id, season,
                )
                .await
                .unwrap_or_default()
                .into_iter()
                .map(|p| {
                    let pos = p.pos_1.as_deref().unwrap_or("?");
                    format!("{} ({})", p.name, pos)
                })
                .collect();

                let _ = tx_c.send(AppMessage::MissingCardsLoaded { batters, pitchers });
            }
        });

        // Load sync statuses
        let pool_c = pool.clone();
        let tx_c = tx.clone();
        tokio::spawn(async move {
            if let Ok(statuses) = crate::db::queries::get_all_sync_statuses(&pool_c).await {
                let _ = tx_c.send(AppMessage::SyncStatusesLoaded(statuses));
            }
        });
    }

    // =========================================================================
    // Key handling
    // =========================================================================

    pub fn handle_key(
        &mut self,
        key: KeyEvent,
        pool: &SqlitePool,
        settings: &Settings,
        tx: &mpsc::UnboundedSender<AppMessage>,
    ) {
        if let KeyCode::Char('s') = key.code {
            self.trigger_sync(pool, settings, tx);
        }
    }

    fn trigger_sync(
        &mut self,
        pool: &SqlitePool,
        settings: &Settings,
        tx: &mpsc::UnboundedSender<AppMessage>,
    ) {
        if self.sync_state == SyncState::Syncing {
            return;
        }
        self.sync_state = SyncState::Syncing;
        self.sync_message = "Syncing...".to_string();
        let pool = pool.clone();
        let settings = settings.clone();
        let tx = tx.clone();
        tokio::spawn(async move {
            let _ = tx.send(AppMessage::SyncStarted);
            let result = crate::api::sync::sync_all(&pool, &settings).await;
            let _ = tx.send(AppMessage::SyncComplete(result));
        });
    }

    // =========================================================================
    // Message handling
    // =========================================================================

    pub fn handle_message(
        &mut self,
        msg: AppMessage,
        pool: &SqlitePool,
        tx: &mpsc::UnboundedSender<AppMessage>,
    ) {
        match msg {
            AppMessage::RosterLoaded(roster) => {
                self.majors_count = roster.majors.len();
                self.minors_count = roster.minors.len();
                self.il_count = roster.il.len();
                self.swar_total = roster.majors.iter().filter_map(|p| p.swar).sum();

                // Compute position counts from majors roster
                let mut counts: HashMap<String, usize> = HashMap::new();
                for player in &roster.majors {
                    if let Some(pos) = &player.pos_1 {
                        *counts.entry(pos.clone()).or_insert(0) += 1;
                    }
                }
                self.position_counts = counts;

                // Extract IL player details
                self.il_players = roster
                    .il
                    .iter()
                    .map(|p| {
                        (
                            p.name.clone(),
                            p.pos_1.clone(),
                            p.il_return.clone(),
                        )
                    })
                    .collect();
            }
            AppMessage::TeamInfoLoaded(name, salary_cap) => {
                self.team_name = name;
                self.swar_cap = salary_cap;
            }
            AppMessage::MissingCardsLoaded { batters, pitchers } => {
                self.batters_missing_cards = batters;
                self.pitchers_missing_cards = pitchers;
            }
            AppMessage::SyncStatusesLoaded(statuses) => {
                self.sync_statuses = statuses
                    .into_iter()
                    .map(|s| SyncStatusInfo {
                        entity_type: s.entity_type,
                        last_sync: s.last_sync,
                        count: s.last_sync_count,
                    })
                    .collect();
                // Show loaded sync status (don't override if currently syncing)
                if self.sync_state == SyncState::Never && !self.sync_statuses.is_empty() {
                    self.sync_state = SyncState::Success;
                }
            }
            AppMessage::SyncStarted => {
                self.sync_state = SyncState::Syncing;
                self.sync_message = "Syncing...".to_string();
            }
            AppMessage::SyncComplete(result) => match result {
                Ok(r) => {
                    self.sync_state = SyncState::Success;
                    self.sync_message = format!(
                        "Synced {} teams, {} players, {} transactions",
                        r.teams, r.players, r.transactions
                    );
                    // Update sync status display with fresh data
                    let now = chrono::Utc::now().naive_utc();
                    self.sync_statuses = vec![
                        SyncStatusInfo {
                            entity_type: "teams".to_string(),
                            last_sync: Some(now),
                            count: Some(r.teams),
                        },
                        SyncStatusInfo {
                            entity_type: "players".to_string(),
                            last_sync: Some(now),
                            count: Some(r.players),
                        },
                        SyncStatusInfo {
                            entity_type: "transactions".to_string(),
                            last_sync: Some(now),
                            count: Some(r.transactions),
                        },
                    ];
                    // Re-fetch roster, team info, and card data to reflect sync changes
                    self.mount(pool, tx);
                }
                Err(e) => {
                    tracing::error!("Sync failed: {e}");
                    self.sync_state = SyncState::Error;
                    self.sync_message = format!("Sync failed: {e}");
                }
            },
            _ => {}
        }
    }

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

    pub fn render(&self, frame: &mut Frame, area: Rect, tick_count: u64) {
        let chunks = Layout::vertical([
            Constraint::Length(3), // title
            Constraint::Length(5), // summary cards
            Constraint::Length(4), // position coverage
            Constraint::Length(6), // IL players
            Constraint::Length(6), // card warnings
            Constraint::Length(4), // sync status
            Constraint::Min(0),   // key hints
        ])
        .split(area);

        self.render_title(frame, chunks[0]);
        self.render_summary_cards(frame, chunks[1]);
        self.render_position_coverage(frame, chunks[2]);
        self.render_il_players(frame, chunks[3]);
        self.render_card_warnings(frame, chunks[4]);
        self.render_sync_status(frame, chunks[5], tick_count);
        self.render_hints(frame, chunks[6]);
    }

    fn render_title(&self, frame: &mut Frame, area: Rect) {
        let display_name = if self.team_name.is_empty() {
            &self.team_abbrev
        } else {
            &self.team_name
        };
        let title = Paragraph::new(format!(
            "SBA Scout  --  {}  --  Season {}",
            display_name, self.season
        ))
        .block(Block::default().borders(Borders::ALL))
        .alignment(Alignment::Center);
        frame.render_widget(title, area);
    }

    fn render_summary_cards(&self, frame: &mut Frame, area: Rect) {
        let card_chunks = Layout::horizontal([
            Constraint::Ratio(1, 4),
            Constraint::Ratio(1, 4),
            Constraint::Ratio(1, 4),
            Constraint::Ratio(1, 4),
        ])
        .split(area);

        // Majors
        let majors_text = format!("{}/{}", self.majors_count, self.major_slots);
        let majors = Paragraph::new(majors_text)
            .block(Block::default().borders(Borders::ALL).title("Majors"))
            .alignment(Alignment::Center);
        frame.render_widget(majors, card_chunks[0]);

        // Minors
        let minors_text = format!("{}/{}", self.minors_count, self.minor_slots);
        let minors = Paragraph::new(minors_text)
            .block(Block::default().borders(Borders::ALL).title("Minors"))
            .alignment(Alignment::Center);
        frame.render_widget(minors, card_chunks[1]);

        // IL
        let il_style = if self.il_count > 0 {
            Style::default().fg(Color::Yellow)
        } else {
            Style::default().fg(Color::Green)
        };
        let il = Paragraph::new(self.il_count.to_string())
            .style(il_style)
            .block(Block::default().borders(Borders::ALL).title("IL"))
            .alignment(Alignment::Center);
        frame.render_widget(il, card_chunks[2]);

        // sWAR with budget bar
        let swar_lines = if let Some(cap) = self.swar_cap {
            let pct = if cap > 0.0 {
                self.swar_total / cap * 100.0
            } else {
                0.0
            };
            let swar_color = if pct >= 97.0 {
                Color::Green
            } else if pct >= 93.0 {
                Color::LightGreen
            } else if pct >= 90.0 {
                Color::Yellow
            } else {
                Color::Red
            };
            // Bar shows only the 90-100% range (each char = 1%)
            let bar_width = 10;
            let bar_pct = (pct - 90.0).clamp(0.0, 10.0);
            let filled = bar_pct.round() as usize;
            let bar: String = format!(
                "{}{}",
                "\u{2588}".repeat(filled),
                "\u{2591}".repeat(bar_width - filled)
            );
            vec![
                Line::from(Span::styled(
                    format!("{:.2}/{:.2} ({:.0}%)", self.swar_total, cap, pct),
                    Style::default().fg(swar_color),
                )),
                Line::from(Span::styled(bar, Style::default().fg(swar_color))),
            ]
        } else {
            vec![Line::from(Span::styled(
                format!("{:.2}", self.swar_total),
                Style::default(),
            ))]
        };
        let swar = Paragraph::new(swar_lines)
            .block(Block::default().borders(Borders::ALL).title("sWAR"))
            .alignment(Alignment::Center);
        frame.render_widget(swar, card_chunks[3]);
    }

    fn render_position_coverage(&self, frame: &mut Frame, area: Rect) {
        let batter_positions = ["C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "DH"];
        let pitcher_positions = ["SP", "RP", "CP"];

        let mut spans: Vec<Span> = Vec::new();
        for (i, pos) in batter_positions.iter().enumerate() {
            let count = self.position_counts.get(*pos).copied().unwrap_or(0);
            let style = if count == 0 {
                Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
            } else {
                Style::default().fg(Color::White)
            };
            if i > 0 {
                spans.push(Span::raw("  "));
            }
            spans.push(Span::styled(format!("{}:{}", pos, count), style));
        }

        let mut pitcher_spans: Vec<Span> = Vec::new();
        for (i, pos) in pitcher_positions.iter().enumerate() {
            let count = self.position_counts.get(*pos).copied().unwrap_or(0);
            let style = if count == 0 {
                Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
            } else {
                Style::default().fg(Color::White)
            };
            if i > 0 {
                pitcher_spans.push(Span::raw("  "));
            }
            pitcher_spans.push(Span::styled(format!("{}:{}", pos, count), style));
        }

        let lines = vec![
            Line::from(spans),
            Line::from(pitcher_spans),
        ];
        let widget = Paragraph::new(lines)
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .title("Position Coverage"),
            );
        frame.render_widget(widget, area);
    }

    fn render_il_players(&self, frame: &mut Frame, area: Rect) {
        let lines: Vec<Line> = if self.il_players.is_empty() {
            vec![Line::from(Span::styled(
                "No players on IL",
                Style::default().fg(Color::Green),
            ))]
        } else {
            self.il_players
                .iter()
                .take(4) // cap display
                .map(|(name, pos, il_return)| {
                    let pos_str = pos.as_deref().unwrap_or("?");
                    let return_str = match il_return {
                        Some(r) if !r.is_empty() => format!(" — returns {}", r),
                        _ => String::new(),
                    };
                    Line::from(vec![
                        Span::styled(
                            format!("  {} ({})", name, pos_str),
                            Style::default().fg(Color::Yellow),
                        ),
                        Span::styled(return_str, Style::default().fg(Color::DarkGray)),
                    ])
                })
                .collect()
        };

        let widget = Paragraph::new(lines).block(
            Block::default()
                .borders(Borders::ALL)
                .title("IL Players"),
        );
        frame.render_widget(widget, area);
    }

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

        // Batters missing cards
        let batter_lines: Vec<Line> = if self.batters_missing_cards.is_empty() {
            vec![Line::from(Span::styled(
                "All cards imported",
                Style::default().fg(Color::Green),
            ))]
        } else {
            self.batters_missing_cards
                .iter()
                .take(4)
                .map(|name| {
                    Line::from(Span::styled(
                        format!("  {}", name),
                        Style::default().fg(Color::Red),
                    ))
                })
                .collect()
        };
        let batter_widget = Paragraph::new(batter_lines).block(
            Block::default()
                .borders(Borders::ALL)
                .title(format!(
                    "Batters Missing Cards ({})",
                    self.batters_missing_cards.len()
                )),
        );
        frame.render_widget(batter_widget, cols[0]);

        // Pitchers missing cards
        let pitcher_lines: Vec<Line> = if self.pitchers_missing_cards.is_empty() {
            vec![Line::from(Span::styled(
                "All cards imported",
                Style::default().fg(Color::Green),
            ))]
        } else {
            self.pitchers_missing_cards
                .iter()
                .take(4)
                .map(|name| {
                    Line::from(Span::styled(
                        format!("  {}", name),
                        Style::default().fg(Color::Red),
                    ))
                })
                .collect()
        };
        let pitcher_widget = Paragraph::new(pitcher_lines).block(
            Block::default()
                .borders(Borders::ALL)
                .title(format!(
                    "Pitchers Missing Cards ({})",
                    self.pitchers_missing_cards.len()
                )),
        );
        frame.render_widget(pitcher_widget, cols[1]);
    }

    fn render_sync_status(&self, frame: &mut Frame, area: Rect, tick_count: u64) {
        let lines: Vec<Line> = if self.sync_state == SyncState::Syncing {
            let spinner = ['|', '/', '-', '\\'][(tick_count as usize / 2) % 4];
            vec![Line::from(Span::styled(
                format!("  {} {}", spinner, self.sync_message),
                Style::default(),
            ))]
        } else if self.sync_statuses.is_empty() {
            vec![Line::from(Span::styled(
                "  Never synced  [s] Sync now",
                Style::default().fg(Color::DarkGray),
            ))]
        } else {
            let mut spans: Vec<Span> = Vec::new();
            spans.push(Span::raw("  "));
            for (i, status) in self.sync_statuses.iter().enumerate() {
                if i > 0 {
                    spans.push(Span::styled(" · ", Style::default().fg(Color::DarkGray)));
                }
                let time_str = match &status.last_sync {
                    Some(dt) => format_relative_time(dt),
                    None => "never".to_string(),
                };
                let count_str = status
                    .count
                    .map(|c| format!(" ({})", c))
                    .unwrap_or_default();
                spans.push(Span::styled(
                    format!("{} {}{}", status.entity_type, time_str, count_str),
                    Style::default().fg(Color::White),
                ));
            }
            spans.push(Span::styled(
                "  [s] Sync",
                Style::default().fg(Color::DarkGray),
            ));

            let mut lines = vec![Line::from(spans)];
            // Show error or last sync message if present
            if self.sync_state == SyncState::Error {
                lines.push(Line::from(Span::styled(
                    format!("  {}", self.sync_message),
                    Style::default().fg(Color::Red),
                )));
            }
            lines
        };

        let border_style = match self.sync_state {
            SyncState::Error => Style::default().fg(Color::Red),
            _ => Style::default(),
        };
        let widget = Paragraph::new(lines).block(
            Block::default()
                .borders(Borders::ALL)
                .title("Sync")
                .border_style(border_style),
        );
        frame.render_widget(widget, area);
    }

    fn render_hints(&self, frame: &mut Frame, area: Rect) {
        let hints = Paragraph::new(" [s] Sync  [g] Gameday  [q] Quit")
            .style(Style::default().fg(Color::DarkGray));
        frame.render_widget(hints, area);
    }
}

fn format_relative_time(dt: &NaiveDateTime) -> String {
    let now = chrono::Utc::now().naive_utc();
    let diff = now.signed_duration_since(*dt);

    let secs = diff.num_seconds();
    if secs < 0 {
        return "just now".to_string();
    }
    if secs < 60 {
        return "just now".to_string();
    }
    let mins = secs / 60;
    if mins < 60 {
        return format!("{}m ago", mins);
    }
    let hours = mins / 60;
    if hours < 24 {
        return format!("{}h ago", hours);
    }
    let days = hours / 24;
    format!("{}d ago", days)
}
