# -*- coding: utf-8 -*-

from decimal import *

from django.db.models import Q
from django.db import IntegrityError

from django.contrib import messages
from django.core.cache import cache
from django.http import HttpResponse
from django.core.management import call_command
from django.utils.translation import ugettext as _
from django.forms.models import inlineformset_factory
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.decorators import permission_required
from django.shortcuts import render, redirect, get_object_or_404

from servo.lib.utils import paginate, send_csv
from servo.models import (Attachment, TaggedItem,
                          Product, ProductCategory,
                          Inventory, Location, inventory_totals,
                          GsxAccount,)
from servo.forms.product import (ProductForm, CategoryForm, ProductSearchForm,
                                 UploadPricesForm,)


def prep_list_view(request, group='all'):
    """
    Prepares the product list view
    """
    title = _("Products")
    search_hint = "products"
    all_products = Product.objects.all()
    categories = ProductCategory.objects.all()

    if group == 'all':
        group = ProductCategory(title=_('All'), slug='all')
    elif group == 'None':
        group = ProductCategory(title=_('None'), slug='none')
        all_products = all_products.filter(categories=None)
    else:
        group = categories.get(slug=group)
        all_products = group.get_products()

    if request.method == 'POST':
        form = ProductSearchForm(request.POST)
        if form.is_valid():
            fdata = form.cleaned_data

            description = fdata.get('description')
            if description:
                all_products = all_products.filter(description__icontains=description)

            title = fdata.get('title')
            if title:
                all_products = all_products.filter(title__icontains=title)

            code = fdata.get('code')
            if code:
                all_products = all_products.filter(code__icontains=code)

            tag = fdata.get('tag')
            if tag:
                tag = tag.tag
                title += u" / %s" % tag
                all_products = all_products.filter(tags__tag=tag)

            location = fdata.get('location')
            if location:
                all_products = all_products.filter(inventory__location=location)
    else:
        form = ProductSearchForm()

    title += u" / %s" % group.title
    page = request.GET.get("page")
    products = paginate(all_products.distinct(), page, 25)

    return locals()


def tags(request):
    """
    Returns all product tags
    """
    from servo.lib.utils import json_response
    tags = TaggedItem.objects.filter(content_type__model="product")
    tags = tags.distinct("tag").values_list("tag", flat=True)
    return json_response(list(tags))


def list_products(request, group='all'):
    data = prep_list_view(request, group)
    p, s = inventory_totals()
    data['total_sales_value'] = s
    data['total_purchase_value'] = p

    return render(request, "products/index.html", data)


@permission_required("servo.change_product")
def upload_gsx_parts(request, group=None):
    from servo.forms.product import PartsImportForm
    form = PartsImportForm()

    data = {'action': request.path}

    if request.method == "POST":

        form = PartsImportForm(request.POST, request.FILES)

        if form.is_valid():

            data = form.cleaned_data
            filename = "uploads/products/partsdb.csv"
            destination = open(filename, "wb+")

            for chunk in data['partsdb'].chunks():
                destination.write(chunk)

            call_command('importparts', filename, data['currency'],
                update_prices=data['update_prices'],
                import_vintage=data['import_vintage'],
            )

            messages.success(request, _("Parts database uploaded for processing..."))
            return redirect(list_products)

    data['form'] = form
    return render(request, "products/upload_gsx_parts.html", data)


@permission_required("servo.change_product")
def get_inventory_report(request):
    """
    Returns stocked amount of products at each location
    """
    import re
    from django.db import connection
    cursor = connection.cursor()
    location_map = {}

    for l in Location.objects.filter(enabled=True):
         location_map[str(l.pk)] = l.title

    # @TODO this should be rewritten as a pivot query
    # but this will have to do for now. This is still much
    # faster than using the ORM.
    query = """SELECT p.id, p.code, l.id, i.amount_stocked 
    FROM servo_product p, servo_inventory i, servo_location l
        WHERE p.id = i.product_id AND l.id = i.location_id
        ORDER BY p.id ASC"""
    cursor.execute(query)

    response = HttpResponse(content_type="text/plain; charset=utf-8")
    filename = 'servo_inventory_report.txt'
    #response['Content-Disposition'] = 'attachment; filename="%s"' % filename
    header = ['ID', 'CODE'] + location_map.values()
    response.write("\t".join(header) + "\n")
    inventory, codemap = {}, {}

    for k in cursor.fetchall():
        product_id = str(k[0])
        codemap[product_id] = k[1] # map product IDs to product codes
        inv_slot = {k[2]: k[3]}

        try:
            inventory[product_id].append(inv_slot)
        except KeyError:
            inventory[product_id] = [inv_slot]

    for k, v in inventory.iteritems():
        inventory_cols = []
        for i, x in location_map.iteritems():
            for p in v:
                amount = p.get(i, '0') # fill empty inventory slots with zeros
                inventory_cols.append(amount)

        code = str(codemap[k])
        row = [k, code] + inventory_cols
        response.write("\t".join(row) + "\n")

    return response


@permission_required("servo.change_product")
def download_products(request, group="all"):
    """
    Downloads entire product DB or just ones belonging to a group
    """
    filename = "products"
    data = u"ID\tCODE\tTITLE\tPURCHASE_PRICE\tSALES_PRICE\tSTOCKED\n"

    if group == "all":
        products = Product.objects.all()
    else:
        category = get_object_or_404(ProductCategory, slug=group)
        products = category.get_products()
        filename = group

    # @FIXME: Add total stocked amount to product
    # currently the last column is a placeholder for stock counts in inventory uploads
    for p in products:
        row = [str(i) for i in (p.pk, p.code, p.title, 
                                p.price_purchase_stock,
                                p.price_sales_stock, 0)]
        data += "\t".join(row) + "\n"

    return send_csv(data, filename)


@permission_required("servo.change_product")
def upload_products(request, group=None):
    """"
    Format should be the same as from download_products
    """
    import io
    from servo.forms import ProductUploadForm
    location = request.user.get_location()
    form = ProductUploadForm()

    if request.method == "POST":
        form = ProductUploadForm(request.POST, request.FILES)
        if form.is_valid():
            string = u''
            category = form.cleaned_data['category']
            data = form.cleaned_data['datafile'].read()

            for i in ('utf-8', 'latin-1',):
                try:
                    string = data.decode(i)
                except:
                    pass

            if not string:
                raise ValueError(_('Unsupported file encoding'))

            i = 0
            sio = io.StringIO(string, newline=None)

            for l in sio.readlines():
                cols = l.strip().split("\t")

                if cols[0] == "ID":
                    continue # Skip header row

                if len(cols) < 2:
                    continue # Skip empty rows

                if len(cols) < 6:  # No ID row, pad it
                    cols.insert(0, "")

                product, created = Product.objects.get_or_create(code=cols[1])

                # Remove Excel escapes
                product.title = cols[2].strip(' "').replace('""', '"')
                product.price_purchase_stock = cols[3].replace(',', '.')
                product.price_sales_stock = cols[4].replace(',', '.')
                product.save()

                if category:
                    product.categories.add(category)

                inventory, created = Inventory.objects.get_or_create(
                    product=product, location=location
                )
                inventory.amount_stocked = cols[5]
                inventory.save()
                i += 1

            messages.success(request, _(u"%d products imported") % i)

            return redirect(list_products)

    action = request.path
    title = _("Upload products")
    return render(request, "products/upload_products.html", locals())


@permission_required("servo.change_product")
def edit_product(request, pk=None, code=None, group='all'):
    """
    Edits a Product! :-)
    """
    initial = {}
    product = Product()

    data = prep_list_view(request, group)

    if pk is not None:
        product = get_object_or_404(Product, pk=pk)
        form = ProductForm(instance=product)

    if not group == 'all':
        cat = get_object_or_404(ProductCategory, slug=group)
        initial = {'categories': [cat]}
        data['group'] = cat

    product.update_photo()

    if code is not None:
        product = cache.get(code)

    form = ProductForm(instance=product, initial=initial)
    InventoryFormset = inlineformset_factory(Product,
                                             Inventory,
                                             extra=1,
                                             max_num=1,
                                             exclude=[])

    formset = InventoryFormset(
        instance=product,
        initial=[{'location': request.user.location}]
    )

    if request.method == "POST":

        form = ProductForm(request.POST, request.FILES, instance=product)

        if form.is_valid():

            product = form.save()
            content_type = ContentType.objects.get(model="product")

            for a in request.POST.getlist("attachments"):
                doc = Attachment.objects.get(pk=a)
                product.attachments.add(doc)

            tags = [x for x in request.POST.getlist('tag') if x != '']

            for t in tags:
                tag, created = TaggedItem.objects.get_or_create(
                    content_type=content_type,
                    object_id=product.pk,
                    tag=t)
                tag.save()

            formset = InventoryFormset(request.POST, instance=product)

            if formset.is_valid():
                formset.save()
                messages.success(request, _(u"Product %s saved") % product.code)
                return redirect(product)
            else:
                messages.error(request, _('Error in inventory details'))
        else:
            messages.error(request, _('Error in product info'))

    data['form'] = form
    data['product'] = product
    data['formset'] = formset
    data['title'] = product.title

    return render(request, "products/form.html", data)


@permission_required("servo.delete_product")
def delete_product(request, pk, group):
    from django.db.models import ProtectedError
    product = get_object_or_404(Product, pk=pk)

    if request.method == 'POST':
        try:
            product.delete()
            Inventory.objects.filter(product=product).delete()
            messages.success(request, _("Product deleted"))
        except ProtectedError:
            messages.error(request, _('Cannot delete product'))

        return redirect(list_products, group)

    action = request.path
    return render(request, 'products/remove.html', locals())


def view_product(request, pk=None, code=None, group=None):

    product = Product()
    inventory = Inventory.objects.none()

    try:
        product = get_object_or_404(Product, pk=pk)
        inventory = Inventory.objects.filter(product=product)
    except Product.DoesNotExist:
        product = cache.get(code)

    data = prep_list_view(request, group)

    data['product'] = product
    data['inventory'] = inventory
    data['title'] = '%s - %s' % (product.code, product.title)

    # Collect data for Sales/Purchases/Invoices tabs
    results_per_page = 50
    page = request.GET.get('page')

    sales = product.serviceorderitem_set.all().select_related('order')
    sales = sales.order_by('-id')
    data['sales'] = paginate(sales, page, results_per_page)

    purchases = product.purchaseorderitem_set.all().select_related('purchase_order')
    purchases = purchases.order_by('-id')
    data['purchases'] = paginate(purchases, page, results_per_page)

    invoices = product.invoiceitem_set.all().select_related('invoice')
    invoices = invoices.order_by('-id')
    data['invoices'] = paginate(invoices, page, results_per_page)

    return render(request, "products/view.html", data)


@permission_required("servo.change_productcategory")
def edit_category(request, slug=None, parent_slug=None):

    form = CategoryForm()
    category = ProductCategory()

    if slug is not None:
        category = get_object_or_404(ProductCategory, slug=slug)
        form = CategoryForm(instance=category)

    if parent_slug is not None:
        parent = get_object_or_404(ProductCategory, slug=parent_slug)
        form = CategoryForm(initial={'parent': parent.pk})

    if request.method == "POST":
        form = CategoryForm(request.POST, instance=category)
        if form.is_valid():
            try:
                category = form.save()
            except IntegrityError:
                error = _(u'Category %s already exists') % category.title
                messages.error(request, error)
                return redirect(list_products)
            messages.success(request, _(u"Category %s saved") % category.title)
            return redirect(category)
        else:
            messages.error(request, form.errors)
            return redirect(list_products)

    return render(request, "products/category_form.html", locals())


@permission_required("servo.delete_productcategory")
def delete_category(request, slug):

    category = get_object_or_404(ProductCategory, slug=slug)

    if request.method == "POST":
        category.delete()
        messages.success(request, _("Category deleted"))
        return redirect(list_products)

    data = {'category': category}
    data['action'] = request.path
    return render(request, 'products/delete_category.html', data)


@permission_required("servo.change_order")
def choose_product(request, order_id, product_id=None, target_url="orders-add_product"):
    """
    order_id can be either Service Order or Purchase Order
    """
    data = {'order': order_id}
    data['action'] = request.path
    data['target_url'] = target_url

    if request.method == "POST":
        query = request.POST.get('q')

        if len(query) > 2:
            products = Product.objects.filter(
                Q(code__icontains=query) | Q(title__icontains=query)
            )
            data['products'] = products

        return render(request, 'products/choose-list.html', data)

    return render(request, 'products/choose.html', data)


def get_info(request, location, code):
    try:
        product = Product.objects.get(code=code)
        inventory = Inventory.objects.filter(product=product)
    except Product.DoesNotExist:
        product = cache.get(code)

    return render(request, 'products/get_info.html', locals())


def update_price(request, pk):
    """
    Updates the price info from GSX
    """
    product = get_object_or_404(Product, pk=pk)
    try:
        GsxAccount.default(request.user)
        product.update_price()
        messages.success(request, _('Price info updated from GSX'))
    except Exception as e:
        messages.error(request, _('Failed to update price from GSX'))

    return redirect(product)


def upload_prices(request):
    """
    Uploads new price data from Excel file
    """
    form = UploadPricesForm()
    action = request.path
    title = _('Upload new price data')

    if request.method == 'POST':
        form = UploadPricesForm(request.POST, request.FILES)

        if form.is_valid():
            import django_excel as excel
            import pyexcel.ext.xls # import it to handle xls file
            import pyexcel.ext.xlsx # import it to handle xlsx file

            counter, errors = 0, 0
            datafile = form.cleaned_data['datafile']
            sheet = datafile.get_sheet()
            del(sheet.row[0]) # skip header row

            for row in sheet:
                if not len(row[0]):
                    continue # skip empty rows

                try:
                    product = Product.objects.get(code=row[0])
                except Product.DoesNotExist:
                    if form.cleaned_data.get('create_new'):
                        product = Product(code=row[0])
                    else:
                        error = _('Error on row %d - product %s not found') % (counter+1, row[0])
                        messages.error(request, error)
                        return redirect(list_products)
                
                product.title = row[1]
                product.description = row[2]
                product.price_sales_exchange = row[3]

                if form.cleaned_data.get('set_fixed'):
                    product.fixed_price = True

                product.save()
                counter += 1

            msg = _('Price info of %d products uploaded successfully') % counter
            messages.success(request, msg)
            return redirect(list_products)

    return render(request, "products/upload_prices.html", locals())
