use uuid::Uuid;

use serde_cbor::{from_slice, to_vec};

use postgres::transaction::Transaction;

use failure::Error;
use failure::err_msg;

use std::iter;

// timekeeping
use chrono::prelude::*;
use chrono::Duration;

use rpc::{InstanceLobbyParams, InstanceJoinParams, InstanceReadyParams, InstanceStateParams};
use account::Account;
use player::{Player, player_create, player_get, player_global_update};
use cryp::{Cryp, cryp_get};
use mob::{instance_mobs};
use game::{Game, Phase, game_get, game_write, game_instance_new};
use vbox::{Var};
use rpc::{RpcResult};
use names::{name};

#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
enum InstancePhase {
    Lobby,
    InProgress,
    Finished,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct Round {
    player_ids: Vec<Uuid>,
    game_id: Option<Uuid>,
    finished: bool,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Instance {
    id: Uuid,
    players: Vec<Player>,
    phase: InstancePhase,
    rounds: Vec<Vec<Round>>,
    open: bool,
    max_players: usize,
    max_rounds: usize,
    password: Option<String>,
    pub name: String,
    phase_end: DateTime<Utc>,
}

impl Instance {
    fn new() -> Instance {
        Instance {
            id: Uuid::new_v4(),
            players: vec![],
            rounds: vec![],
            phase: InstancePhase::Lobby,
            open: true,
            max_players: 2,
            max_rounds: 16,
            name: String::new(),
            password: None,
            phase_end: Utc::now(),
        }
    }

    pub fn global(player: Player) -> Instance {
        Instance {
            id: Uuid::nil(),
            players: vec![player],
            rounds: vec![],
            phase: InstancePhase::InProgress,
            open: false,
            max_players: 0,
            max_rounds: 1,
            name: "Global Matchmaking".to_string(),
            password: None,
            phase_end: Utc::now(),
        }
    }

    fn phase_timed_out(&self) -> bool {
        Utc::now().signed_duration_since(self.phase_end).num_milliseconds() > 0
    }

    fn timed_out_players(&self) -> Vec<Uuid> {
        self.players
            .iter()
            .filter(|p| !p.ready)
            .filter(|p| self.current_game_id(p.id).is_none())
            .map(|p| p.id)
            .collect::<Vec<Uuid>>()
    }

    pub fn upkeep(mut self) -> (Instance, Vec<Game>) {
        if self.phase != InstancePhase::InProgress {
            return (self, vec![]);
        }

        if !self.phase_timed_out() {
            return (self, vec![]);
        }

        let new_games = self
            .timed_out_players()
            .iter()
            .filter_map(|p| self.player_ready(*p).unwrap())
            .collect::<Vec<Game>>();

        (self, new_games)
    }

    fn set_max_players(mut self, max: usize) -> Result<Instance, Error> {
        if max > 16 || max % 2 != 0 {
            return Err(err_msg("max players must be divisible by 2 and less than 16"));
        }

        self.max_players = max;
        Ok(self)
    }

    fn set_name(mut self, name: String) -> Result<Instance, Error> {
        if name.len() == 0 {
            return Err(err_msg("name must have a length"));
        }

        self.name = name;
        Ok(self)
    }

    fn set_max_rounds(mut self, rounds: usize) -> Result<Instance, Error> {
        if rounds == 0 {
            return Err(err_msg("max rounds must be nonzero"));
        }

        self.max_rounds = rounds;
        Ok(self)
    }

    fn add_bots(mut self) -> Instance {
        self.open = false;
        self.players = iter::repeat_with(|| {
                let bot_id = Uuid::new_v4();
                let cryps = instance_mobs(bot_id);
                let mut p = Player::new(bot_id, &name(), cryps).set_bot(true);
                p.set_ready(true);
                p
            })
            .take(15)
            .collect::<Vec<Player>>();
        self
    }

    fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> {
        match self.players.iter().find(|p| p.id == player.id) {
            Some(_p) => return Err(err_msg("already joined")),
            None => (),
        };

        self.players.push(player);
        Ok(self)
    }

    fn player_ready(&mut self, player_id: Uuid) -> Result<Option<Game>, Error> {
        if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) {
            return Err(err_msg("instance not in start or vbox phase"));
        }

        // LOBBY CHECKS
        if self.phase == InstancePhase::Lobby {
            let i = self.players
                .iter_mut()
                .position(|p| p.id == player_id)
                .ok_or(err_msg("player_id not found"))?;

            let v = !self.players[i].ready;
            self.players[i].set_ready(v);

            match self.can_start() {
                true => {
                    self.start();
                    return Ok(None);
                }
                false => return Ok(None),
            };
        }

        // GAME PHASE READY
        let i = self.players
            .iter_mut()
            .position(|p| p.id == player_id)
            .ok_or(err_msg("player_id not found"))?;

        let v = !self.players[i].ready;
        self.players[i].set_ready(v);

        // start the game even if afk noobs have no skills
        if !self.phase_timed_out() && self.players[i].cryps.iter().all(|c| c.skills.len() == 0) {
            return Err(err_msg("your cryps have no skills"));
        }

        // create a game object if both players are ready
        // this should only happen once

        let all_ready = self.round_ready_check(player_id);

        if !all_ready {
            return Ok(None);
        }

        let game = self.create_round_game(player_id);

        {
            let round_num = self.rounds.len() - 1;
            let current_round = self.rounds[round_num]
                .iter_mut()
                .find(|g| g.player_ids.contains(&player_id))
                .unwrap();

            current_round.game_id = Some(game.id);
        }

        return Ok(Some(game));

    }

    fn round_ready_check(&mut self, player_id: Uuid) -> bool {
        let current_round = self.current_round(player_id);
        self.players
            .iter()
            .filter(|p| current_round.player_ids.contains(&p.id))
            .all(|p| p.ready)
    }

    // maybe just embed the games in the instance
    // but seems hella inefficient
    fn create_round_game(&self, player_id: Uuid) -> Game {
        let current_round = self.current_round(player_id);
        let mut game = Game::new();

        game
            .set_player_num(2)
            .set_player_cryps(3)
            .set_instance(self.id);

        // create the initiators player
        for player in self.players
            .clone()
            .into_iter()
            .filter(|p| current_round.player_ids.contains(&p.id)) {
            game.player_add(player).unwrap();
        }

        assert!(game.can_start());
        return game.start();
    }

    fn can_start(&self) -> bool {
        self.players.len() == self.max_players && self.all_ready()
    }

    fn start(&mut self) -> &mut Instance {
        // self.players.sort_unstable_by_key(|p| p.id);
        self.open = false;
        self.next_round()
    }

    fn next_round(&mut self) -> &mut Instance {
        self.phase = InstancePhase::InProgress;
        self.phase_end = Utc::now()
            .checked_add_signed(Duration::seconds(12000))
            .expect("could not set phase end");


        if self.rounds.len() >= self.max_rounds {
            return self.finish();
        }

        self.players.iter_mut().for_each(|p| {
            p.set_ready(false);
            p.vbox.fill();
        });

        self.generate_rounds();
        self.bot_round_actions();

        self
    }

    fn finish(&mut self) -> &mut Instance {
        self.phase = InstancePhase::Finished;
        self
    }

    fn finished(&self) -> bool {
        self.phase == InstancePhase::Finished
    }

    fn bot_round_actions(&mut self) -> &mut Instance {
        for bot in self.players.iter_mut().filter(|p| p.bot) {
            bot.vbox.fill();
            bot.autobuy();
        }

        let games = self.players
            .clone()
            .iter()
            .filter(|b| b.bot)
            .filter_map(|b| self.player_ready(b.id).unwrap())
            .collect::<Vec<Game>>();

        for game in games {
            if game.finished() {
                self.game_finished(&game).unwrap();
            } else {
                println!("{:?} unfishededes", game);
            }
        }

        self
    }

    fn generate_rounds(&mut self) -> &mut Instance {
        let round_num = self.rounds.len();
        let mut matched_players = self.players
            .iter()
            .map(|p| p.id)
            .collect::<Vec<Uuid>>();

        let np = matched_players.len();

        if round_num > 0 {
            matched_players.rotate_right(round_num % np);
            matched_players.swap(0,1);
        }

        // only set up for even player numbers atm
        // no byes
        let current_round = matched_players[0..(np / 2)]
            .iter()
            .enumerate()
            .map(|(i, id)| Round {
                player_ids: vec![*id, matched_players[np - (i + 1)]],
                game_id: None,
                finished: false,
            })
            .collect::<Vec<Round>>();

        self.rounds.push(current_round);

        self
    }

    fn current_round(&self, player_id: Uuid) -> &Round {
        let round_num = self.rounds.len() - 1;

        let current_round = self.rounds[round_num]
            .iter()
            .find(|g| g.player_ids.contains(&player_id))
            .unwrap();

        current_round
    }

    fn current_game_id(&self, player_id: Uuid) -> Option<Uuid> {
        if self.phase == InstancePhase::Lobby {
            return None;
        }

        let current_round = self.current_round(player_id);

        if current_round.finished || current_round.game_id.is_none() {
            return None;
        }

        return current_round.game_id;
    }

    fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> {
        let round_num = self.rounds.len() - 1;
        self.rounds[round_num]
            .iter_mut()
            .filter(|r| r.game_id.is_some())
            .find(|r| r.game_id.unwrap() == game.id)
            .ok_or(err_msg("could not find matchup in current round"))?
            .finished = true;

        // if you don't win, you lose
        // ties can happen if both players forfeit
        let winner_id = match game.winner() {
            Some(w) => w.id,
            None => Uuid::nil(),
        };

        for player in game.players.iter() {
            let mut player = self.account_player(player.id)?;
            match player.id == winner_id {
                true => player.add_win(),
                false => player.add_loss(),
            };
        }

        if self.all_games_finished() {
            self.next_round();
        }

        Ok(self)
    }

    fn all_ready(&self) -> bool {
        self.players.iter().all(|p| p.ready)
    }

    fn all_games_finished(&self) -> bool {
        match self.rounds.last() {
            Some(r) => r.iter().all(|g| g.finished),
            None => true,
        }
    }

    // PLAYER ACTIONS
    fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> {
        self.players
            .iter_mut()
            .find(|p| p.id == account)
            .ok_or(err_msg("account not in instance"))
    }

    pub fn vbox_action_allowed(&self, account: Uuid) -> Result<(), Error> {
        if self.phase == InstancePhase::Lobby {
            return Err(err_msg("game not yet started"));
        }
        if self.current_game_id(account).is_some() {
            return Err(err_msg("you cannot perform vbox actions while in a game"));
        }

        Ok(())
    }

    pub fn vbox_discard(mut self, account: Uuid) -> Result<Instance, Error> {
        self.vbox_action_allowed(account)?;
        self.account_player(account)?
            .vbox_discard()?;
        Ok(self)
    }

    pub fn vbox_accept(mut self, account: Uuid, group: usize, index: usize) -> Result<Instance, Error> {
        self.vbox_action_allowed(account)?;
        self.account_player(account)?
            .vbox_accept(group, index)?;
        Ok(self)
    }

    pub fn vbox_combine(mut self, account: Uuid, indices: Vec<usize>) -> Result<Instance, Error> {
        self.vbox_action_allowed(account)?;
        self.account_player(account)?
            .vbox_combine(indices)?;
        Ok(self)
    }

    pub fn vbox_reclaim(mut self, account: Uuid, index: usize) -> Result<Instance, Error> {
        self.vbox_action_allowed(account)?;
        self.account_player(account)?
            .vbox_reclaim(index)?;
        Ok(self)
    }

    pub fn vbox_apply(mut self, account: Uuid, index: usize, cryp_id: Uuid) -> Result<Instance, Error> {
        self.vbox_action_allowed(account)?;
        self.account_player(account)?
            .vbox_apply(index, cryp_id)?;
        Ok(self)
    }

    pub fn vbox_unequip(mut self, account: Uuid, target: Var, cryp_id: Uuid) -> Result<Instance, Error> {
        self.vbox_action_allowed(account)?;
        self.account_player(account)?
            .vbox_unequip(target, cryp_id)?;
        Ok(self)
    }
}

pub fn instance_create(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
    let instance_bytes = to_vec(&instance)?;

    let query = "
        INSERT INTO instances (id, data, open)
        VALUES ($1, $2, $3)
        RETURNING id;
    ";

    let result = tx
        .query(query, &[&instance.id, &instance_bytes, &instance.open])?;

    result.iter().next().ok_or(format_err!("no instances written"))?;

    return Ok(instance);
}

pub fn instance_update(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
    let instance_bytes = to_vec(&instance)?;

    let query = "
        UPDATE instances
        SET data = $1, open = $2, finished = $3, updated_at = now()
        WHERE id = $4
        RETURNING id, data;
    ";

    let result = tx
        .query(query, &[&instance_bytes, &instance.open, &instance.finished(), &instance.id])?;

    result.iter().next().ok_or(err_msg("no instance row returned"))?;

    // println!("{:?} wrote instance", instance.id);

    return Ok(instance);
}

pub fn instance_get(tx: &mut Transaction, instance_id: Uuid) -> Result<Instance, Error> {
    let query = "
        SELECT *
        FROM instances
        WHERE id = $1
        FOR UPDATE;
    ";

    let result = tx
        .query(query, &[&instance_id])?;

    let returned = match result.iter().next() {
        Some(row) => row,
        None => return Err(err_msg("instance not found")),
    };

    let instance_bytes: Vec<u8> = returned.get("data");
    let instance = from_slice::<Instance>(&instance_bytes)?;

    return Ok(instance);
}

pub fn instance_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
    let query = "
        DELETE
        FROM instances
        WHERE id = $1;
    ";

    let result = tx
        .execute(query, &[&id])?;

    if result != 1 {
        return Err(format_err!("unable to delete instance {:?}", id));
    }

    println!("instance deleted {:?}", id);

    return Ok(());
}

pub fn instance_get_open(tx: &mut Transaction) -> Result<Instance, Error> {
    let query = "
        SELECT *
        FROM instances
        WHERE open = true;
    ";

    let result = tx
        .query(query, &[])?;

    let returned = match result.iter().next() {
        Some(row) => row,
        None => return Err(err_msg("instance not found")),
    };

    let instance_bytes: Vec<u8> = returned.get("data");
    let instance = from_slice::<Instance>(&instance_bytes)?;

    return Ok(instance);
}

pub fn instances_need_upkeep(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
    let query = "
        SELECT data, id
        FROM instances
        WHERE finished = false
        AND id != '00000000-0000-0000-0000-000000000000'
        FOR UPDATE;
    ";

    let result = tx
        .query(query, &[])?;

    let mut list = vec![];

    for row in result.into_iter() {
        let bytes: Vec<u8> = row.get(0);
        let id = row.get(1);

        match from_slice::<Instance>(&bytes) {
            Ok(i) => list.push(i),
            Err(_e) => {
                instance_delete(tx, id)?;
            }
        };
    }

    return Ok(list);
}

pub fn instance_new(params: InstanceLobbyParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
    let mut instance = match params.players {
        1 => Instance::new()
            .set_max_players(16)?
            .set_name(params.name)?
            .add_bots(),
        _ => Instance::new()
            .set_max_players(params.players)?
            .set_name(params.name)?,
    };

    instance = instance_create(tx, instance)?;
    let join_params = InstanceJoinParams { instance_id: instance.id, cryp_ids: params.cryp_ids };

    instance_join(join_params, tx, account)
}

pub fn instance_join(params: InstanceJoinParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
    let mut instance = instance_get(tx, params.instance_id)?;

    let cryps = params.cryp_ids
        .iter()
        .map(|id| cryp_get(tx, *id, account.id))
        .collect::<Result<Vec<Cryp>, Error>>()?;

    if cryps.len() != 3 {
        return Err(format_err!("incorrect player size. ({:})", 3));
    }

    let player = player_create(tx, Player::new(account.id, &account.name, cryps), instance.id, account)?;

    instance.add_player(player)?;

    instance_update(tx, instance)
}

// pub fn instance_ready_global(tx: &mut Transaction, _account: &Account, player: Player) -> Result<Game, Error> {
//     // get the game
//     let game = match game_global_get(tx) {
//         Ok(g) => {
//             println!("received global game {:?}", g.id);
//             // if there is one try to join
//             match game_instance_join(tx, player.clone(), g.id) {
//                 Ok(g) => g,
//                 // if fails make a new one
//                 Err(_e) => game_instance_new(tx, vec![player], Uuid::new_v4(), Uuid::nil())?,
//             }
//         },
//         // if not found make a new one
//         Err(_) => game_instance_new(tx, vec![player], Uuid::new_v4(), Uuid::nil())?,
//     };

//     // set the current game
//     game_global_set(tx, &game)?;
//     Ok(game)
// }

pub fn instance_ready(params: InstanceReadyParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
    let mut instance = instance_get(tx, params.instance_id)?;
    let player_id = instance.account_player(account.id)?.id;

    if let Some(game) = instance.player_ready(player_id)? {
        game_write(tx, &game)?;
    }

    instance_update(tx, instance)
}

pub fn instance_state(params: InstanceStateParams, tx: &mut Transaction, account: &Account) -> Result<RpcResult, Error> {
    let instance = instance_get(tx, params.instance_id)?;

    if let Some(game_id) = instance.current_game_id(account.id) {
        let game = game_get(tx, game_id)?;

        // return the game until it's finished
        if game.phase != Phase::Finish {
            return Ok(RpcResult::GameState(game))
        }
    }

    Ok(RpcResult::InstanceState(instance))
}

pub fn global_game_finished(tx: &mut Transaction, game: &Game) -> Result<(), Error> {
    let winner = game.winner().ok_or(err_msg("game not finished"))?;

    for player in game.players.iter() {
        let mut player = player_get(tx, player.id, Uuid::nil())?;
        match player.id == winner.id {
            true => player.add_win(),
            false => player.add_loss(),
        };
        player.vbox.fill();
        player_global_update(tx, player, true)?;
    }

    Ok(())
}

pub fn instance_game_finished(tx: &mut Transaction, game: &Game, instance_id: Uuid) -> Result<(), Error> {
    let mut instance = instance_get(tx, instance_id)?;
    instance.game_finished(game)?;
    // println!("{:?}", instance_get(tx, instance_id)?);

    instance_update(tx, instance)?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn instance_pve_test() {
        let mut instance = Instance::new()
            .set_max_players(16).expect("unable to set max players")
            .set_max_rounds(2).expect("max rounds failure")
            .add_bots();

        let player_account = Uuid::new_v4();
        let cryps = instance_mobs(player_account);
        let player = Player::new(player_account, &"test".to_string(), cryps).set_bot(true);
        let player_id = player.id;

        instance.add_player(player).expect("could not add player");

        assert_eq!(instance.phase, InstancePhase::Lobby);
        instance.player_ready(player_id).unwrap();

        assert_eq!(instance.phase, InstancePhase::Finished);
        assert_eq!(instance.rounds[0].len(), 8);
        assert_eq!(instance.rounds.len(), 2);
    }

    #[test]
    fn instance_bot_vbox_test() {
        let instance = Instance::new();
        let player_account = Uuid::new_v4();
        let cryps = instance_mobs(player_account);
        let _player = Player::new(player_account, &"test".to_string(), cryps).set_bot(true);
    }

    #[test]
    fn instance_start_test() {
        let mut instance = Instance::new()
            .set_max_players(2)
            .expect("could not create instance");

        assert_eq!(instance.max_players, 2);

        let player_account = Uuid::new_v4();
        let cryps = instance_mobs(player_account);
        let player = Player::new(player_account, &"a".to_string(), cryps);
        let a_id = player.id;

        instance.add_player(player).expect("could not add player");
        assert!(!instance.can_start());

        let player_account = Uuid::new_v4();
        let cryps = instance_mobs(player_account);
        let player = Player::new(player_account, &"b".to_string(), cryps);
        let b_id = player.id;

        instance.add_player(player).expect("could not add player");

        assert_eq!(instance.phase, InstancePhase::Lobby);
        instance.player_ready(a_id).expect("a ready");
        assert!(!instance.can_start());

        instance.player_ready(b_id).expect("b ready");
        assert_eq!(instance.phase, InstancePhase::InProgress);

        assert!(!instance.can_start());

        instance.players[0].autobuy();
        instance.players[1].autobuy();

        instance.player_ready(a_id).expect("a ready");
        let game = instance.player_ready(b_id).expect("b ready");

        assert!(game.is_some());
    }

    #[test]
    fn instance_upkeep_test() {
        let mut instance = Instance::new()
            .set_max_players(2)
            .expect("could not create instance");

        let player_account = Uuid::new_v4();
        let cryps = instance_mobs(player_account);
        let player = Player::new(player_account, &"a".to_string(), cryps);
        let a_id = player.id;

        instance.add_player(player).expect("could not add player");
        assert!(!instance.can_start());

        let player_account = Uuid::new_v4();
        let cryps = instance_mobs(player_account);
        let player = Player::new(player_account, &"b".to_string(), cryps);
        let b_id = player.id;
        instance.add_player(player).expect("could not add player");

        instance.players[0].autobuy();

        instance.player_ready(a_id).expect("a ready");
        instance.player_ready(b_id).expect("b ready");

        instance.phase_end = Utc::now().checked_sub_signed(Duration::seconds(61)).unwrap();

        let (mut instance, new_games) = instance.upkeep();

        assert_eq!(new_games.len(), 1);

        let game = &new_games[0];
        assert!(game.finished());

        instance.game_finished(game).unwrap();

        assert_eq!(instance.rounds.len(), 2);
        assert!(instance.players.iter().all(|p| !p.ready));

        // println!("{:#?}", instance);
    }
}
