use crate::attempt::Attempt;
use crate::status::Status;
use crate::words::get_word_of_the_day;
use std::collections::{HashMap, VecDeque};
use uuid::Uuid;

pub type Board = [Option<Attempt>; 6];

#[derive(Clone)]
pub enum InsertionStatus {
    Ok,
    LengthError,
    GameOver,
}

#[derive(Clone, Debug)]
pub enum GameStatus {
    Playing,
    Win(String),
    Fail,
}

#[derive(Clone)]
pub struct Game {
    pub uuid: Uuid,
    pub attempt_index: usize,
    pub answer: String,
    pub board: Board,
    pub status: GameStatus,
    pub last_insertion_status: InsertionStatus,
}

impl Game {
    pub async fn new() -> Self {
        Game {
            uuid: Uuid::new_v4(),
            attempt_index: 0,
            answer: get_word_of_the_day().await,
            board: Default::default(),
            status: GameStatus::Playing,
            last_insertion_status: InsertionStatus::Ok,
        }
    }

    pub fn add_attempt(&mut self, word: &String) -> InsertionStatus {
        let word = word.to_uppercase();

        let win = word.eq(&self.answer);

        let insertion_status = match (self.attempt_index, word.chars().count(), &self.status) {
            (0..=5, 5, &GameStatus::Playing) => {
                let new_attempt = Attempt::from_string(&word, &self.answer);
                self.board[self.attempt_index] = Some(new_attempt);
                self.attempt_index += 1;

                match (win, self.attempt_index) {
                    (true, _) => {
                        self.status = GameStatus::Win(self.generate_result_pattern());
                        InsertionStatus::GameOver
                    }
                    (false, 6) => {
                        self.status = GameStatus::Fail;
                        InsertionStatus::GameOver
                    }
                    (_, _) => InsertionStatus::Ok,
                }
            }
            (0..=5, _, &GameStatus::Playing) => InsertionStatus::LengthError,
            (_, _, &GameStatus::Win(_) | &GameStatus::Fail) => InsertionStatus::GameOver,
            _ => todo!(),
        };
        self.last_insertion_status = insertion_status.clone();
        insertion_status
    }

    fn generate_result_pattern(&self) -> String {
        let mut output = String::new();
        for attempt in &self.board {
            match attempt {
                Some(att) => {
                    for ch in &att.chars {
                        output.push(match ch.status {
                            Status::Found => '🟩',
                            Status::Almost => '🟨',
                            Status::NotFound => '⬛',
                        });
                    }
                    output.push('\n');
                }
                None => {}
            }
        }
        output
    }
}

pub struct SlotManager {
    max_length: usize,
    slots: HashMap<Uuid, Game>,
    order: VecDeque<Uuid>,
}

impl SlotManager {
    pub fn new(max_length: usize) -> Self {
        SlotManager {
            max_length,
            slots: HashMap::new(),
            order: VecDeque::new(),
        }
    }

    pub async fn new_game(&mut self) -> &mut Game {
        if self.order.len() >= self.max_length {
            if let Some(oldest_id) = self.order.pop_front() {
                self.slots.remove(&oldest_id);
            }
        }

        let new_game = Game::new().await;
        let new_id = new_game.uuid;
        self.slots.insert(new_id, new_game);
        self.order.push_back(new_id);

        // This should be safe because the game is inserted previously
        self.slots.get_mut(&new_id).unwrap()
    }

    pub fn get_game(&mut self, id: Uuid) -> Option<&mut Game> {
        self.slots.get_mut(&id)
    }
}