import datetime
from decimal import Decimal
import uuid
from brewman.models import DBSession
from brewman.models.master import CostCenter, 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 issue_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('Issue'))
    DBSession.add(voucher)

    for item in json['Journals']:
        if int(item['Debit']) == 1:
            destination = uuid.UUID(item['CostCenter']['CostCenterID'])
        else:
            source = uuid.UUID(item['CostCenter']['CostCenterID'])
    if source == destination:
        raise ValidationError("Source cannot be the same as destination")
    batch_consumed = True if source == CostCenter.cost_center_purchase() else False if destination == CostCenter.cost_center_purchase() else None
    for item in json['Inventories']:
        issue_create_inventory(voucher, item, batch_consumed)
    for item in issue_create_journals(voucher.inventories, source, destination):
        voucher.journals.append(item)
        DBSession.add(item)
    journals_valid(voucher)
    inventory_valid(voucher)
    return voucher


def issue_create_inventory(voucher, item, batch_consumed):
    batch = Batch.by_id(uuid.UUID(item['Batch']['BatchID']))
    inventory_id = uuid.UUID(item['InventoryID']) if 'InventoryID' in item else None
    quantity = round(Decimal(item['Quantity']), 2)

    if quantity <= 0:
        raise ValidationError("Quantity of {0} cannot be zero".format(item.product.name))
    if batch_consumed == True and quantity > batch.quantity_remaining:
        raise ValidationError("Quantity available is {0} only".format(batch.quantity_remaining))
    if batch.name > voucher.date:
        raise ValidationError("Batch of {0} was purchased after the issue date".format(batch.product.name))
    if batch_consumed is None:
        pass
    elif batch_consumed:
        batch.quantity_remaining -= quantity
    else:
        batch.quantity_remaining += quantity

    item = Inventory(id=inventory_id, product_id=batch.product.id, quantity=quantity, rate=batch.rate, tax=batch.tax,
                     discount=batch.discount, batch=batch)
    voucher.inventories.append(item)
    DBSession.add(item)


def issue_create_journals(inventories, source, destination):
    amount = 0
    for item in inventories:
        amount += item.amount
    return [Journal(debit=-1, ledger_id=LedgerBase.all_purchases(), amount=round(amount, 2),
                    cost_center_id=source),
            Journal(debit=1, ledger_id=LedgerBase.all_purchases(), amount=round(amount, 2),
                    cost_center_id=destination)]


def issue_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.last_edit_date = datetime.datetime.utcnow()

    for item in voucher.journals:
        if item.debit == 1:
            destination = item.cost_center_id
        else:
            source = item.cost_center_id
    old_batch_consumed = True if source == CostCenter.cost_center_purchase() else False if destination == CostCenter.cost_center_purchase() else None

    for item in json['Journals']:
        if int(item['Debit']) == 1:
            destination = uuid.UUID(item['CostCenter']['CostCenterID'])
        else:
            source = uuid.UUID(item['CostCenter']['CostCenterID'])
    if source == destination:
        raise ValidationError("Source cannot be the same as destination")

    new_batch_consumed = True if source == CostCenter.cost_center_purchase() else False if destination == CostCenter.cost_center_purchase() else None
    if new_batch_consumed != old_batch_consumed:
        raise ValidationError("Purchase cost center cannot be changed")

    issue_update_inventory(voucher, json['Inventories'], old_batch_consumed)
    issue_update_journals(voucher, source, destination)
    journals_valid(voucher)
    inventory_valid(voucher)
    return voucher


def issue_update_inventory(voucher, newInventories, batch_consumed):
    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']):
                batch = Batch.by_id(uuid.UUID(i['Batch']['BatchID']))
                found = True
                if item.batch_id != batch.id:
                    raise ValidationError('Product / Batch 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 batch_consumed is True and new_quantity - old_quantity > quantity_remaining:
                    raise ValidationError("Maximum quantity available for {0} is {1}".format(item.product.full_name,
                                                                                             old_quantity + quantity_remaining))
                if item.batch.name > voucher.date:
                    raise ValidationError("Batch of {0} was purchased after the issue date".format(item.product.name))

                if batch_consumed is None:
                    pass
                elif batch_consumed:
                    item.batch.quantity_remaining -= (new_quantity - old_quantity)
                else:
                    item.batch.quantity_remaining += (new_quantity - old_quantity)

                item.quantity = new_quantity
                item.rate = batch.rate
                item.tax = batch.tax
                item.discount = batch.discount

                newInventories.remove(i)
                break
        if not found:
            if batch_consumed is None:
                pass
            elif batch_consumed:
                item.batch.quantity_remaining += item.quantity
            else:
                if item.batch.quantity_remaining < item.quantity:
                    raise ValidationError(
                        "Product {0} cannot be removed, minimum quantity is {1}".format(item.product.name,
                                                                                        item.batch.quantity_remaining))
                item.batch.quantity_remaining -= item.quantity
            DBSession.delete(item)
            voucher.inventories.remove(item)
    for i in newInventories:
        issue_create_inventory(voucher, i, batch_consumed)


def issue_update_journals(voucher, source, destination):
    amount = 0
    for item in voucher.inventories:
        amount += item.amount

    for i in range(len(voucher.journals), 0, -1):
        item = voucher.journals[i - 1]
        if item.debit == -1:
            item.cost_center_id = source
            item.amount = round(amount, 2)
        else:
            item.cost_center_id = destination
            item.amount = round(amount, 2)


