from decimal import Decimal, InvalidOperation
import uuid
from pyramid.response import Response
from pyramid.security import authenticated_userid

from pyramid.view import view_config
from sqlalchemy.orm import joinedload_all
import transaction
from brewman import groupfinder
from brewman.models import DBSession

from brewman.models.master import Product, CostCenter, LedgerType, Ledger
from brewman.models.validation_exception import TryCatchFunction, ValidationError
from brewman.models.voucher import Voucher, Batch, Inventory, VoucherType


@view_config(route_name='product_list', renderer='brewman:templates/angular_base.mako', permission='Authenticated')
@view_config(request_method='GET', route_name='product_id', renderer='brewman:templates/angular_base.mako',
             permission='Products')
@view_config(request_method='GET', route_name='product', renderer='brewman:templates/angular_base.mako',
             permission='Products')
def html(request):
    return {}


@view_config(request_method='POST', route_name='api_product', renderer='json', permission='Products')
@TryCatchFunction
def save(request):
    json = request.json_body

    name = json.get('Name', '').strip()
    if name == '':
        raise ValidationError('Name cannot be blank')

    units = json.get('Units', '').strip()

    try:
        fraction = Decimal(json.get('Fraction', 0))
        if fraction <= 0 or fraction > 1:
            raise ValidationError("Fraction must be a decimal > 0 and <= 1")
    except (ValueError, InvalidOperation):
        raise ValidationError("Fraction must be a decimal > 0 and <= 1")

    fraction_units = json.get('FractionUnits', '').strip()

    try:
        product_yield = Decimal(json.get('ProductYield', 1))
        if product_yield < 0 or product_yield > 1:
            raise ValidationError("Yield must be a decimal >= 0 <= 1")
    except (ValueError, InvalidOperation):
        raise ValidationError("Yield must be a decimal >= 0 <= 1")

    show_for_purchase = json.get('ShowForPurchase', True)
    product_group = json.get('ProductGroup', None)
    if product_group is None:
        raise ValidationError('Please choose a product group')
    product_group_id = uuid.UUID(product_group['ProductGroupID'])
    try:
        price = Decimal(json.get('Price', 0))
        if price < 0:
            raise ValidationError("Price must be a decimal > 0")
    except (ValueError, InvalidOperation):
        raise ValidationError("Price must be a decimal > 0")
    discontinued = json.get('Discontinued', False)
    item = Product(0, name, units, fraction, fraction_units, product_yield, show_for_purchase, product_group_id,
                   Ledger.all_purchases(), price, discontinued).create()
    transaction.commit()
    return product_info(item.id)


@view_config(request_method='POST', route_name='api_product_id', renderer='json', permission='Products')
@TryCatchFunction
def update(request):
    item = Product.by_id(uuid.UUID(request.matchdict['id']))
    if item.is_fixture:
        raise ValidationError("{0} is a fixture and cannot be edited or deleted.".format(item.full_name))
    item.name = request.json_body['Name'].strip()
    item.units = request.json_body['Units'].strip()
    try:
        item.fraction = Decimal(request.json_body['Fraction'])
        if item.fraction <= 0 or item.fraction > 1:
            raise ValidationError("Fraction must be a decimal > 0 and <= 1")
    except (ValueError, InvalidOperation):
        raise ValidationError("Fraction must be a decimal > 0 and <= 1")
    item.fraction_units = request.json_body['FractionUnits']

    try:
        item.product_yield = Decimal(request.json_body['ProductYield'])
        if item.product_yield < 0 or item.product_yield > 1:
            raise ValidationError("Yield must be a decimal >= 0 <= 1")
    except (ValueError, InvalidOperation):
        raise ValidationError("Yield must be a decimal >= 0 <= 1")

    item.show_for_purchase = request.json_body['ShowForPurchase']
    item.product_group_id = uuid.UUID(request.json_body['ProductGroup']['ProductGroupID'])
    item.ledger_id = Ledger.all_purchases()
    try:
        item.price = Decimal(request.json_body['Price'])
        if item.price < 0:
            raise ValidationError("Price must be a decimal > 0")
    except (ValueError, InvalidOperation):
        raise ValidationError("Price must be a decimal > 0")
    item.discontinued = request.json_body['Discontinued']
    transaction.commit()
    return product_info(item.id)


@view_config(request_method='DELETE', route_name='api_product_id', renderer='json', permission='Products')
@TryCatchFunction
def delete(request):
    product = Product.by_id(uuid.UUID(request.matchdict['id']))
    can_delete, reason = product.can_delete('Advanced Delete' in groupfinder(authenticated_userid(request), request))

    if can_delete:
        delete_with_data(product)
        transaction.commit()
        return product_info(None)
    else:
        transaction.abort()
        response = Response("Cannot delete product because {0}".format(reason))
        response.status_int = 500
        return response


@view_config(request_method='GET', route_name='api_product_id', renderer='json', permission='Products')
def show_id(request):
    return product_info(uuid.UUID(request.matchdict.get('id', None)))


@view_config(request_method='GET', route_name='api_product', renderer='json', permission='Products')
def show_blank(request):
    return product_info(None)


@view_config(request_method='GET', route_name='api_product', request_param='list', renderer='json',
             permission='Authenticated')
def show_list(request):
    list = Product.query().order_by(Product.discontinued).order_by(Product.product_group_id).order_by(
        Product.name).all()
    products = []
    for item in list:
        products.append({'Code': item.code, 'Name': item.name, 'Units': item.units, 'Price': item.price,
                         'ProductGroup': item.product_group.name, 'Discontinued': item.discontinued,
                         'IsFixture': item.is_fixture, 'Url': request.route_url('product_id', id=item.id)})
    return products


@view_config(request_method='GET', route_name='api_product', renderer='json', request_param='term',
             permission='Authenticated')
def show_term(request):
    term = request.GET.get('term', None)
    term = term if term is not None and term is not '' else None
    active = request.GET.get('a', None)
    active = active if active is not None else None
    count = request.GET.get('count', None)
    count = None if count is None or count == '' else int(count)
    list = []
    for index, item in enumerate(Product.list(term, active)):
        list.append({'ProductID': item.id, 'Name': item.full_name, 'Price': item.price})
        if count is not None and index == count - 1:
            break
    return list


def product_info(id):
    if id is None:
        product = {'Code': '(Auto)', 'Type': LedgerType.by_name('Creditors').id, 'IsActive': True,
                   'CostCenter': CostCenter.overall(), 'ShowForPurchase': True, 'Discontinued': False}
    else:
        product = Product.by_id(id)
        product = {'ProductID': product.id, 'Code': product.code, 'Name': product.name, 'Units': product.units,
                   'Fraction': product.fraction, 'FractionUnits': product.fraction_units,
                   'ProductYield': product.product_yield, 'ShowForPurchase': product.show_for_purchase,
                   'Discontinued': product.discontinued, 'IsFixture': product.is_fixture,
                   'ProductGroup': {'ProductGroupID': product.product_group_id},
                   'Ledger': {'LedgerID': product.ledger_id}, 'Price': product.price}
    return product


def delete_with_data(product):
    suspense_product = Product.by_id(Product.suspense())
    suspense_batch = Batch.by_id(Batch.suspense())
    query = Voucher.query().options(joinedload_all(Voucher.inventories, Inventory.product, innerjoin=True)) \
        .filter(Voucher.inventories.any(Inventory.product_id == product.id)) \
        .all()

    for voucher in query:
        others, sus_inv, prod_inv = False, None, None
        for inventory in voucher.inventories:
            if inventory.product_id == product.id:
                prod_inv = inventory
            elif inventory.product_id == Product.suspense():
                sus_inv = inventory
            else:
                others = True
        if not others and voucher.type == VoucherType.by_id('Issue'):
            DBSession.delete(voucher)
        else:
            if sus_inv is None:
                prod_inv.product = suspense_product
                prod_inv.quantity = prod_inv.amount
                prod_inv.rate = 1
                prod_inv.tax = 0
                prod_inv.discount = 0
                prod_inv.batch = suspense_batch
                voucher.narration += '\nSuspense \u20B9{0:,.2f} is {1}'.format(prod_inv.amount, product.name)
            else:
                sus_inv.quantity += prod_inv.amount
                DBSession.delete(prod_inv)
                voucher.narration += '\nDeleted \u20B9{0:,.2f} of {1}'.format(prod_inv.amount, product.name)
    for batch in product.batches:
        DBSession.delete(batch)
    DBSession.delete(product)
