use tungstenite::Message;
use tungstenite::protocol::WebSocket;
use tungstenite::Message::Binary;
use postgres::transaction::Transaction;
use std::net::{TcpStream};

// demo
use std::iter;
use rand::{thread_rng, Rng};
use rand::distributions::{Alphanumeric};

use serde_cbor::{from_slice, to_vec};
use uuid::Uuid;
use failure::Error;
use failure::err_msg;

use net::Db;
use cryp::{Cryp, cryp_spawn};
use game::{Game, game_state, game_skill};
use account::{Account, account_create, account_login, account_from_token, account_cryps, account_players};
use skill::{Skill};
// use zone::{Zone, zone_create, zone_join, zone_close};
use spec::{Spec};
use player::{Score, player_state, player_cryps_set, Player};
use instance::{instance_join, instance_ready, instance_scores};
use vbox::{Var, vbox_accept, vbox_apply, vbox_discard, vbox_combine, vbox_reclaim, vbox_unequip};

pub struct Rpc;

impl Rpc {
    pub fn receive(&self, msg: Message, db: &Db, client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        // consume the ws data into bytes
        let data = msg.into_data();

        // cast the msg to this type to receive method name
        match from_slice::<RpcMessage>(&data) {
            Ok(v) => {
                let mut tx = db.transaction()?;

                let account: Option<Account> = match v.token {
                    Some(t) => Some(account_from_token(t, &mut tx)?),
                    None => None,
                };

                // check the method
                // if no auth required
                match v.method.as_ref() {
                    "account_create" => (),
                    "account_login"  => (),
                    "account_demo"  => (),
                    _ => match account {
                        Some(_) => (),
                        None => return Err(err_msg("auth required")),
                    },
                };

                // now we have the method name
                // match on that to determine what fn to call
                let response = match v.method.as_ref() {
                    // no auth methods
                    "account_create" => Rpc::account_create(data, &mut tx, client),
                    "account_login" => Rpc::account_login(data, &mut tx, client),
                    "account_demo" => Rpc::account_demo(data, &mut tx, client),

                    // auth methods
                    "account_cryps" => Rpc::account_cryps(data, &mut tx, account.unwrap(), client),
                    "account_players" => Rpc::account_players(data, &mut tx, account.unwrap(), client),
                    // "account_zone" => Rpc::account_zone(data, &mut tx, account.unwrap(), client),

                    "cryp_spawn" => Rpc::cryp_spawn(data, &mut tx, account.unwrap(), client),

                    "game_state" => Rpc::game_state(data, &mut tx, account.unwrap(), client),
                    // "game_pve" => Rpc::game_pve(data, &mut tx, account.unwrap(), client),
                    "game_skill" => Rpc::game_skill(data, &mut tx, account.unwrap(), client),

                    // "zone_create" => Rpc::zone_create(data, &mut tx, account.unwrap(), client),
                    // "zone_join" => Rpc::zone_join(data, &mut tx, account.unwrap(), client),
                    // "zone_close" => Rpc::zone_close(data, &mut tx, account.unwrap(), client),

                    "instance_join" => Rpc::instance_join(data, &mut tx, account.unwrap(), client),
                    "instance_ready" => Rpc::instance_ready(data, &mut tx, account.unwrap(), client),
                    "instance_scores" => Rpc::instance_scores(data, &mut tx, account.unwrap(), client),

                    "player_state" => Rpc::player_state(data, &mut tx, account.unwrap(), client),
                    "player_cryps_set" => Rpc::player_cryps_set(data, &mut tx, account.unwrap(), client),
                    "player_vbox_accept" => Rpc::player_vbox_accept(data, &mut tx, account.unwrap(), client),
                    "player_vbox_apply" => Rpc::player_vbox_apply(data, &mut tx, account.unwrap(), client),
                    "player_vbox_combine" => Rpc::player_vbox_combine(data, &mut tx, account.unwrap(), client),
                    "player_vbox_discard" => Rpc::player_vbox_discard(data, &mut tx, account.unwrap(), client),
                    "player_vbox_reclaim" => Rpc::player_vbox_reclaim(data, &mut tx, account.unwrap(), client),
                    "player_vbox_unequip" => Rpc::player_vbox_unequip(data, &mut tx, account.unwrap(), client),

                    _ => Err(format_err!("unknown method - {:?}", v.method)),
                };

                tx.commit()?;

                return response;
            },
            Err(e) => {
                println!("{:?}", e);
                Err(err_msg("unknown error"))
            },
        }
    }

    fn send_msg(client: &mut WebSocket<TcpStream>, msg: RpcResponse) -> Result<(), Error> {
        let bytes = to_vec(&msg)?;
        match client.write_message(Binary(bytes)) {
            Ok(()) => Ok(()),
            Err(e) => Err(err_msg(e))
        }
    }

    fn game_state(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<GameStateMsg>(&data).or(Err(err_msg("invalid params")))?;

        let game_response = RpcResponse {
            method: "game_state".to_string(),
            params: RpcResult::GameState(game_state(msg.params, tx, &account)?)
        };

        return Ok(game_response);
    }

    // fn game_pve(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
    //     let msg = from_slice::<GamePveMsg>(&data).or(Err(err_msg("invalid params")))?;

    //     let game_response = RpcResponse {
    //         method: "game_state".to_string(),
    //         params: RpcResult::GameState(game_pve(msg.params, tx, &account)?)
    //     };

    //     return Ok(game_response);
    // }

    fn game_skill(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<GameSkillMsg>(&data).or(Err(err_msg("invalid params")))?;

        let game_response = RpcResponse {
            method: "game_state".to_string(),
            params: RpcResult::GameState(game_skill(msg.params, tx, &account)?)
        };

        return Ok(game_response);
    }

    fn cryp_spawn(data: Vec<u8>, tx: &mut Transaction, account: Account, client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<CrypSpawnMsg>(&data).or(Err(err_msg("invalid params")))?;

        Rpc::send_msg(client, RpcResponse {
            method: "cryp_spawn".to_string(),
            params: RpcResult::CrypSpawn(cryp_spawn(msg.params, tx, &account)?)
        })?;

        let cryp_list = RpcResponse {
            method: "account_cryps".to_string(),
            params: RpcResult::CrypList(account_cryps(tx, &account)?)
        };

        Ok(cryp_list)
    }

    fn account_create(data: Vec<u8>, tx: &mut Transaction, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<AccountCreateMsg>(&data).or(Err(err_msg("invalid params")))?;

        let account = account_create(msg.params, tx)?;

        Ok(RpcResponse {
            method: "account_create".to_string(),
            params: RpcResult::Account(account)
        })
    }

    fn account_login(data: Vec<u8>, tx: &mut Transaction, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        match from_slice::<AccountLoginMsg>(&data) {
            Ok(v) => Ok(RpcResponse {
                method: v.method,
                params: RpcResult::Account(account_login(v.params, tx)?)
            }),
            Err(_e) => Err(err_msg("invalid params")),
        }
    }

    fn account_demo(_data: Vec<u8>, tx: &mut Transaction, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let mut rng = thread_rng();

        let acc_name: String = iter::repeat(()).map(|()| rng.sample(Alphanumeric)).take(8).collect();

        let account = account_create(AccountCreateParams { name: acc_name, password: "grepgrepgrep".to_string() }, tx)?;

        let name: String = iter::repeat(()).map(|()| rng.sample(Alphanumeric)).take(8).collect();
        cryp_spawn(CrypSpawnParams { name }, tx, &account)?;

        let name: String = iter::repeat(()).map(|()| rng.sample(Alphanumeric)).take(8).collect();
        cryp_spawn(CrypSpawnParams { name }, tx, &account)?;

        let name: String = iter::repeat(()).map(|()| rng.sample(Alphanumeric)).take(8).collect();
        cryp_spawn(CrypSpawnParams { name }, tx, &account)?;

        let res = RpcResponse {
            method: "account_create".to_string(),
            params: RpcResult::Account(account),
        };

        return Ok(res);
    }


    fn account_cryps(_data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        Ok(RpcResponse {
            method: "account_cryps".to_string(),
            params: RpcResult::CrypList(account_cryps(tx, &account)?)
        })
    }

    fn account_players(_data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        Ok(RpcResponse {
            method: "account_players".to_string(),
            params: RpcResult::PlayerList(account_players(tx, &account)?)
        })
    }

    // fn account_zone(_data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
    //     Ok(RpcResponse {
    //         method: "zone_state".to_string(),
    //         params: RpcResult::ZoneState(account_zone(tx, &account)?)
    //     })
    // }

    // fn zone_create(_data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
    //     // let _msg = from_slice::<ZoneCreateMsg>(&data).or(Err(err_msg("invalid params")))?;

    //     let response = RpcResponse {
    //         method: "zone_state".to_string(),
    //         params: RpcResult::ZoneState(zone_create(tx, &account)?)
    //     };

    //     return Ok(response);
    // }

    // fn zone_join(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
    //     let msg = from_slice::<ZoneJoinMsg>(&data).or(Err(err_msg("invalid params")))?;

    //     let response = RpcResponse {
    //         method: "game_state".to_string(),
    //         params: RpcResult::GameState(zone_join(msg.params, tx, &account)?)
    //     };

    //     return Ok(response);
    // }

    // fn zone_close(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
    //     let msg = from_slice::<ZoneCloseMsg>(&data).or(Err(err_msg("invalid params")))?;

    //     let response = RpcResponse {
    //         method: "zone_close".to_string(),
    //         params: RpcResult::ZoneClose(zone_close(msg.params, tx, &account)?)
    //     };

    //     return Ok(response);
    // }

    fn instance_join(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<InstanceJoinMsg>(&data).or(Err(err_msg("invalid params")))?;

        let response = RpcResponse {
            method: "player_state".to_string(),
            params: RpcResult::PlayerState(instance_join(msg.params, tx, &account)?)
        };

        return Ok(response);
    }

    fn instance_ready(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<InstanceReadyMsg>(&data).or(Err(err_msg("invalid params")))?;

        let response = RpcResponse {
            method: "game_state".to_string(),
            params: RpcResult::GameState(instance_ready(msg.params, tx, &account)?)
        };

        return Ok(response);
    }

    fn instance_scores(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<InstanceReadyMsg>(&data).or(Err(err_msg("invalid params")))?;

        let response = RpcResponse {
            method: "instance_scores".to_string(),
            params: RpcResult::InstanceScores(instance_scores(msg.params, tx, &account)?)
        };

        return Ok(response);
    }



    fn player_state(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<PlayerStateMsg>(&data).or(Err(err_msg("invalid params")))?;

        let response = RpcResponse {
            method: "player_state".to_string(),
            params: RpcResult::PlayerState(player_state(msg.params, tx, &account)?)
        };

        return Ok(response);
    }

    fn player_cryps_set(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<PlayerCrypsSetMsg>(&data).or(Err(err_msg("invalid params")))?;

        let response = RpcResponse {
            method: "player_state".to_string(),
            params: RpcResult::PlayerState(player_cryps_set(msg.params, tx, &account)?)
        };

        return Ok(response);
    }

    fn player_vbox_accept(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<VboxAcceptMsg>(&data).or(Err(err_msg("invalid params")))?;

        let response = RpcResponse {
            method: "player_state".to_string(),
            params: RpcResult::PlayerState(vbox_accept(msg.params, tx, &account)?)
        };

        return Ok(response);
    }

    fn player_vbox_discard(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<VboxDiscardMsg>(&data).or(Err(err_msg("invalid params")))?;

        let response = RpcResponse {
            method: "player_state".to_string(),
            params: RpcResult::PlayerState(vbox_discard(msg.params, tx, &account)?)
        };

        return Ok(response);
    }


    fn player_vbox_combine(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<VboxCombineMsg>(&data).or(Err(err_msg("invalid params")))?;

        let response = RpcResponse {
            method: "player_state".to_string(),
            params: RpcResult::PlayerState(vbox_combine(msg.params, tx, &account)?)
        };

        return Ok(response);
    }

    fn player_vbox_apply(data: Vec<u8>, tx: &mut Transaction, account: Account, client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<VboxApplyMsg>(&data).or(Err(err_msg("invalid params")))?;

        let response = RpcResponse {
            method: "player_state".to_string(),
            params: RpcResult::PlayerState(vbox_apply(msg.params, tx, &account)?)
        };

        Rpc::send_msg(client, RpcResponse {
            method: "account_cryps".to_string(),
            params: RpcResult::CrypList(account_cryps(tx, &account)?)
        })?;

        return Ok(response);
    }

    fn player_vbox_reclaim(data: Vec<u8>, tx: &mut Transaction, account: Account, _client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<VboxReclaimMsg>(&data).or(Err(err_msg("invalid params")))?;

        let response = RpcResponse {
            method: "player_state".to_string(),
            params: RpcResult::PlayerState(vbox_reclaim(msg.params, tx, &account)?)
        };

        return Ok(response);
    }

    fn player_vbox_unequip(data: Vec<u8>, tx: &mut Transaction, account: Account, client: &mut WebSocket<TcpStream>) -> Result<RpcResponse, Error> {
        let msg = from_slice::<VboxUnequipMsg>(&data).or(Err(err_msg("invalid params")))?;

        let response = RpcResponse {
            method: "player_state".to_string(),
            params: RpcResult::PlayerState(vbox_unequip(msg.params, tx, &account)?)
        };

        Rpc::send_msg(client, RpcResponse {
            method: "account_cryps".to_string(),
            params: RpcResult::CrypList(account_cryps(tx, &account)?)
        })?;

        return Ok(response);
    }


}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct RpcResponse {
    method: String,
    params: RpcResult,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub enum RpcResult {
    CrypSpawn(Cryp),
    CrypForget(Cryp),
    CrypLearn(Cryp),
    CrypUnspec(Cryp),
    Account(Account),
    CrypList(Vec<Cryp>),
    GameState(Game),
    InstanceScores(Vec<(String, Score)>),
    // ZoneState(Zone),
    // ZoneClose(()),

    PlayerList(Vec<Player>),
    PlayerState(Player),
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct RpcMessage {
    method: String,
    token: Option<String>,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct CrypSpawnMsg {
    method: String,
    params: CrypSpawnParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct CrypSpawnParams {
    pub name: String,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct CrypLearnMsg {
    method: String,
    params: CrypLearnParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct CrypLearnParams {
    pub id: Uuid,
    pub skill: Skill,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct CrypForgetParams {
    pub id: Uuid,
    pub skill: Skill,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct CrypForgetMsg {
    method: String,
    params: CrypForgetParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct CrypUnspecParams {
    pub id: Uuid,
    pub spec: Spec,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct CrypUnspecMsg {
    method: String,
    params: CrypUnspecParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct GameStateMsg {
    method: String,
    params: GameStateParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct GameStateParams {
    pub id: Uuid,
}

// #[derive(Debug,Clone,Serialize,Deserialize)]
// struct GamePveMsg {
//     method: String,
//     params: GamePveParams,
// }

// #[derive(Debug,Clone,Serialize,Deserialize)]
// pub struct GamePveParams {
//     pub cryp_ids: Vec<Uuid>,
// }

#[derive(Debug,Clone,Serialize,Deserialize)]
struct GameSkillMsg {
    method: String,
    params: GameSkillParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct GameSkillParams {
    pub game_id: Uuid,
    pub cryp_id: Uuid,
    pub target_cryp_id: Option<Uuid>,
    pub skill: Skill,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct AccountCreateMsg {
    method: String,
    params: AccountCreateParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct AccountCreateParams {
    pub name: String,
    pub password: String,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct AccountLoginMsg {
    method: String,
    params: AccountLoginParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct AccountLoginParams {
    pub name: String,
    pub password: String,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct AccountCrypsMsg {
    method: String,
    params: (),
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct ZoneCreateMsg {
    method: String,
    params: (),
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct ZoneJoinMsg {
    method: String,
    params: ZoneJoinParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct ZoneJoinParams {
    pub zone_id: Uuid,
    pub node_id: u32,
    pub cryp_ids: Vec<Uuid>,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct ZoneCloseMsg {
    method: String,
    params: ZoneCloseParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct ZoneCloseParams {
    pub zone_id: Uuid,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct InstanceJoinMsg {
    method: String,
    params: InstanceJoinParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct InstanceJoinParams {
    pub cryp_ids: Vec<Uuid>,
    pub pve: bool,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct InstanceReadyMsg {
    method: String,
    params: InstanceReadyParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct InstanceReadyParams {
    pub instance_id: Uuid,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct PlayerStateMsg {
    method: String,
    params: PlayerStateParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct PlayerStateParams {
    pub instance_id: Uuid,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct PlayerCrypsSetMsg {
    method: String,
    params: PlayerCrypsSetParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct PlayerCrypsSetParams {
    pub cryp_ids: Vec<Uuid>,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct VboxAcceptMsg {
    method: String,
    params: VboxAcceptParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct VboxAcceptParams {
    pub instance_id: Uuid,
    pub group: usize,
    pub index: usize,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct VboxDiscardMsg {
    method: String,
    params: VboxDiscardParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct VboxDiscardParams {
    pub instance_id: Uuid,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct VboxCombineMsg {
    method: String,
    params: VboxCombineParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct VboxCombineParams {
    pub instance_id: Uuid,
    pub indices: Vec<usize>,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct VboxApplyMsg {
    method: String,
    params: VboxApplyParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct VboxApplyParams {
    pub instance_id: Uuid,
    pub cryp_id: Uuid,
    pub index: usize,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct VboxUnequipMsg {
    method: String,
    params: VboxUnequipParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct VboxUnequipParams {
    pub instance_id: Uuid,
    pub cryp_id: Uuid,
    pub target: Var,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct VboxReclaimMsg {
    method: String,
    params: VboxReclaimParams,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct VboxReclaimParams {
    pub instance_id: Uuid,
    pub index: usize,
}

// #[cfg(test)]
// mod tests {
//     use super::*;
//     use serde_cbor::to_vec;
//     #[test]
//     fn rpc_parse() {
//         let rpc = Rpc {};
//         let msg = GenerateMsg { method: "cryp_generate".to_string(), params: GenerateParams { level: 64 }  };
//         let v = to_vec(&msg).unwrap();
//         let received = rpc.receive(Message::Binary(v));
//     }
// }