import json
from datetime import timedelta, datetime

from bottle import request, response
import model
from debug import debug
from passlib.hash import sha256_crypt

from util import salt


def missing_attributes(attributes):
    for attr in attributes:
        if attr not in request.json or request.json[attr] == '' or request.json[attr] is None:
            if str(attr) == 'session_id':
                return 'You are not signed in.'
            return 'Missing value for attribute ' + str(attr)
        if str(attr) == 'session_id':
            if not model.valid_session_id(request.json['session_id']):
                return 'You are not signed in.'
    return False


def login():
    if debug:
        missing = missing_attributes(['username'])
    else:
        missing = missing_attributes(['username', 'password'])
    if missing:
        return bad_request(missing)
    username = request.json['username']
    password = request.json['password']
    session_id = model.login(username, password)
    if session_id:
        return {'session_id': session_id}
    else:
        return forbidden('Invalid login data')


def depot():
    missing = missing_attributes(['session_id'])
    if missing:
        return bad_request(missing)
    user_id = model.get_user_id_by_session_id(request.json['session_id'])
    return {'data': model.get_user_ownership(user_id),
            'own_wealth': model.user_wealth(user_id)}


def register():
    missing = missing_attributes(['username', 'password'])
    if missing:
        return bad_request(missing)
    username = request.json['username'].strip()
    if username == '':
        return bad_request('Username can not be empty.')
    hashed_password = sha256_crypt.encrypt(request.json['password'] + salt)
    if model.user_exists(username):
        return bad_request('User already exists.')
    game_key = ''
    if 'game_key' in request.json:
        game_key = request.json['game_key'].strip().upper()
        if game_key != '' and not model.valid_key(game_key):
            return bad_request('Game key is not valid.')
    if model.register(username, hashed_password, game_key):
        return {'message': "successfully registered user"}
    else:
        return bad_request('Registration not successful')


def activate_key():
    missing = missing_attributes(['key', 'session_id'])
    if missing:
        return bad_request(missing)
    if model.valid_key(request.json['key']):
        user_id = model.get_user_id_by_session_id(request.json['session_id'])
        model.activate_key(request.json['key'], user_id)
        return {'message': "successfully activated key"}
    else:
        return bad_request('Invalid key.')


def order():
    missing = missing_attributes(['buy', 'session_id', 'amount', 'ownable', 'time_until_expiration'])
    if missing:
        return bad_request(missing)
    if not model.ownable_name_exists(request.json['ownable']):
        return bad_request('This kind of object can not be ordered.')

    buy = request.json['buy']
    sell = not buy
    session_id = request.json['session_id']
    amount = request.json['amount']
    try:
        amount = int(amount)
    except ValueError:
        return bad_request('Invalid amount.')
    if amount < 0:
        return bad_request('You can not order a negative amount.')
    if amount < 1:
        return bad_request('The minimum order size is 1.')
    ownable_name = request.json['ownable']
    time_until_expiration = float(request.json['time_until_expiration'])
    if time_until_expiration < 0:
        return bad_request('Invalid expiration time.')
    ownable_id = model.ownable_id_by_name(ownable_name)
    user_id = model.get_user_id_by_session_id(session_id)
    model.own(user_id, ownable_name)
    ownership_id = model.get_ownership_id(ownable_id, user_id)

    try:
        if request.json['limit'] == '':
            limit = None
        elif request.json['limit'] is None:
            limit = None
        else:
            limit = float(request.json['limit'])
    except ValueError:  # for example when float fails
        limit = None
    except KeyError:  # for example when limit was not specified
        limit = None

    try:
        if request.json['stop_loss'] == '':
            stop_loss = None
        elif request.json['stop_loss'] is None:
            stop_loss = None
        else:
            stop_loss = 'stop_loss' in request.json and request.json['stop_loss']
        if stop_loss is not None and limit is None:
            return bad_request('Can only set stop-loss for limit orders')
    except KeyError:  # for example when stop_loss was not specified
        stop_loss = None

    if sell:
        if not model.user_owns_at_least(amount, user_id, ownable_id):
            return bad_request('You can not sell more than you own.')
    try:
        expiry = datetime.strptime(model.current_db_time(), '%Y-%m-%d %H:%M:%S') + \
                 timedelta(minutes=time_until_expiration)
    except OverflowError:
        return bad_request('The expiration time is too far in the future.')
    model.place_order(buy, ownership_id, limit, stop_loss, amount, expiry)
    return {'message': "Order placed."}


def gift():
    missing = missing_attributes(['session_id', 'amount', 'object_name', 'username'])
    if missing:
        return bad_request(missing)
    if not model.ownable_name_exists(request.json['object_name']):
        return bad_request('This kind of object can not be given away.')
    if request.json['username'] == 'bank' or not model.user_exists(request.json['username']):
        return bad_request('There is no user with this name.')
    try:
        amount = float(request.json['amount'])
    except ValueError:
        return bad_request('Invalid amount.')
    ownable_id = model.ownable_id_by_name(request.json['object_name'])
    sender_id = model.get_user_id_by_session_id(request.json['session_id'])
    recipient_id = model.get_user_id_by_name(request.json['username'])
    if not model.user_owns_at_least(amount, sender_id, ownable_id):
        amount = model.available_amount(sender_id, ownable_id)

    model.send_ownable(sender_id, recipient_id, request.json['object_name'], amount)

    return {'message': "Gift sent."}


def orders():
    missing = missing_attributes(['session_id'])
    if missing:
        return bad_request(missing)
    data = model.get_user_orders(model.get_user_id_by_session_id(request.json['session_id']))
    return {'data': data}


def orders_on():
    missing = missing_attributes(['session_id', 'ownable'])
    if missing:
        return bad_request(missing)
    if not model.ownable_name_exists(request.json['ownable']):
        return bad_request('This kind of object can not be ordered.')
    user_id = model.get_user_id_by_session_id(request.json['session_id'])
    ownable_id = model.ownable_id_by_name(request.json['ownable'])
    data = model.get_ownable_orders(user_id, ownable_id)
    return {'data': data}


def cancel_order():
    missing = missing_attributes(['session_id', 'order_id'])
    if missing:
        return bad_request(missing)
    if not model.user_has_order_with_id(request.json['session_id'], request.json['order_id']):
        return bad_request('You do not have an order with that number.')
    model.delete_order(request.json['order_id'])
    return {'message': "Successfully deleted order"}


def change_password():
    missing = missing_attributes(['session_id', 'password'])
    if missing:
        return bad_request(missing)
    hashed_password = sha256_crypt.encrypt(request.json['password'] + salt)
    model.change_password(request.json['session_id'], hashed_password)
    model.sign_out_user(request.json['session_id'])
    return {'message': "Successfully changed password"}


def news():
    return {'data': model.news()}


def tradables():
    return {'data': model.ownables()}


def transactions():
    missing = missing_attributes(['session_id', 'ownable', 'limit'])
    if missing:
        return bad_request(missing)
    if not model.ownable_name_exists(request.json['ownable']):
        return bad_request('This kind of object can not have transactions.')
    return {'data': model.transactions(model.ownable_id_by_name(request.json['ownable']), request.json['limit'])}


def leaderboard():
    return {'data': model.leaderboard()}


def not_found(msg=''):
    response.status = 404
    if debug:
        msg = str(response.status) + ': ' + msg
    response.content_type = 'application/json'
    return json.dumps({"error_message": msg})


def forbidden(msg=''):
    response.status = 403
    if debug:
        msg = str(response.status) + ': ' + msg
    response.content_type = 'application/json'
    return json.dumps({"error_message": msg})


def bad_request(msg=''):
    response.status = 400
    if debug:
        msg = str(response.status) + ': ' + msg
    response.content_type = 'application/json'
    return json.dumps({"error_message": msg})