use std::io::{self, Write};

use crossterm::{
    cursor::MoveTo,
    event::{
        self, Event, KeyCode, KeyEvent,
        KeyEventKind::{Press, Repeat},
        KeyModifiers,
    },
    style::{Print, PrintStyledContent, Stylize},
    terminal::{Clear, ClearType},
    QueueableCommand,
};
use falling_tetromino_engine::Stat;

use crate::{
    fmt_helpers::fmt_duration,
    game_renderers::TetroTUIRenderer,
    game_restoration::{EncodedInputHistory, GameRestorationData},
    tui_menus::{title_bar, Menu, MenuUpdate},
    Application, ScoreEntry, ScoreEntrySorting,
};

impl<T: Write> Application<T> {
    #[allow(clippy::len_zero)]
    pub fn run_menu_scores_and_replays(
        &mut self,
        cursor_pos: &mut usize,
        camera_pos: &mut usize,
    ) -> io::Result<MenuUpdate> {
        let mut re_sort_scoreboard = true;
        let mut view_replay_error = String::new();
        const CAMERA_SIZE: usize = 11;
        const CAMERA_MARGIN: usize = 2;
        loop {
            let w_main = Self::W_MAIN.into();
            let (x_main, y_main) = Self::viewport_offset();
            let y_selection = Self::H_MAIN / 5;
            self.term
                .queue(Clear(ClearType::All))?
                .queue(MoveTo(x_main, y_main + y_selection))?
                .queue(PrintStyledContent(
                    format!("{:^w_main$}", "* Scores and Replays *").bold(),
                ))?
                .queue(MoveTo(x_main, y_main + y_selection + 2))?
                .queue(Print(format!("{:^w_main$}", title_bar(&self.settings))))?;

            let sorting = self.scores_and_replays.sorting;
            let fmt_stat = |p: &ScoreEntry| {
                let show_stat = match sorting {
                    ScoreEntrySorting::Chronological | ScoreEntrySorting::ModeDependent => {
                        p.game_meta_data.stat_and_desc_order.0
                    }
                    ScoreEntrySorting::GameStat(stat) => stat,
                };
                match show_stat {
                    Stat::TimeElapsed(_) => fmt_duration(p.time),
                    Stat::PiecesLocked(_) => {
                        format!("{} tetrominos", p.pieces.iter().sum::<u32>())
                    }
                    Stat::LinesCleared(_) => format!("{} lines", p.lineclears),
                    Stat::PointsScored(_) => format!("{} points", p.points),
                }
                // match show_stat {
                //     Stat::TimeElapsed(_) => format!("time: {}", fmt_duration(p.time_elapsed)),
                //     Stat::PiecesLocked(_) => format!("pieces: {}", p.pieces_locked.iter().sum::<u32>()),
                //     Stat::LinesCleared(_) => format!("lines: {}", p.lineclears),
                //     Stat::PointsScored(_) => format!("points: {}", p.points_scored),
                // }
            };
            let fmt_past_game = |(rank, (entry, opt_rep)): (
                usize,
                &(ScoreEntry, Option<GameRestorationData<EncodedInputHistory>>),
            )| {
                let lhs_annotation = match sorting {
                    ScoreEntrySorting::Chronological => entry.game_meta_data.datetime.to_owned(),
                    ScoreEntrySorting::ModeDependent | ScoreEntrySorting::GameStat(_) => {
                        format!("{rank: >2}{}", if rank == 1 { '#' } else { '.' })
                    }
                };
                format!(
                    "{} {}{} | {}{}",
                    lhs_annotation,
                    if entry.is_win { "" } else { "unf." },
                    entry.game_meta_data.title,
                    fmt_stat(entry),
                    if opt_rep.is_some() { "°" } else { "" }
                )
            };

            if self.scores_and_replays.entries.is_empty() {
                self.term
                    .queue(MoveTo(x_main, y_main + y_selection + 4 + 3))?
                    .queue(PrintStyledContent(
                        format!("{:^w_main$}", "The scoreboard is empty.").italic(),
                    ))?
                    .queue(MoveTo(x_main, y_main + y_selection + 4 + 4))?
                    .queue(PrintStyledContent(
                        format!("{:^w_main$}", "If you finish a game it will show up here!")
                            .italic(),
                    ))?;
            } else if re_sort_scoreboard {
                re_sort_scoreboard = false;
                let mut h = std::hash::DefaultHasher::new();
                std::hash::Hash::hash(&self.scores_and_replays.entries[*cursor_pos], &mut h);
                let old_hash = std::hash::Hasher::finish(&h);

                self.scores_and_replays.sort();

                // let d_pos = cursor_pos.saturating_sub(*camera_pos);
                *cursor_pos = self
                    .scores_and_replays
                    .entries
                    .iter()
                    .enumerate()
                    .find_map(|(i, entry)| {
                        let mut h = std::hash::DefaultHasher::new();
                        std::hash::Hash::hash(entry, &mut h);
                        let new_hash = std::hash::Hasher::finish(&h);
                        old_hash.eq(&new_hash).then_some(i)
                    })
                    .unwrap_or(*cursor_pos);
                // *camera_pos = cursor_pos.saturating_sub(d_pos);
                *camera_pos = cursor_pos.saturating_sub(CAMERA_SIZE / 2).min(
                    self.scores_and_replays
                        .entries
                        .len()
                        .saturating_sub(CAMERA_SIZE),
                );
            }

            for (i, entry) in self
                .scores_and_replays
                .entries
                .iter()
                .scan((1, None), |(i, prev_title), e| {
                    if Some(&e.0.game_meta_data.title) != prev_title.as_ref() {
                        *prev_title = Some(e.0.game_meta_data.title.clone());
                        *i = 1;
                    } else {
                        *i += 1;
                    }
                    Some((*i, e))
                })
                .skip(*camera_pos)
                .take(CAMERA_SIZE)
                .map(fmt_past_game)
                .enumerate()
            {
                self.term
                    .queue(MoveTo(
                        x_main,
                        y_main + y_selection + 4 + u16::try_from(i).unwrap(),
                    ))?
                    .queue(PrintStyledContent(if *cursor_pos == *camera_pos + i {
                        format!("{:<w_main$}", format!(">{}", entry)).bold()
                    } else {
                        format!("{:<w_main$}", format!(" {}", entry)).reset()
                    }))?;
            }

            let entries_left = self
                .scores_and_replays
                .entries
                .len()
                .saturating_sub(*camera_pos + CAMERA_SIZE);
            self.term
                .queue(MoveTo(
                    x_main,
                    y_main + y_selection + 4 + u16::try_from(CAMERA_SIZE).unwrap(),
                ))?
                .queue(PrintStyledContent(
                    format!(
                        "{:^w_main$}",
                        if entries_left > 0 {
                            format!("... +{entries_left} more")
                        } else {
                            "".to_owned()
                        }
                    )
                    .italic(),
                ))?;
            self.term
                .queue(MoveTo(
                    x_main,
                    y_main + y_selection + 4 + u16::try_from(CAMERA_SIZE).unwrap() + 1,
                ))?
                .queue(PrintStyledContent(
                    format!(
                        "{:^w_main$}",
                        format!("(Order = {} [←|→])", self.scores_and_replays.sorting)
                    )
                    .italic(),
                ))?;
            self.term
                .queue(MoveTo(
                    x_main,
                    y_main + y_selection + 4 + u16::try_from(CAMERA_SIZE).unwrap() + 2,
                ))?
                .queue(PrintStyledContent(
                    format!(
                        "{:^w_main$}",
                        "(Controls: [↓|↑]=scroll [Del]=delete [Enter]=replay°)"
                    )
                    .italic(),
                ))?;
            if !view_replay_error.is_empty() {
                self.term
                    .queue(MoveTo(
                        x_main,
                        y_main + y_selection + 4 + u16::try_from(CAMERA_SIZE).unwrap() + 3,
                    ))?
                    .queue(PrintStyledContent(
                        format!(
                            "{:^w_main$}",
                            format!("Error loading replay: {view_replay_error}")
                        )
                        .italic(),
                    ))?;
            }

            self.term.flush()?;

            // Wait for new input.
            match event::read()? {
                // Quit menu.
                Event::Key(KeyEvent {
                    code: KeyCode::Char('c' | 'C'),
                    modifiers: KeyModifiers::CONTROL,
                    kind: Press | Repeat,
                    state: _,
                }) => break Ok(MenuUpdate::Push(Menu::Quit)),
                Event::Key(KeyEvent {
                    code: KeyCode::Esc | KeyCode::Char('q' | 'Q') | KeyCode::Backspace,
                    kind: Press,
                    ..
                }) => break Ok(MenuUpdate::Pop),

                // Move selector up.
                Event::Key(KeyEvent {
                    code: KeyCode::Up | KeyCode::Char('k' | 'K'),
                    kind: kind @ (Press | Repeat),
                    ..
                }) if self.scores_and_replays.entries.len() > 0 => {
                    // We allow wrapping cursor pos, but only on manual presses (if detectable).
                    if 0 < *cursor_pos || kind == Press {
                        // Cursor pos possibly wraps back down.
                        *cursor_pos += self.scores_and_replays.entries.len() - 1;
                        *cursor_pos %= self.scores_and_replays.entries.len();
                        // If it does, then manually reset camera to bottom of scoreboard.
                        if *cursor_pos == self.scores_and_replays.entries.len() - 1 {
                            *camera_pos = self
                                .scores_and_replays
                                .entries
                                .len()
                                .saturating_sub(CAMERA_SIZE);
                        // Otherwise cursor just moved normally, and we may have to adapt camera (unless it hit scoreboard end).
                        } else if 0 < *camera_pos && *cursor_pos < *camera_pos + CAMERA_MARGIN {
                            *camera_pos -= 1;
                        }
                    }
                }

                // Move selector top.
                Event::Key(KeyEvent {
                    code: KeyCode::Home,
                    kind: Press | Repeat,
                    ..
                }) if self.scores_and_replays.entries.len() > 0 => {
                    *cursor_pos = 0;
                    *camera_pos = 0;
                }

                // Move selector down.
                Event::Key(KeyEvent {
                    code: KeyCode::Down | KeyCode::Char('j' | 'J'),
                    kind: kind @ (Press | Repeat),
                    ..
                }) if self.scores_and_replays.entries.len() > 0 => {
                    // We allow wrapping cursor pos, but only on manual presses (if detectable).
                    if *cursor_pos < self.scores_and_replays.entries.len() - 1 || kind == Press {
                        // Cursor pos possibly wraps back up.
                        *cursor_pos += 1;
                        *cursor_pos %= self.scores_and_replays.entries.len();
                        // If it does, then manually reset camera to bottom of scoreboard.
                        if *cursor_pos == 0 {
                            *camera_pos = 0;
                        // Otherwise cursor just moved normally, and we may have to adapt camera (unless it hit scoreboard end).
                        } else if *camera_pos + CAMERA_SIZE - CAMERA_MARGIN <= *cursor_pos
                            && *camera_pos
                                < self
                                    .scores_and_replays
                                    .entries
                                    .len()
                                    .saturating_sub(CAMERA_SIZE)
                        {
                            *camera_pos += 1;
                        }
                    }
                }

                // Move selector bottom.
                Event::Key(KeyEvent {
                    code: KeyCode::End,
                    kind: Press | Repeat,
                    ..
                }) if self.scores_and_replays.entries.len() > 0 => {
                    *cursor_pos = self.scores_and_replays.entries.len() - 1;
                    *camera_pos = self
                        .scores_and_replays
                        .entries
                        .len()
                        .saturating_sub(CAMERA_SIZE);
                }

                Event::Key(KeyEvent {
                    code: KeyCode::Left | KeyCode::Char('h' | 'H'),
                    kind: Press | Repeat,
                    ..
                }) => {
                    self.scores_and_replays.sorting = match self.scores_and_replays.sorting {
                        ScoreEntrySorting::Chronological => ScoreEntrySorting::ModeDependent,
                        ScoreEntrySorting::ModeDependent => {
                            ScoreEntrySorting::GameStat(Stat::LinesCleared(0))
                        }
                        ScoreEntrySorting::GameStat(Stat::LinesCleared(_)) => {
                            ScoreEntrySorting::GameStat(Stat::PiecesLocked(0))
                        }
                        ScoreEntrySorting::GameStat(Stat::PiecesLocked(_)) => {
                            ScoreEntrySorting::GameStat(Stat::PointsScored(0))
                        }
                        ScoreEntrySorting::GameStat(Stat::PointsScored(_)) => {
                            ScoreEntrySorting::GameStat(Stat::TimeElapsed(Default::default()))
                        }
                        ScoreEntrySorting::GameStat(Stat::TimeElapsed(_)) => {
                            ScoreEntrySorting::Chronological
                        }
                    };
                    re_sort_scoreboard = true;
                }

                Event::Key(KeyEvent {
                    code: KeyCode::Right | KeyCode::Char('l' | 'L'),
                    kind: Press | Repeat,
                    ..
                }) => {
                    self.scores_and_replays.sorting = match self.scores_and_replays.sorting {
                        ScoreEntrySorting::Chronological => {
                            ScoreEntrySorting::GameStat(Stat::TimeElapsed(Default::default()))
                        }
                        ScoreEntrySorting::ModeDependent => ScoreEntrySorting::Chronological,
                        ScoreEntrySorting::GameStat(Stat::LinesCleared(_)) => {
                            ScoreEntrySorting::ModeDependent
                        }
                        ScoreEntrySorting::GameStat(Stat::PiecesLocked(_)) => {
                            ScoreEntrySorting::GameStat(Stat::LinesCleared(0))
                        }
                        ScoreEntrySorting::GameStat(Stat::PointsScored(_)) => {
                            ScoreEntrySorting::GameStat(Stat::PiecesLocked(0))
                        }
                        ScoreEntrySorting::GameStat(Stat::TimeElapsed(_)) => {
                            ScoreEntrySorting::GameStat(Stat::PointsScored(0))
                        }
                    };
                    re_sort_scoreboard = true;
                }

                // Delete entire slot.
                Event::Key(KeyEvent {
                    code: KeyCode::Delete | KeyCode::Char('d' | 'D'),
                    kind: Press | Repeat,
                    modifiers,
                    ..
                }) if self.scores_and_replays.entries.len() > 0 => {
                    if modifiers.contains(KeyModifiers::ALT) {
                        self.scores_and_replays.entries[*cursor_pos].1.take();
                    } else {
                        self.scores_and_replays.entries.remove(*cursor_pos);
                        if 0 < *cursor_pos && *cursor_pos == self.scores_and_replays.entries.len() {
                            *cursor_pos -= 1;
                            *camera_pos = camera_pos.saturating_sub(1);
                        }
                    }
                }

                // Load slot as replay.
                Event::Key(KeyEvent {
                    code: KeyCode::Enter | KeyCode::Char('e' | 'E'),
                    kind: Press | Repeat,
                    ..
                }) if self.scores_and_replays.entries.len() > 0 => {
                    let (score_entry, opt_restoration_data) =
                        &self.scores_and_replays.entries[*cursor_pos];
                    if let Some(game_restoration_data) = opt_restoration_data {
                        match game_restoration_data.clone().try_decode() {
                            Ok(game_restoration_data) => {
                                let game_meta_data = score_entry.game_meta_data.clone();
                                break Ok(MenuUpdate::Push(Menu::ReplayGame {
                                    game_restoration_data: Box::new(game_restoration_data),
                                    game_meta_data,
                                    replay_length: score_entry.time,
                                    game_renderer: TetroTUIRenderer::with_number(
                                        self.temp_data.renderer_selected,
                                    )
                                    .into(),
                                }));
                            }
                            Err(e) => view_replay_error = e,
                        }
                    } else {
                        view_replay_error =
                            format!("no data for {}", score_entry.game_meta_data.title)
                    }
                }

                // Other event: don't care.
                _ => {}
            };
        }
    }
}
