import random
from datetime import timedelta, datetime
from math import log2, ceil

import model
from debug import debug
from game import DEFAULT_ORDER_EXPIRY


def place_order(ownable_id):
    """
    places a new order according to the algorithm described in `assets/follower.py`
    :param ownable_id: on which ownable to place the order
    :return: True iff a new order was placed
    """

    best_buy_order, cheapest_sell_order = model.abs_spread(ownable_id)
    if best_buy_order is None or cheapest_sell_order is None:
        return False
    investors_id = model.bank_id()
    ownership_id = model.get_ownership_id(ownable_id, investors_id)

    if debug:  # the bot should only have one order
        model.cursor.execute('''
        SELECT COUNT(*) = 0
        FROM orders
        WHERE ownership_id = ? 
        ''', (ownership_id,))
        if not model.cursor.fetchone()[0]:
            raise AssertionError('The bot should no orders at this point.')

    amounts = model.cursor.execute('''
        SELECT ordered_amount 
        FROM orders, ownership
        WHERE orders.ownership_id = ownership.rowid
        AND ownership.ownable_id = ?
        ''', (ownable_id,)).fetchall()
    if len(amounts) < 2:
        raise AssertionError('We should have found at least two orders.')
    amounts = [random.choice(amounts)[0] for _ in range(int(ceil(log2(len(amounts)))))]
    amount = ceil(sum(amounts) / len(amounts))

    expiry = datetime.strptime(model.current_db_time(), '%Y-%m-%d %H:%M:%S') + timedelta(minutes=DEFAULT_ORDER_EXPIRY)

    limit = round(random.uniform(best_buy_order, cheapest_sell_order) * 10000) / 10000
    if limit - best_buy_order < cheapest_sell_order - limit:
        model.place_order(buy=True,
                          ownership_id=ownership_id,
                          limit=limit,
                          stop_loss=False,
                          amount=amount,
                          expiry=expiry)
    else:
        model.place_order(buy=False,
                          ownership_id=ownership_id,
                          limit=limit,
                          stop_loss=False,
                          amount=amount,
                          expiry=expiry)
    return True


def notify_expired_orders(orders):
    for order in orders:
        # order_id = order[0]
        ownership_id = order[1]

        # check if that was one of the bots orders
        bank_ownership_id = model.get_ownership_id(model.ownable_id_by_ownership_id(ownership_id), model.bank_id())
        if ownership_id != bank_ownership_id:
            continue

        # create a new order
        ownable_id = model.ownable_id_by_ownership_id(ownership_id)
        place_order(ownable_id)


def notify_order_traded(ownable_id):
    """
    Called after a trade has been done and now the auctions are finished.
    :param ownable_id: the ownable that was traded
    :return: True iff a new order was placed
    """
    model.connect()
    if ownable_id == model.currency_id():
        return False
    ownership_id = model.get_ownership_id(ownable_id, model.bank_id())

    if debug:  # the bot should only have one order
        model.cursor.execute('''
        SELECT COUNT(*) <= 1
        FROM orders
        WHERE ownership_id = ? 
        ''', (ownership_id,))
        if not model.cursor.fetchone()[0]:
            raise AssertionError('The bot should have at most one order.')

    model.cursor.execute('''
        SELECT rowid, ordered_amount, expiry_dt
        FROM orders 
        WHERE ownership_id = ? 
        -- no need for ORDER since the bot should have only one order
        UNION ALL
        SELECT * FROM (
            SELECT NULL, ordered_amount, expiry_dt
            FROM order_history
            WHERE ownership_id = ?
            ORDER BY rowid DESC -- equivalent to ordering by time created
        )
        LIMIT 1
    ''', (ownership_id, ownership_id,))
    data = model.cursor.fetchall()

    if not data:
        return place_order(ownable_id)
    my_last_order = data[0]
    last_order_open = my_last_order[0] is not None
    last_order_id = my_last_order[0]
    last_amount = my_last_order[1]
    expiry = my_last_order[2]
    dt_order_placed = datetime.strptime(expiry, '%Y-%m-%d %H:%M:%S') - timedelta(minutes=DEFAULT_ORDER_EXPIRY)

    model.cursor.execute('''
        SELECT COALESCE(SUM(amount), 0) >= 2 * ?
        FROM transactions
        WHERE ownable_id = ? 
          AND dt > ? -- interestingly >= would be problematic
    ''', (last_amount, ownable_id, dt_order_placed))

    if model.cursor.fetchone()[0]:
        if last_order_open:
            model.delete_order(last_order_id, 'Canceled')
        return place_order(ownable_id)

    return False


def main():
    """the initial part of the trading bot algorithm"""
    if model.get_user_orders(model.bank_id()):
        raise AssertionError('The trading bot already has some orders.')
    if input('Are you sure you want to place the initial orders? (type in "yes" or something else):') == 'yes':
        for ownable_id in model.ownable_ids():
            if ownable_id != model.currency_id():
                place_order(ownable_id)
    else:
        print('Not placing orders.')
    model.cleanup()


if __name__ == '__main__':
    main()