import uuid
import datetime
from decimal import Decimal, InvalidOperation

import pkg_resources
from pyramid.response import FileResponse
from pyramid.view import view_config
from sqlalchemy import desc
import transaction

from brewman.models import DBSession
from brewman.models.master import Ledger, Recipe, ProductGroup, MenuItem, ProductBase, RecipeItem
from brewman.models.validation_exception import TryCatchFunction, ValidationError


@view_config(route_name='recipe_list', permission='Recipes')
@view_config(request_method='GET', route_name='recipe_id', permission='Recipes')
@view_config(request_method='GET', route_name='recipe', permission='Recipes')
def html(request):
    package, resource = 'brewman:static/base.html'.split(':', 1)
    file = pkg_resources.resource_filename(package, resource)
    return FileResponse(file, request=request)


@view_config(request_method='POST', route_name='api_recipe', renderer='json', permission='Recipes')
@TryCatchFunction
def save(request):
    json = request.json_body
    name = json.get('Name', '').strip()
    if name == '':
        raise ValidationError('Name cannot be blank')

    try:
        recipe_quantity = Decimal(json.get('Quantity', 0))
        if recipe_quantity < 0:
            raise ValidationError("Quantity must be a decimal >= 0")
    except (ValueError, InvalidOperation):
        raise ValidationError("Quantity must be a decimal >= 0")

    try:
        date = datetime.datetime.strptime(request.json_body['Date'], '%d-%b-%Y')
    except (ValueError, KeyError, TypeError):
        raise ValidationError("Date is not a valid date")

    is_semi = json.get('IsSemi', False)

    if not is_semi:
        try:
            sale_price = Decimal(json.get('SalePrice', 0))
            if sale_price < 0:
                raise ValidationError("Sale Price must be a decimal >= 0")
        except (ValueError, InvalidOperation):
            raise ValidationError("Sale Price must be a decimal >= 0")
        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'])
        units = 'Portion'
        fraction = 1
        fraction_units = 'Portion'
    else:
        sale_price = 0
        product_group_id = ProductGroup.semi()
        units = json.get('Units', '').strip()
        try:
            fraction = Decimal(json.get('Fraction', 0))
            if fraction <= 0:
                raise ValidationError("Fraction must be a decimal > 0")
        except (ValueError, InvalidOperation):
            raise ValidationError("Fraction must be a decimal > 0")

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

    recipe_cost = 0
    if len(json['RecipeItems']) == 0:
        raise ValidationError("Recipe has no ingredients")
    recipe_items = []
    for item in json['RecipeItems']:
        product = ProductBase.by_id(uuid.UUID(item['Product']['ProductID']))
        quantity = round(Decimal(item['Quantity']), 2)
        ingredient_price = round(Decimal(item['Price']), 2)
        rate = ingredient_price / (product.fraction * product.product_yield)
        recipe_cost += rate * quantity
        recipe_items.append(RecipeItem(None, product.id, quantity, ingredient_price))

    menu_item = MenuItem(0, name, units=units, is_semi=is_semi, fraction=fraction, fraction_units=fraction_units,
                         product_group_id=product_group_id, ledger_id=Ledger.all_purchases(),
                         price=round(recipe_cost / recipe_quantity, 2), is_active=True).create()

    recipe = Recipe(date=date, product_id=menu_item.id, quantity=recipe_quantity, sale_price=sale_price, notes='')
    recipe.product = menu_item
    DBSession.add(recipe)
    for item in recipe_items:
        item.recipe_id = recipe.id
        recipe.recipe_items.append(item)
        DBSession.add(item)
    transaction.commit()
    return recipe_info(recipe.id)


@view_config(request_method='POST', route_name='api_recipe_id', renderer='json', permission='Recipes')
@TryCatchFunction
def update(request):
    json = request.json_body
    recipe = DBSession.query(Recipe).filter(Recipe.id == uuid.UUID(request.matchdict['id'])).first()
    name = json.get('Name', '').strip()
    if name == '':
        raise ValidationError('Name cannot be blank')
    if recipe.product.name != name and recipe.product.is_fixture:
        raise ValidationError("{0} is a fixture and cannot be edited or deleted.".format(recipe.product.name))
    try:
        date = datetime.datetime.strptime(json['Date'], '%d-%b-%Y')
    except (ValueError, KeyError, TypeError):
        raise ValidationError("Date is not a valid date")
    try:
        recipe.quantity = Decimal(json.get('Quantity', 0))
        if recipe.quantity < 0:
            raise ValidationError("Quantity must be a decimal >= 0")
    except (ValueError, InvalidOperation):
        raise ValidationError("Quantity must be a decimal >= 0")
    recipe.product.is_semi = json.get('IsSemi', False)

    if recipe.product.is_semi:
        recipe.product.units = json.get('Units', '').strip()
        try:
            recipe.product.fraction = Decimal(json.get('Fraction', 0))
            if recipe.product.fraction <= 0:
                raise ValidationError("Fraction must be a decimal > 0")
        except (ValueError, InvalidOperation):
            raise ValidationError("Fraction must be a decimal > 0")

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

    if len(json['RecipeItems']) == 0:
        raise ValidationError("Recipe has no ingredients")
    if date == recipe.date:
        recipe.date = date
        recipe_cost = 0
        try:
            recipe.sale_price = Decimal(json.get('SalePrice', 0))
            if recipe.sale_price < 0:
                raise ValidationError("Sale Price must be a decimal >= 0")
        except (ValueError, InvalidOperation):
            raise ValidationError("Sale Price must be a decimal >= 0")
        recipe_items = []
        for item in json['RecipeItems']:
            product = ProductBase.by_id(uuid.UUID(item['Product']['ProductID']))
            quantity = round(Decimal(item['Quantity']), 2)
            ingredient_price = round(Decimal(item['Price']), 2)
            rate = ingredient_price / (product.fraction * product.product_yield)
            recipe_cost += rate * quantity
            recipe_items.append(RecipeItem(None, product.id, quantity, ingredient_price))
        recipe.product.price = round(recipe_cost / recipe.quantity, 2)
        for i in range(len(recipe.recipe_items), 0, -1):
            recipe_item = recipe.recipe_items.pop()
            DBSession.delete(recipe_item)
        recipe.recipe_items = []
        for recipe_item in recipe_items:
            recipe_item.recipe_id = recipe.id
            recipe.recipe_items.append(recipe_item)
            DBSession.add(recipe_item)
    else:
        recipe_cost = 0
        try:
            sale_price = Decimal(json.get('SalePrice', 0))
            if sale_price < 0:
                raise ValidationError("Sale Price must be a decimal >= 0")
        except (ValueError, InvalidOperation):
            raise ValidationError("Sale Price must be a decimal >= 0")
        try:
            recipe_quantity = Decimal(json.get('Quantity', 0))
            if recipe_quantity < 0:
                raise ValidationError("Quantity must be a decimal >= 0")
        except (ValueError, InvalidOperation):
            raise ValidationError("Quantity must be a decimal >= 0")
        recipe_items = []
        for item in json['RecipeItems']:
            product = ProductBase.by_id(uuid.UUID(item['Product']['ProductID']))
            quantity = round(Decimal(item['Quantity']), 2)
            ingredient_price = round(Decimal(item['Price']), 2)
            rate = ingredient_price / (product.fraction * product.product_yield)
            recipe_cost += rate * quantity
            recipe_items.append(RecipeItem(None, product.id, quantity, ingredient_price))

        menu_item = recipe.product
        recipe = Recipe(date=date, product_id=menu_item.id, quantity=recipe_quantity, sale_price=sale_price, notes='')
        recipe.product = menu_item
        menu_item.price = round(recipe_cost / recipe_quantity, 2)
        DBSession.add(recipe)
        for item in recipe_items:
            item.recipe_id = recipe.id
            recipe.recipe_items.append(item)
            DBSession.add(item)
    transaction.commit()
    return recipe_info(recipe.id)


@view_config(request_method='DELETE', route_name='api_recipe_id', renderer='json', permission='Recipes')
def delete(request):
    recipe = DBSession.query(Recipe).filter(Recipe.id == uuid.UUID(request.matchdict['id'])).first()
    if len(recipe.product.recipes) > 1:
        DBSession.delete(recipe)
    else:
        DBSession.delete(recipe)
        DBSession.delete(recipe.product)
    transaction.commit()
    return recipe_info(None)


@view_config(request_method='GET', route_name='api_recipe_id', renderer='json', permission='Recipes')
def show_id(request):
    return recipe_info(uuid.UUID(request.matchdict['id']))


@view_config(request_method='GET', route_name='api_recipe', renderer='json', permission='Recipes')
def show_blank(request):
    return recipe_info(None)


@view_config(request_method='GET', route_name='api_recipe', renderer='json', request_param='list',
             permission='Authenticated')
def show_list(request):
    list = DBSession.query(Recipe).order_by(Recipe.product_id).order_by(desc(Recipe.date)).all()
    recipes = []
    previous_product_id = None
    for item in list:
        cost_price = 0
        current_cost_price = 0
        recipe_items = []
        for recipe_item in item.recipe_items:
            product = recipe_item.product
            cost_rate = (recipe_item.price / product.product_yield) / product.fraction
            cost_price += recipe_item.quantity * cost_rate
            current_cost_price += (product.price / product.product_yield) * recipe_item.quantity / product.fraction
            recipe_items.append({'Name': recipe_item.product.name, 'FractionUnits': recipe_item.product.fraction_units,
                                 'Price': cost_rate, 'Quantity': recipe_item.quantity})
        costing = 0 if item.sale_price == 0 else cost_price / item.sale_price
        current_costing = 0 if item.sale_price == 0 else current_cost_price / item.sale_price
        recipe = {'Name': item.product.name, 'Units': item.product.units, 'Date': item.date.strftime('%d-%b-%Y'),
                  'SalePrice': item.sale_price, 'CostPrice': cost_price, 'CurrentCostPrice': current_cost_price,
                  'Costing': costing, 'CurrentCosting': current_costing, 'IsSemi': item.product.is_semi,
                  'RecipeItems': recipe_items, 'IsLatest': previous_product_id != item.product.id,
                  'Url': request.route_url('recipe_id', id=item.id)}
        recipes.append(recipe)
        previous_product_id = item.product.id
    return recipes


def recipe_info(id):
    if id is None:
        info = {'Quantity': 1, 'IsSemi': False, 'Units': 'Portion', 'Fraction': 1, 'FractionUnits': 'Portion',
                'RecipeItems': []}
    else:
        recipe = DBSession.query(Recipe).filter(Recipe.id == id).first()
        info = {'RecipeID': recipe.id, 'Name': recipe.product.name, 'Quantity': recipe.quantity,
                'Date': recipe.date.strftime('%d-%b-%Y'), 'IsSemi': recipe.product.is_semi,
                'Units': recipe.product.units, 'Fraction': recipe.product.fraction,
                'FractionUnits': recipe.product.fraction_units, 'SalePrice': recipe.sale_price,
                'ProductGroup': {'ProductGroupID': recipe.product.product_group_id},
                'RecipeItems': []}
        for item in recipe.recipe_items:
            info['RecipeItems'].append({
                'Product': {'ProductID': item.product.id, 'Name': item.product.name,
                            'FractionUnits': item.product.fraction_units, 'Fraction': item.product.fraction,
                            'ProductYield': item.product.product_yield},
                'Quantity': item.quantity, 'Price': item.price})
    return info
