import datetime
from decimal import Decimal
import uuid
from sqlalchemy import func
from brewman.models import DBSession
from brewman.models.master import Product, LedgerBase
from brewman.models.operations import journals_valid, inventory_valid
from brewman.models.validation_exception import ValidationError
from brewman.models.voucher import Voucher, VoucherType, Batch, Inventory, Journal

__author__ = 'tanshu'


def purchase_create_voucher(json, user):
    dt = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
    voucher = Voucher(date=dt, narration=json['Narration'], user_id=user.id, type=VoucherType.by_name(json['Type']))
    DBSession.add(voucher)

    for item in json['Inventories']:
        purchase_create_inventory(voucher, item, dt)
    for item in purchase_create_journals(voucher.inventories, json['Journals'][0]['Ledger']['LedgerID']):
        voucher.journals.append(item)
        DBSession.add(item)
    journals_valid(voucher)
    inventory_valid(voucher)
    return voucher


def purchase_create_inventory(voucher, item, date):
    if 'Product' not in item or 'ProductID' not in item['Product']:
        raise ValidationError('No Product in item')
    product = Product.by_id(uuid.UUID(item['Product']['ProductID']))
    if product is None:
        raise ValidationError('No Product in item')
    inventory_id = uuid.UUID(item['InventoryID']) if 'InventoryID' in item else None
    quantity = round(Decimal(item['Quantity']), 2)
    rate = round(Decimal(item['Rate']), 2)
    tax = round(Decimal(item['Tax']), 5)
    discount = round(Decimal(item['Discount']), 5)
    batch = Batch(name=date, product_id=product.id, quantity_remaining=quantity, rate=rate, tax=tax,
                  discount=discount)
    DBSession.add(batch)
    inventory = Inventory(id=inventory_id, product_id=product.id, batch=batch, quantity=quantity, rate=rate, tax=tax,
                          discount=discount)
    product.price = rate
    voucher.inventories.append(inventory)
    DBSession.add(inventory)


def purchase_create_journals(inventories, ledgerID):
    otherLedger = LedgerBase.by_id(uuid.UUID(ledgerID))
    journals = dict()
    amount = 0
    for item in inventories:
        ledger = Product.by_id(item.product_id).ledger
        item_amount = round(item.amount, 2)
        amount += item_amount
        if ledger.id in journals:
            journals[ledger.id].amount += item_amount
        else:
            journals[ledger.id] = Journal(debit=1, cost_center_id=ledger.costcenter_id, ledger_id=ledger.id,
                                          amount=item_amount)
    journals[otherLedger.id] = Journal(debit=-1, cost_center_id=otherLedger.costcenter_id, ledger_id=otherLedger.id,
                                       amount=amount)
    return list(journals.values())


def purchase_update_voucher(voucher, json, user):
    voucher.date = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
    voucher.narration = json['Narration']
    voucher.user_id = user.id
    voucher.posted = False
    voucher.last_edit_date = datetime.datetime.utcnow()

    purchase_update_inventory(voucher, json['Inventories'])
    purchase_update_journals(voucher, json['Journals'])
    journals_valid(voucher)
    inventory_valid(voucher)
    return voucher


def purchase_update_inventory(voucher, newInventories):
    for it in range(len(voucher.inventories), 0, -1):
        item = voucher.inventories[it - 1]
        found = False
        for j in range(len(newInventories), 0, -1):
            i = newInventories[j - 1]
            if 'InventoryID' in i and item.id == uuid.UUID(i['InventoryID']):
                product = Product.by_id(uuid.UUID(i['Product']['ProductID']))
                found = True
                if item.product_id != product.id:
                    raise ValidationError('Product cannot be changed')
                new_quantity = round(Decimal(i['Quantity']), 2)
                old_quantity = round(Decimal(item.quantity), 2)
                quantity_remaining = round(Decimal(item.batch.quantity_remaining), 2)
                if new_quantity <= 0:
                    raise ValidationError("Quantity of {0} cannot be zero".format(item.product.name))
                if old_quantity > new_quantity and quantity_remaining < (old_quantity - new_quantity):
                    raise ValidationError("{0} has been issued, minimum quantity is".format(
                        old_quantity - quantity_remaining))
                item.batch.quantity_remaining -= (old_quantity - new_quantity)
                item.quantity = new_quantity
                rate = round(Decimal(i['Rate']), 2)
                discount = round(Decimal(i['Discount']), 5)
                tax = round(Decimal(i['Tax']), 5)
                if voucher.date != item.batch.name:
                    item.batch.name = voucher.date
                    if voucher.date < item.batch.name:
                    # TODO: check for issued products which might have been in a back date
                        pass
                item.rate = rate
                item.batch.rate = rate
                item.discount = discount
                item.batch.discount = discount
                item.tax = tax
                item.batch.tax = tax
                product.price = rate
                newInventories.remove(i)
                #TODO: Update all references of the batch with the new rates
                break
        if not found:
            uses = DBSession.query(func.count(Inventory.id)) \
                .filter(Inventory.batch_id == item.batch.id) \
                .filter(Inventory.id != item.id).scalar()
            if uses > 0:
                raise ValidationError("{0} has been issued, it cannot be deleted".format(item.product.name))
            else:
                DBSession.delete(item.batch)
                DBSession.delete(item)
                voucher.inventories.remove(item)
    for i in newInventories:
        product = Product.by_id(uuid.UUID(i['Product']['ProductID']))
        new_quantity = round(Decimal(i['Quantity']), 2)
        rate = round(Decimal(i['Rate']), 2)
        tax = round(Decimal(i['Tax']), 5)
        discount = round(Decimal(i['Discount']), 5)
        batch = Batch(name=voucher.date, product_id=product.id, quantity_remaining=new_quantity, rate=rate, tax=tax,
                      discount=discount)
        inventory = Inventory(id=None, product_id=product.id, batch=batch, quantity=new_quantity, rate=rate, tax=tax,
                              discount=discount)
        inventory.voucher_id = voucher.id
        DBSession.add(batch)
        inventory.batch_id = batch.id
        product.price = rate
        voucher.inventories.append(inventory)
        DBSession.add(inventory)


def purchase_update_journals(voucher, journals):
    other_ledger = [ff for ff in journals if ff['Debit'] == -1]
    other_ledger = LedgerBase.by_id(uuid.UUID(other_ledger[0]['Ledger']['LedgerID']))
    journals = dict()
    amount = 0
    for item in voucher.inventories:
        ledger = Product.by_id(item.product_id).ledger
        amount += item.amount
        if ledger.id in journals:
            journals[ledger.id].amount += item.amount
        else:
            journals[ledger.id] = Journal(debit=1, cost_center_id=ledger.costcenter_id, ledger_id=ledger.id,
                                          amount=item.amount)
    journals[other_ledger.id] = Journal(debit=-1, cost_center_id=other_ledger.costcenter_id, ledger_id=other_ledger.id,
                                       amount=amount)
    for i in range(len(voucher.journals), 0, -1):
        item = voucher.journals[i - 1]
        if item.ledger_id in journals:
            item.debit = journals[item.ledger_id].debit
            item.amount = round(journals[item.ledger_id].amount, 2)
            item.cost_center_id = journals[item.ledger_id].cost_center_id
            del journals[item.ledger_id]
        else:
            DBSession.delete(item)
            voucher.journals.remove(item)
    for item in journals.values():
        item.amount = round(item.amount, 2)
        voucher.journals.append(item)
        DBSession.add(item)


