use uuid::Uuid;
use bcrypt::{hash, verify};
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use std::iter;
use serde_cbor::{from_slice};

use postgres::transaction::Transaction;

use rpc::{AccountCreateParams, AccountLoginParams};

use cryp::{Cryp, cryp_recover};
use player::{Player, player_delete};

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

static PASSWORD_MIN_LEN: usize = 12;

#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Account {
    pub id: Uuid,
    pub name: String,
    token: String,
}

#[derive(Debug,Clone,Serialize,Deserialize)]
struct AccountEntry {
    id: Uuid,
    name: String,
    password: String,
    token: String,
}

// MAYBE
// hash tokens with a secret
pub fn account_from_token(token: String, tx: &mut Transaction) -> Result<Account, Error> {
    let query = "
        SELECT id, name, token
        FROM accounts
        WHERE token = $1;
    ";

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

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

    let entry = Account {
        id: returned.get(0),
        name: returned.get(1),
        token: returned.get(2),
    };

    return Ok(entry);
}

pub fn account_create(params: AccountCreateParams, tx: &mut Transaction) -> Result<Account, Error> {
    let id = Uuid::new_v4();

    if params.password.len() < PASSWORD_MIN_LEN {
        return Err(err_msg("password must be at least 12 characters"));
    }

    let rounds = 8;
    let password = hash(&params.password, rounds)?;

    let mut rng = thread_rng();
    let token: String = iter::repeat(())
        .map(|()| rng.sample(Alphanumeric))
        .take(64)
        .collect();

    let account = AccountEntry {
        name: params.name,
        id,
        password,
        token,
    };

    let query = "
        INSERT INTO accounts (id, name, password, token)
        VALUES ($1, $2, $3, $4)
        RETURNING id, name, token;
    ";


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

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

    let entry = Account {
        id: returned.get(0),
        name: returned.get(1),
        token: returned.get(2),
    };

    println!("{:?} registered", entry.name);

    return Ok(entry);
}

pub fn account_login(params: AccountLoginParams, tx: &mut Transaction) -> Result<Account, Error> {
    let query = "
        SELECT id, name, token, password
        FROM accounts
        WHERE name = $1;
    ";

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

    let returned = match result.iter().next() {
        Some(row) => row,
        // MAYBE
        // verify gibberish to delay response for timing attacks
        None => return Err(err_msg("account not found")),
    };

    let entry = AccountEntry {
        id: returned.get(0),
        name: returned.get(1),
        token: returned.get(2),
        password: returned.get(3),
    };

    if !verify(&params.password, &entry.password)? {
        return Err(err_msg("password does not match"));
    }

    println!("{:?} logged in", entry.name);

    // MAYBE
    // update token?
    // don't necessarily want to log every session out when logging in

    let account = Account {
        id: entry.id,
        name: entry.name,
        token: entry.token,
    };

    return Ok(account);
}

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

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

    let cryps: Result<Vec<Cryp>, _> = result.iter()
        .map(|row| {
            let cryp_bytes: Vec<u8> = row.get(0);
            match from_slice::<Cryp>(&cryp_bytes) {
                Ok(c) => Ok(c),
                Err(_e) => cryp_recover(cryp_bytes, tx),
            }
        })
        .collect();

    // catch any errors
    if cryps.is_err() {
        return Err(err_msg("could not deserialize a cryp"));
    }

    let mut cryps = cryps.unwrap();
    cryps.sort_by_key(|c| c.id);
    return Ok(cryps);
}

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

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

    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::<Player>(&bytes) {
            Ok(i) => list.push(i),
            Err(_e) => {
                player_delete(tx, id)?;
            }
        };
    }

    list.sort_by_key(|c| c.instance);

    return Ok(list);
}