use serde_cbor::{from_slice, to_vec};
use uuid::Uuid;

use postgres::transaction::Transaction;
use failure::Error;

// drops
use rand::prelude::*;
use rand::{thread_rng};
use rand::distributions::{LogNormal,WeightedIndex};

use account::Account;
use rpc::{ItemUseParams};
use cryp::{Stat, cryp_get, cryp_write};
use game::{GameMode};
use spec::{Spec, SpecType};

#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum ItemAction {
    RerollRedDamage,
    RerollBlueDamage,
    RerollSpeed,
    RerollHp,
    RerollRedShield,
    RerollBlueShield,
    RerollEvasion,

    SpecRedDamage5,
    SpecBlueDamage5,
    SpecRedShield5,
    SpecBlueShield5,
    SpecBlueEvasion5,
}

#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub struct Item {
    // mods: Vec<Mod>,
    id: Uuid,
    account: Uuid,
    action: ItemAction,
}

impl Item {
    pub fn new(action: ItemAction, account_id: Uuid) -> Item {
        let id = Uuid::new_v4();
        return Item {
            id,
            account: account_id,
            action,
        };
    }

    fn apply(&mut self, tx: &mut Transaction, target: Uuid) -> Result<(), Error> {
        match self.action {
            ItemAction::RerollHp => reroll(self, tx, target, Stat::Hp),
            ItemAction::RerollRedDamage => reroll(self, tx, target, Stat::RedDamage),
            ItemAction::RerollBlueDamage => reroll(self, tx, target, Stat::BlueDamage),
            ItemAction::RerollSpeed => reroll(self, tx, target, Stat::Speed),
            ItemAction::RerollRedShield => reroll(self, tx, target, Stat::RedShield),
            ItemAction::RerollBlueShield => reroll(self, tx, target, Stat::BlueShield),
            ItemAction::RerollEvasion => reroll(self, tx, target, Stat::Evasion),

            ItemAction::SpecRedDamage5 => spec_add(self, tx, target, SpecType::RedDamage5),
            ItemAction::SpecBlueDamage5 => spec_add(self, tx, target, SpecType::BlueDamage5),
            _ => unimplemented!(),
        }
    }
}

fn spec_add(item: &mut Item, tx: &mut Transaction, target: Uuid, spec_type: SpecType) -> Result<(), Error> {
    let mut cryp = cryp_get(tx, target, item.account)?;
    let spec = Spec::new(spec_type);
    cryp.spec_add(spec)?;
    cryp_write(cryp, tx)?;
    return Ok(());
}

fn reroll(item: &mut Item, tx: &mut Transaction, target: Uuid, stat: Stat) -> Result<(), Error> {
    let mut cryp = cryp_get(tx, target, item.account)?;
    cryp.roll_stat(stat);
    cryp_write(cryp, tx)?;
    return Ok(());
}

fn mode_drops(mode: GameMode) -> Vec<(ItemAction, usize)> {
    match mode {
        GameMode::Normal => vec![
            (ItemAction::RerollHp, 1),
            (ItemAction::RerollRedDamage, 1),
            (ItemAction::RerollBlueDamage, 1),
        ],
        GameMode::Pvp => vec![
            (ItemAction::RerollSpeed, 1),
        ],
        GameMode::Zone3v2Attack |
        GameMode::Zone2v2Caster |
        GameMode::Zone3v3MeleeMiniboss => vec![
            (ItemAction::RerollEvasion, 1),
            (ItemAction::RerollRedShield, 1),
            (ItemAction::RerollBlueShield, 1),
        ],
        GameMode::Zone3v3HealerBoss => vec![
            (ItemAction::RerollSpeed, 1),
        ],
        // _ => vec![
        //     (ItemAction::RerollHp, 1),
        //     (ItemAction::RerollRedDamage, 1),
        //     (ItemAction::RerollBlueDamage, 1),
        //     (ItemAction::RerollSpeed, 1),
        //     (ItemAction::RerollRedShield, 1),
        //     (ItemAction::RerollBlueShield, 1),
        //     (ItemAction::RerollEvasion, 1),
        // ],
    }
}

pub fn item_drop(tx: &mut Transaction, account_id: Uuid, mode: GameMode) -> Result<(), Error> {
    let mut rng = thread_rng();

    let log_normal = LogNormal::new(1.0, 1.0);
    let num_drops = log_normal.sample(&mut rng).floor() as u16;

    let actions = mode_drops(mode);
    println!("{:?} drops", num_drops);

    for _i in 0..num_drops {
        let dist = WeightedIndex::new(actions.iter().map(|item| item.1)).unwrap();
        let kind = actions[dist.sample(&mut rng)].0;
        let item = Item::new(kind, account_id);

        println!("{:?} dropped {:?}", account_id, item);
        item_create(item, tx, account_id)?;
    }

    Ok(())
}


pub fn item_create(item: Item, tx: &mut Transaction, account_id: Uuid) -> Result<Item, Error> {
    let item_bytes = to_vec(&item)?;

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

    let result = tx
        .query(query, &[&item.id, &account_id, &item_bytes])?;

    result.iter().next().expect("no row returned");

    return Ok(item);
}

pub fn item_use(params: ItemUseParams, tx: &mut Transaction, account: &Account) -> Result<(), Error> {
    let query = "
        SELECT data
        FROM items
        WHERE id = $1
        AND account = $2
        FOR UPDATE;
    ";

    let result = tx
        .query(query, &[&params.item, &account.id])?;

    let returned = result.iter().next().expect("no row returned");

    let item_bytes: Vec<u8> = returned.get(0);
    let mut item = from_slice::<Item>(&item_bytes)?;

    item.apply(tx, params.target)?;
    item_delete(tx, params.item)?;

    return Ok(());
}

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

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

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

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

    return Ok(());
}

pub fn items_list(tx: &mut Transaction, account: &Account) -> Result<Vec<Item>, Error> {
    let query = "
        SELECT data, id
        FROM items
        WHERE account = $1;
    ";

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

    let mut items = vec![];

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

        match from_slice::<Item>(&item_bytes) {
            Ok(i) => items.push(i),
            Err(_e) => {
                item_delete(tx, id)?;
            }
        };
    }

    return Ok(items);
}

// # max damage potion
// name

//   "MapMonstersCurseEffectOnSelfFinal3": {
//     "adds_tags": [],
//     "domain": "area",
//     "generation_type": "prefix",
//     "generation_weights": [],
//     "grants_buff": {},
//     "grants_effect": {},
//     "group": "MapHexproof",
//     "is_essence_only": false,
//     "name": "Hexwarded",
//     "required_level": 1,
//     "spawn_weights": [
//       {
//         "tag": "top_tier_map",
//         "weight": 0
//       },
//       {
//         "tag": "default",
//         "weight": 0
//       }
//     ],
//     "stats": [
//       {
//         "id": "map_item_drop_quantity_+%",
//         "max": 15,
//         "min": 15
//       },
//       {
//         "id": "map_item_drop_rarity_+%",
//         "max": 8,
//         "min": 8
//       },
//       {
//         "id": "map_monsters_curse_effect_on_self_+%_final",
//         "max": -60,
//         "min": -60
//       }
//     ]
//   },
