use super::traits::{HelpContent, HelpPanel, Screen, SplitPanel};
use crate::core::constants::MIN_PLAYERS_PER_GAME;
use crate::core::team::Team;
use crate::image::spaceship::{SPACESHIP_IMAGE_HEIGHT, SPACESHIP_IMAGE_WIDTH};
use crate::types::{AppResult, HashMapWithResult};
use crate::ui::button::Button;
use crate::ui::clickable_list::ClickableListState;
use crate::ui::gif_map::GifMap;
use crate::ui::renders::{
    go_to_team_current_planet_button, render_challenge_button, render_spaceship_description,
};
use crate::ui::ui_callback::UiCallback;
use crate::ui::ui_frame::UiFrame;
use crate::ui::ui_key;
use crate::ui::ui_screen::{tab_link, UiTab};
use crate::ui::{
    constants::*,
    renders::{default_block, selectable_list},
    utils::img_to_lines,
};
use crate::{
    core::{
        position::{GamePosition, GamePositionUtils},
        skill::Rated,
        world::World,
    },
    image::game::floor_from_size,
    image::player::{PLAYER_IMAGE_HEIGHT, PLAYER_IMAGE_WIDTH},
    types::{PlayerId, TeamId},
};
use core::fmt::Debug;
use itertools::Itertools;
use ratatui::crossterm;
use ratatui::crossterm::event::KeyCode;
use ratatui::layout::Margin;
use ratatui::style::{Styled, Stylize};
use ratatui::text::Line;
use ratatui::{
    layout::{Alignment, Constraint, Layout},
    prelude::Rect,
    widgets::Paragraph,
};
use std::collections::HashMap;
use std::fmt::Display;
use std::sync::{LazyLock, Mutex};

const IMG_FRAME_WIDTH: u16 = 80;

static FLOOR_CACHE: LazyLock<Mutex<HashMap<u32, Vec<Line<'static>>>>> =
    LazyLock::new(|| Mutex::new(HashMap::new()));

fn floor_lines_for(width: u32) -> Vec<Line<'static>> {
    let mut cache = FLOOR_CACHE.lock().expect("floor cache mutex poisoned");
    cache
        .entry(width)
        .or_insert_with(|| img_to_lines(&floor_from_size(width, 2)))
        .clone()
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum TeamView {
    #[default]
    All,
    OpenToChallenge,
    Peers,
}

impl TeamView {
    const fn next(&self) -> Self {
        match self {
            Self::All => Self::OpenToChallenge,
            Self::OpenToChallenge => Self::Peers,
            Self::Peers => Self::All,
        }
    }

    const fn previous(&self) -> Self {
        match self {
            Self::All => Self::Peers,
            Self::OpenToChallenge => Self::All,
            Self::Peers => Self::OpenToChallenge,
        }
    }

    fn rule(&self, team: &Team, own_team: &Team) -> bool {
        match self {
            Self::All => true,
            Self::OpenToChallenge => {
                own_team.can_challenge_local_team(team).is_ok()
                    || own_team.can_challenge_network_team(team).is_ok()
            }
            Self::Peers => team.peer_id.is_some(),
        }
    }
}

impl Display for TeamView {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::All => write!(f, "All"),
            Self::OpenToChallenge => write!(f, "Open to challenge"),
            Self::Peers => write!(f, "From swarm"),
        }
    }
}

#[derive(Debug, Default)]
pub struct TeamListPanel {
    pub index: Option<usize>,
    pub player_index: usize,
    pub selected_player_id: PlayerId,
    pub selected_team_id: TeamId,
    pub team_ids: Vec<TeamId>,
    pub all_team_ids: Vec<TeamId>,
    view: TeamView,
    update_view: bool,
    current_team_players_length: usize,
    tick: usize,
    gif_map: GifMap,
    crews_list_state: ClickableListState,
}

impl TeamListPanel {
    pub fn new() -> Self {
        Self::default()
    }

    const fn next_player_index(&mut self) {
        if self.current_team_players_length == 0 {
            return;
        }
        let current_index = self.player_index;
        self.player_index = (current_index + 1) % self.current_team_players_length;
    }

    const fn previous_player_index(&mut self) {
        if self.current_team_players_length == 0 {
            return;
        }
        let current_index = self.player_index;
        self.player_index = (current_index + self.current_team_players_length - 1)
            % self.current_team_players_length;
    }

    fn build_left_panel(
        &mut self,
        frame: &mut UiFrame,
        world: &World,
        area: Rect,
    ) -> AppResult<()> {
        let split = Layout::vertical([
            Constraint::Length(3),
            Constraint::Length(3),
            Constraint::Length(3),
            Constraint::Fill(1),
        ])
        .split(area);

        let mut filter_all_button = Button::new(
            TeamView::All.to_string(),
            UiCallback::SetTeamPanelView {
                view: TeamView::All,
            },
        )
        .bold()
        .hover_text("View all crews.");

        let own_team = world.get_own_team()?;
        let num_open_to_challenge = world
            .teams
            .values()
            .filter(|team| {
                own_team.can_challenge_local_team(team).is_ok()
                    || own_team.can_challenge_network_team(team).is_ok()
            })
            .collect_vec()
            .len();
        let mut filter_challenge_button = Button::new(
            format!("{} ({})", TeamView::OpenToChallenge, num_open_to_challenge),
            UiCallback::SetTeamPanelView {
                view: TeamView::OpenToChallenge,
            },
        )
        .bold()
        .hover_text("View all crews that can be currently challenged to a game.");

        let mut filter_peers_button = Button::new(
            TeamView::Peers.to_string(),
            UiCallback::SetTeamPanelView {
                view: TeamView::Peers,
            },
        ).bold()

        .hover_text(
            "View all crews received from the network (i.e. crews controlled by other players online)."
                ,
        );
        match self.view {
            TeamView::All => filter_all_button.select(),
            TeamView::OpenToChallenge => filter_challenge_button.select(),
            TeamView::Peers => filter_peers_button.select(),
        }

        frame.render_interactive_widget(filter_all_button, split[0]);
        frame.render_interactive_widget(filter_challenge_button, split[1]);
        frame.render_interactive_widget(filter_peers_button, split[2]);

        if !self.team_ids.is_empty() {
            let mut options = vec![];
            for team_id in self.team_ids.iter() {
                let team = if let Some(team) = world.teams.get(team_id) {
                    team
                } else {
                    continue;
                };
                let mut style = UiStyle::DEFAULT;
                if team.id == world.own_team_id {
                    style = UiStyle::OWN_TEAM;
                } else if team.peer_id.is_some() {
                    style = UiStyle::NETWORK;
                }
                let text = format!(
                    "{:<MAX_NAME_LENGTH$} {}",
                    team.name,
                    world.team_rating(&team.id).unwrap_or_default().stars()
                );
                options.push((text, style));
            }
            let list = selectable_list(options);

            self.crews_list_state.select(self.index);
            frame.render_stateful_interactive_widget(
                list.block(default_block().title("Crews ↓/↑")),
                split[3],
                &mut self.crews_list_state,
            );
        } else {
            frame.render_widget(default_block().title("Crews"), split[3]);
        }

        Ok(())
    }

    fn build_right_panel(
        &mut self,
        frame: &mut UiFrame,
        world: &World,
        area: Rect,
    ) -> AppResult<()> {
        let index = if let Some(index) = self.index {
            index
        } else {
            frame.render_widget(default_block(), area);
            return Ok(());
        };

        let team = world.teams.get_or_err(&self.team_ids[index])?;
        self.current_team_players_length = team.player_ids.len();
        let vertical_split = Layout::vertical([
            Constraint::Length(PLAYER_IMAGE_HEIGHT as u16 / 2), //players
            Constraint::Length(1),                              //floor
            Constraint::Length(1),                              //name
            Constraint::Length(2),                              //rating
            Constraint::Fill(1),                                //bottom
        ])
        .split(area);

        frame.render_widget(
            Paragraph::new(floor_lines_for(area.width as u32)).centered(),
            vertical_split[1].inner(Margin {
                horizontal: 1,
                vertical: 0,
            }),
        );

        let constraints = [
            Constraint::Fill(1),
            Constraint::Min(PLAYER_IMAGE_WIDTH as u16 + 2),
            Constraint::Min(PLAYER_IMAGE_WIDTH as u16 + 2),
            Constraint::Min(PLAYER_IMAGE_WIDTH as u16 + 2),
            Constraint::Min(PLAYER_IMAGE_WIDTH as u16 + 2),
            Constraint::Min(PLAYER_IMAGE_WIDTH as u16 + 2),
            Constraint::Fill(1),
        ];

        let player_img_split = Layout::horizontal(constraints).split(vertical_split[0]);
        let player_name_split = Layout::horizontal(constraints).split(vertical_split[2]);
        let player_rating_split = Layout::horizontal(constraints).split(vertical_split[3]);

        for i in 0..MIN_PLAYERS_PER_GAME {
            if i >= team.player_ids.len() {
                break;
            }

            // recalculate button area: to offset the missing box of the radiobutton
            // we add an extra row to top and bottom
            let button_area = Rect::new(
                player_img_split[i + 1].x,
                player_img_split[i + 1].y,
                player_img_split[i + 1].width,
                player_img_split[i + 1].height + 1,
            );

            let mut button = Button::no_box(
                "",
                UiCallback::GoToPlayer {
                    player_id: team.player_ids[i],
                },
            )
            .set_hover_style(UiStyle::SELECTED);
            if self.player_index == i {
                button = button.set_style(UiStyle::SELECTED);
            }

            frame.render_interactive_widget(button, button_area);

            let player = world.players.get_or_err(&team.player_ids[i])?;

            if let Ok(lines) = self.gif_map.player_frame_lines(player, self.tick) {
                frame.render_widget(Paragraph::new(lines).centered(), player_img_split[i + 1]);
            }

            frame.render_widget(
                Paragraph::new(player.info.short_name()).centered(),
                player_name_split[i + 1],
            );

            frame.render_widget(
                Paragraph::new(format!(
                    "{} {}",
                    (i as GamePosition).as_role(),
                    player.position_rating(i as GamePosition).stars()
                ))
                .centered(),
                player_rating_split[i + 1],
            );
        }

        let bottom_split = Layout::horizontal([
            Constraint::Length(44),
            Constraint::Min(SPACESHIP_IMAGE_WIDTH as u16 + 2 + 34),
        ])
        .split(vertical_split[4]);

        let bench_out_split =
            Layout::vertical([Constraint::Length(6), Constraint::Fill(1)]).split(bottom_split[0]);

        frame.render_widget(default_block().title("Bench"), bench_out_split[0]);
        frame.render_widget(default_block().title("Out"), bench_out_split[1]);

        if team.player_ids.len() > MIN_PLAYERS_PER_GAME {
            let out_row_split = Layout::vertical([
                Constraint::Length(4),
                Constraint::Length(4),
                Constraint::Fill(1),
            ])
            .split(bench_out_split[1].inner(Margin {
                horizontal: 2,
                vertical: 1,
            }));

            let row_splits = [
                Layout::horizontal([Constraint::Length(20), Constraint::Length(20)]).split(
                    bench_out_split[0].inner(Margin {
                        horizontal: 2,
                        vertical: 1,
                    }),
                ),
                Layout::horizontal([Constraint::Length(20), Constraint::Length(20)])
                    .split(out_row_split[0]),
                Layout::horizontal([Constraint::Length(20), Constraint::Length(20)])
                    .split(out_row_split[1]),
            ];

            for (i, player_id) in team
                .player_ids
                .iter()
                .skip(MIN_PLAYERS_PER_GAME)
                .enumerate()
            {
                if let Some(player) = world.players.get(player_id) {
                    let info = format!("{}\n", player.info.short_name());
                    let best_role = player.best_position();

                    let role_info = format!(
                        "{:<2} {:<5}",
                        best_role.as_role(),
                        player.position_rating(best_role).stars()
                    );
                    let mut button = Button::new(
                        format!("{info}{role_info}"),
                        UiCallback::GoToPlayer {
                            player_id: team.player_ids[i + MIN_PLAYERS_PER_GAME],
                        },
                    );

                    if self.player_index == i + MIN_PLAYERS_PER_GAME {
                        button = button
                            .set_style(UiStyle::SELECTED)
                            .set_hover_style(UiStyle::SELECTED);
                    }

                    let row = i / 2;
                    let column = i % 2;
                    let area = row_splits[row][column];

                    frame.render_interactive_widget(button, area);
                }
            }
        }

        let ship_buttons_split = Layout::vertical([
            Constraint::Min(SPACESHIP_IMAGE_HEIGHT as u16 / 2 + 1), // ship
            Constraint::Length(3),                                  //button
        ])
        .split(bottom_split[1].inner(Margin {
            horizontal: 1,
            vertical: 1,
        }));

        let button_split = Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
            .split(ship_buttons_split[1]);

        match go_to_team_current_planet_button(world, &team.id) {
            Ok(go_to_team_current_planet_button) => {
                frame.render_interactive_widget(go_to_team_current_planet_button, button_split[0])
            }
            Err(e) => log::error!("go_to_team_current_planet_button error: {e} "),
        }

        if team.id != world.own_team_id {
            render_challenge_button(world, team, true, frame, button_split[1])?;
        }

        render_spaceship_description(
            team,
            world,
            world.team_rating(&team.id).unwrap_or_default(),
            false,
            world.teams.contains_key(&team.id),
            &mut self.gif_map,
            self.tick,
            frame,
            bottom_split[1],
        );

        let box_split = Layout::vertical([
            Constraint::Length(PLAYER_IMAGE_HEIGHT as u16 / 2 + 4),
            Constraint::Fill(1),
        ])
        .split(area);

        frame.render_widget(
            default_block()
                .title(format!(
                    "{} {}/{}",
                    team.name,
                    ui_key::NEXT_SELECTION,
                    ui_key::PREVIOUS_SELECTION
                ))
                .title_alignment(Alignment::Left),
            box_split[0],
        );

        Ok(())
    }

    pub const fn set_view(&mut self, filter: TeamView) {
        self.view = filter;
        self.update_view = true;
        self.index = None;
        self.crews_list_state.reset_offset();
    }

    pub const fn reset_view(&mut self) {
        self.set_view(TeamView::All);
    }
}

impl Screen for TeamListPanel {
    fn tick(&mut self) {
        self.tick += 1;
    }

    fn update(&mut self, world: &World) -> AppResult<()> {
        if world.dirty_ui || self.all_team_ids.len() != world.teams.len() {
            self.all_team_ids = world.teams.keys().copied().collect();
            self.all_team_ids.sort_by(|a, b| {
                world
                    .team_rating(b)
                    .unwrap_or_default()
                    .partial_cmp(&world.team_rating(a).unwrap_or_default())
                    .expect("Rating should exists")
            });
            self.update_view = true;
        }

        if self.update_view {
            let own_team = world.get_own_team()?;
            self.team_ids = self
                .all_team_ids
                .iter()
                .filter(|&team_id| {
                    let team = world.teams.get_or_err(team_id).unwrap();
                    self.view.rule(team, own_team)
                })
                .copied()
                .collect();
            self.update_view = false;
        }

        if let Some(index) = self.index {
            if self.team_ids.is_empty() {
                self.index = None;
            } else if index >= self.team_ids.len() && !self.team_ids.is_empty() {
                self.set_index(self.team_ids.len() - 1);
            }

            if index < self.team_ids.len() {
                self.selected_team_id = self.team_ids[index];
                let players = world
                    .teams
                    .get_or_err(&self.selected_team_id)?
                    .player_ids
                    .clone();
                if self.player_index < players.len() {
                    self.selected_player_id = players[self.player_index];
                }
            }
        } else if !self.team_ids.is_empty() {
            self.set_index(0);
        }

        Ok(())
    }
    fn render(
        &mut self,
        frame: &mut UiFrame,
        world: &World,
        area: Rect,

        _debug_view: bool,
    ) -> AppResult<()> {
        // Split into left and right panels
        let left_right_split = Layout::horizontal([
            Constraint::Length(LEFT_PANEL_WIDTH),
            Constraint::Min(IMG_FRAME_WIDTH),
        ])
        .split(area);
        self.build_left_panel(frame, world, left_right_split[0])?;
        if self.all_team_ids.is_empty() {
            frame.render_widget(
                Paragraph::new(" No crews yet!"),
                left_right_split[1].inner(Margin::new(1, 1)),
            );
            return Ok(());
        }
        self.build_right_panel(frame, world, left_right_split[1])?;
        Ok(())
    }

    fn handle_key_events(
        &mut self,
        key_event: crossterm::event::KeyEvent,
        _world: &World,
    ) -> Option<UiCallback> {
        match key_event.code {
            KeyCode::Up => self.next_index(),
            KeyCode::Down => self.previous_index(),
            ui_key::NEXT_SELECTION => self.next_player_index(),
            ui_key::PREVIOUS_SELECTION => self.previous_player_index(),
            ui_key::CYCLE_VIEW => {
                return Some(UiCallback::SetTeamPanelView {
                    view: self.view.next(),
                });
            }
            ui_key::CYCLE_VIEW_BACK => {
                return Some(UiCallback::SetTeamPanelView {
                    view: self.view.previous(),
                });
            }
            KeyCode::Enter => {
                let player_id = self.selected_player_id;
                return Some(UiCallback::GoToPlayer { player_id });
            }
            _ => {}
        }
        None
    }

    fn footer_spans(&self) -> Vec<String> {
        vec![
            format!(" {} ", ui_key::CYCLE_VIEW.to_string()),
            " Next tab ".to_string(),
            format!(
                " {}/{} ",
                ui_key::PREVIOUS_SELECTION.to_string(),
                ui_key::NEXT_SELECTION.to_string()
            ),
            " Select player ".to_string(),
        ]
    }
}

impl HelpPanel for TeamListPanel {
    fn help_content(&self) -> HelpContent {
        HelpContent {
            description: [
                "Browse all the rival crews. Inspect their roster and challenge them to a match.",
                "",
                "Manage your own crew in My Team.",
                "To inspect individual players, browse Pirates.",
                "To find a planet and travel there, see Galaxy.",
            ]
            .join("\n"),
            links: vec![
                tab_link("My Team", UiTab::MyTeam),
                tab_link("Pirates", UiTab::Pirates),
                tab_link("Galaxy", UiTab::Galaxy),
            ],
            controls: vec![
                Line::from("Controls:"),
                Line::from(format!(
                    "  {}        Cycle view (All / OpenToChallenge / Peers)",
                    ui_key::CYCLE_VIEW
                )),
                Line::from("  ↑/↓        Move highlight in the team list"),
                Line::from(format!(
                    "  {} / {}      Scroll the player list inside the team",
                    ui_key::PREVIOUS_SELECTION,
                    ui_key::NEXT_SELECTION
                )),
                Line::from(format!(
                    "  {}          Challenge highlighted team to a match",
                    ui_key::game::CHALLENGE_TEAM
                )),
                Line::from(format!(
                    "  {}          Open home planet / {} current planet",
                    ui_key::GO_TO_HOME_PLANET,
                    ui_key::ON_PLANET
                )),
            ],
        }
    }
}

impl SplitPanel for TeamListPanel {
    fn index(&self) -> Option<usize> {
        self.index
    }

    fn max_index(&self) -> usize {
        self.team_ids.len()
    }

    fn set_index(&mut self, index: usize) {
        self.index = Some(index);
    }
}
