use std::collections::HashMap;

use api::game_actions::SpawnToken;
use game::{
    Game, GameImpl,
    chat_message::ChatMessage,
    entry::{ActionDefinition, DiceRoll},
};
use serde::{Deserialize, Serialize};
use tokio::sync::{broadcast, mpsc};

pub mod api;
pub mod game;
pub mod pathfinder2r_impl;
pub mod user;

#[derive(Serialize, Deserialize)]
pub struct GameServer {
    game: Game<pathfinder2r_impl::Pathfinder2rCharacterSheet, pathfinder2r_impl::entry::Entry>,
    chat: Vec<(String, ChatMessage)>,
    // we dont want to save the logged users as it will always be empty when the server restarts
    #[serde(skip)]
    /// Logged in users, bool value used to know if the user is an admin or not
    users: HashMap<String, bool>,
    // TODO: JEESH REPLACE THIS WITH A DATABASE AND PROPER SECURITY ONCE ITS DONE PLEASE GOD
    creds: HashMap<String, String>,
}
impl GameServer {
    pub fn new() -> Self {
        let mut creds = HashMap::new();
        creds.insert("rusty".to_string(), "".to_string());
        creds.insert("test".to_string(), "test".to_string());
        creds.insert("dragonfly".to_string(), "dragonfly".to_string());
        Self {
            game: Game::new(),
            chat: vec![(
                "Server".to_string(),
                ChatMessage::new("a weapon description".to_string())
                    .id(1)
                    .character(Some("Sword or something".to_string()))
                    .with_action(
                        ActionDefinition::new("weapon/attack".to_string())
                            .display_name(Some("Attack +7".to_string()))
                            .with_roll(DiceRoll::new("Pierce".to_string(), 12, 1).constant(1))
                            .with_roll(DiceRoll::new("Fire".to_string(), 4, 2).enabled(false)),
                    )
                    .with_action(ActionDefinition::new("Attack +3".to_string()).with_roll(DiceRoll::new(
                        "Base".to_string(),
                        20,
                        1,
                    )))
                    .with_action(ActionDefinition::new("Attack -1".to_string())),
            )],
            users: HashMap::new(),
            creds,
        }
    }

    pub async fn server_loop(
        mut self,
        mut msgs: mpsc::Receiver<(String, api::Request)>,
        broadcast: broadcast::Sender<(Option<String>, api::Response)>,
    ) {
        while let Some(req) = msgs.recv().await {
            // TODO: do stuff yo!
            let (id, req) = req;
            println!("Got message from {}: {:?}", &id, &req);

            match req {
                // ignore errors, should probably be blocked before they are sent here
                api::Request::Error => {}
                api::Request::Login(login) => {
                    println!("login req from {}: {:?}", &id, &login);
                    if !self.users.contains_key(&login.username) &&
                        self.creds
                            .get(&login.username)
                            .map(|p| p == &login.password)
                            .unwrap_or(false)
                    {
                        self.users.insert(login.username.clone(), login.username == "rusty"); // rusty will be admin for now :)
                        _ = broadcast.send((
                            Some(id),
                            api::Response::Login(api::login::LoginResult {
                                success: true,
                                username: login.username,
                            }),
                        ));
                    } else {
                        _ = broadcast.send((
                            Some(id.clone()),
                            api::Response::Login(api::login::LoginResult {
                                success: false,
                                username: login.username,
                            }),
                        ));
                    }
                }
                api::Request::Message(mut msg) => {
                    if msg.id == 0 || msg.id > self.chat.len() {
                        msg.id = self.chat.len() + 1; // set the message id, 0 is invalid
                    } else if id == self.chat[msg.id - 1].0 || *self.users.get(&id).unwrap_or(&false) {
                        self.chat[msg.id - 1] = (id.clone(), msg.clone());
                    } else {
                        // if its an edit message and editor is not the owner, skip
                        continue;
                    }
                    // Force the sender id to be the new id of the message, even if an id was provided
                    msg.source = id.clone();

                    self.chat.push((id.clone(), msg.clone()));
                    if msg.whisper.is_some() {
                        _ = broadcast.send((Some(id.clone()), api::Response::Message(msg.clone())));
                    }
                    _ = broadcast.send((msg.whisper.clone(), api::Response::Message(msg)));
                }
                api::Request::GetChatHistory {
                    mut amount,
                    from: last_msg,
                } => {
                    if amount == 0 {
                        amount = self.chat.len();
                    }
                    let history: Vec<ChatMessage> = self
                        .chat
                        .iter()
                        .skip(last_msg)
                        .take(amount)
                        .map(|m| m.1.clone())
                        .collect();
                    _ = broadcast.send((Some(id), api::Response::GetChatHistory(history)));
                }
                api::Request::GetLastMessages { mut amount } => {
                    if amount == 0 {
                        amount = self.chat.len();
                    }
                    let start = if amount >= self.chat.len() {
                        0
                    } else {
                        self.chat.len() - amount
                    };
                    let history: Vec<ChatMessage> = self.chat.iter().skip(start).map(|m| m.1.clone()).collect();
                    _ = broadcast.send((Some(id), api::Response::GetChatHistory(history)));
                }
                api::Request::GetTokens { scene } => {
                    for token_id in self.game.available_tokens(scene) {
                        if let Some(ti) = self.game.token_info(0, token_id) {
                            _ = broadcast.send((
                                Some(id.clone()),
                                api::Response::SpawnToken(SpawnToken {
                                    token_id: token_id,
                                    x: ti.x,
                                    y: ti.y,
                                    img: ti.img_source.clone(),
                                }),
                            ));
                        }
                    }
                }
                api::Request::GetCurrentScene => {
                    let scene = self.game.current_scene();
                    let scene_tokens = self
                        .game
                        .available_tokens(scene)
                        .iter()
                        .map(|&id| self.game.token_info(scene, id).map(|info| (id, info)))
                        .flatten()
                        .map(|(id, info)| SpawnToken {
                            token_id: id,
                            x: info.x,
                            y: info.y,
                            img: info.img_source.clone(),
                        })
                        .collect::<Vec<_>>();
                    let map = self.game.scene_map(scene);
                    _ = broadcast.send((
                        Some(id.clone()),
                        api::Response::ShowScene {
                            scene: scene,
                            tokens: scene_tokens,
                            background: map.map(|m| m.background.clone()),
                            grid_cell_size: map.map(|m| m.grid_cell_size),
                            grid_offset: map.map(|m| m.grid_offset.clone()),
                        },
                    ));
                }
                api::Request::SpawnToken {
                    map_id,
                    character,
                    x,
                    y,
                    img_path,
                } => {
                    let token_id = self.game.create_token(map_id, character, img_path.clone(), x, y);
                    _ = broadcast.send((
                        Some(id.clone()),
                        api::Response::SpawnToken(SpawnToken {
                            token_id,
                            x,
                            y,
                            img: img_path.clone(),
                        }),
                    ));
                }
                api::Request::MoveToken { token_id, x, y } => {
                    // TODO: add check to make sure the actor is authorized to move the token
                    if self.game.move_token(0, token_id, x, y) {
                        // TODO: maybe chage move_token to return optional x,y values if succeeded to make sure the token is where it was going to be
                        _ = broadcast.send((None, api::Response::MoveToken { token_id, x, y }));
                    }
                }
                api::Request::ActionResult(result) => {
                    let msg = ChatMessage::new(
                        result
                            .results
                            .iter()
                            // .map(|d| &d.result_text)
                            .fold(String::new(), |a, b| {
                                a + &format!("{}: {} = {}\n", &b.name, &b.result_text, b.result)
                            }),
                    )
                    .character(Some(result.name))
                    .source(id.clone())
                    .targets(Some(result.targets))
                    .id(self.chat.len() + 1);
                    self.chat.push((id, msg.clone()));
                    _ = broadcast.send((None, api::Response::Message(msg)));
                }
                api::Request::CreateCharacter => {
                    // check if user is admin
                    if self.users.get(&id).map(|a| *a).unwrap_or(false) {
                        let new_id = self.game.create_character();
                        // return the new id with the character i think
                        _ = broadcast.send((Some(id), api::Response::CharacterCreated(new_id)));
                    }
                }
                api::Request::Quit => {
                    if self.users.contains_key(&id) {
                        self.users.remove(&id);
                    }
                    _ = broadcast.send((None, api::Response::Quit { id }));
                }
                api::Request::Kick(id) => {
                    if self.users.contains_key(&id) {
                        self.users.remove(&id);
                    }
                    _ = broadcast.send((Some(id), api::Response::Shutdown));
                }
                api::Request::Shutdown => break,
                api::Request::CharacterDisplay { id } => todo!(),
                api::Request::CharacterInputs { id } => todo!(),
                api::Request::CharacterGetField { id, field } => todo!(),
                api::Request::CharacterSetField { id, field, val } => todo!(),
                api::Request::CharacterAssign { id, user } => todo!(),
            }
        }
        _ = broadcast.send((None, api::Response::Shutdown));
    }
}
