# -*- coding: utf-8 -*-
# Copyright (c) 2013, First Party Software
# All rights reserved.

# Redistribution and use in source and binary forms, with or without modification, 
# are permitted provided that the following conditions are met:

# 1. Redistributions of source code must retain the above copyright notice, 
# this list of conditions and the following disclaimer.

# 2. Redistributions in binary form must reproduce the above copyright notice, 
# this list of conditions and the following disclaimer in the documentation 
# and/or other materials provided with the distribution.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
# SUCH DAMAGE.

import gsxws

from django.db.models import Q
from django.contrib import messages

from django.core.cache import cache
from django.shortcuts import render, redirect, get_object_or_404

from django.utils.translation import ugettext as _
from django.template.defaultfilters import slugify
from django.views.decorators.cache import cache_page
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

from servo.models import Device, Order, Product, GsxAccount, ServiceOrderItem
from servo.forms.devices import DeviceForm, DeviceUploadForm, DeviceSearchForm


class RepairDiagnosticResults:
    pass


class DiagnosticResults(object):
    def __init__(self, diags):
        if not diags.diagnosticTestData:
            raise gsxws.GsxError('Missing diagnostic data')

        self.diags = dict(result={}, profile={}, report={})

        for r in diags.diagnosticTestData.testResult.result:
            self.diags['result'][r.name] = r.value

        for r in diags.diagnosticProfileData.profile.unit.key:
            self.diags['profile'][r.name] = r.value

        for r in diags.diagnosticProfileData.report.reportData.key:
            self.diags['report'][r.name] = r.value

    def __iter__(self):
        return iter(self.diags)


def model_from_slug(product_line, model=None):
    """
    Returns product description for model slug or models dict for
    the specified product line
    """
    if not cache.get("slugmap"):
        slugmap = {}  # Map model slug to corresponding product description
        product_lines = gsxws.products.models()

        for k, v in product_lines.items():
            d = {}
            for p in v['models']:
                slug = slugify(p)
                d[slug] = p

            slugmap[k] = d

        cache.set("slugmap", slugmap)

    models = cache.get("slugmap").get(product_line)

    if model is not None:
        return models.get(model)

    return models


def prep_list_view(request, product_line=None, model=None):
    title = _('Devices')
    all_devices = Device.objects.all()
    product_lines = gsxws.products.models()

    if product_line is None:
        product_line = product_lines.keys()[0]

    models = model_from_slug(product_line)

    if model is None:
        model = models.keys()[0]
        title = product_lines[product_line]['name']
    else:
        title = models.get(model)

    if product_line == "OTHER":
        all_devices = all_devices.filter(product_line=product_line)
    else:
        all_devices = all_devices.filter(slug=model)

    page = request.GET.get('page')
    paginator = Paginator(all_devices, 50)

    try:
        devices = paginator.page(page)
    except PageNotAnInteger:
        devices = paginator.page(1)
    except EmptyPage:
        devices = paginator.page(paginator.num_pages)

    return locals()


def prep_detail_view(request, pk, product_line=None, model=None):
    if pk is None:
        device = Device()
    else:
        device = Device.objects.get(pk=pk)

    data = prep_list_view(request, product_line, model)

    data['device'] = device
    data['title'] = device.description

    return data


def index(request, product_line=None, model=None):
    if request.session.get('return_to'):
        del(request.session['return_to'])

    data = prep_list_view(request, product_line, model)

    if data['all_devices'].count() > 0:
        return redirect(data['all_devices'].latest())

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


def delete_device(request, product_line, model, pk):
    dev = Device.objects.get(pk=pk)

    if request.method == 'POST':
        from django.db.models import ProtectedError
        try:
            dev.delete()
            messages.success(request, _("Device deleted"))
        except ProtectedError:
            messages.error(request, _("Cannot delete device with GSX repairs"))
            return redirect(dev)

        return redirect(index)

    data = {'action': request.path}
    data['device'] = dev

    return render(request, "devices/remove.html", data)


def edit_device(request, pk=None, product_line=None, model=None):
    """
    Edits an existing device or adds a new one
    """
    device = Device()
    device.sn = request.GET.get('sn', '')

    if product_line is not None:
        device.product_line = product_line

    if model is not None:
        device.product_line = product_line
        device.description = model_from_slug(product_line, model)

    if pk is not None:
        device = Device.objects.get(pk=pk)

    form = DeviceForm(instance=device)

    if request.method == "POST":

        form = DeviceForm(request.POST, request.FILES, instance=device)

        if form.is_valid():
            device = form.save()
            messages.success(request, _(u"%s saved") % device.description)
            device.add_tags(request.POST.getlist('tag'))

            return redirect(view_device,
                            pk=device.pk,
                            product_line=device.product_line,
                            model=device.slug)

    data = prep_detail_view(request, pk, product_line, model)
    data['form'] = form

    return render(request, 'devices/form.html', data)


def view_device(request, pk, product_line=None, model=None):
    data = prep_detail_view(request, pk, product_line, model)
    return render(request, "devices/view.html", data)


def diagnostics(request, pk):
    """
    Fetches MRI diagnostics or initiates iOS diags from GSX
    """
    device = get_object_or_404(Device, pk=pk)

    if request.GET.get('a') == 'init':
        if request.method == 'POST':
            from gsxws import diagnostics
            order = request.POST.get('order')
            order = device.order_set.get(pk=order)
            email = request.POST.get('email')
            diag = diagnostics.Diagnostics(serialNumber=device.sn)
            diag.emailAddress = email
            diag.shipTo = order.location.gsx_shipto

            try:
                GsxAccount.default(request.user)
                res = diag.initiate()
                msg = _('Diagnostics initiated - diags://%s') % res
                order.notify("init_diags", msg, request.user)
                messages.success(request, msg)
            except gsxws.GsxError, e:
                messages.error(request, e)

            return redirect(order)

        order = request.GET.get('order')
        order = device.order_set.get(pk=order)
        customer = order.customer
        url = request.path
        return render(request, "devices/diagnostic_init.html", locals())

    if request.GET.get('a') == 'get':
        try:
            diagnostics = device.get_diagnostics(request.user)
            if device.is_ios():
                diagnostics = DiagnosticResults(diagnostics)
                return render(request, "devices/diagnostic_ios.html", locals())
            return render(request, "devices/diagnostic_results.html", locals())
        except gsxws.GsxError, e:
            return render(request, "devices/diagnostic_error.html", {'error': e})

    return render(request, "devices/diagnostics.html", locals())


def get_gsx_search_results(request, what, param, query):
    """
    The second phase of a GSX search.
    There should be an active GSX session open at this stage.
    """
    data    = {}
    results = []
    query   = query.upper()
    device  = Device(sn=query)
    error_template = "search/results/gsx_error.html"

    # @TODO: this isn't a GSX search. Move it somewhere else.
    if what == "orders":
        try:
            if param == 'serialNumber':
                device = Device.objects.get(sn__exact=query)
            if param == 'alternateDeviceId':
                device = Device.objects.get(imei__exact=query)
        except (Device.DoesNotExist, ValueError,):
            return render(request, "search/results/gsx_notfound.html")

        orders = device.order_set.all()
        return render(request, "orders/list.html", locals())

    if what == "warranty":
        # Update wty info if been here before
        try:
            device = Device.objects.get(sn__exact=query)
            device.update_gsx_details()
        except Exception:
            try:
                device = Device.from_gsx(query)
            except Exception as e:
                return render(request, error_template, {'message': e})

        results.append(device)

        # maybe it's a device we've already replaced...
        try:
            soi = ServiceOrderItem.objects.get(sn__iexact=query)
            results[0].repeat_service = soi.order
        except ServiceOrderItem.DoesNotExist:
            pass

    if what == "parts":
        # looking for parts
        if param == "partNumber":
            # ... with a part number
            part = gsxws.Part(partNumber=query)

            try:
                partinfo = part.lookup()
            except gsxws.GsxError, e:
                return render(request, error_template, {'message': e})

            product = Product.from_gsx(partinfo)
            cache.set(query, product)
            results.append(product)

        if param == "serialNumber":
            # ... with a serial number
            try:
                results = device.get_parts()
                data['device'] = device
            except Exception, e:
                return render(request, error_template, {'message': e})

        if param == "productName":
            product = gsxws.Product(productName=query)
            parts = product.parts()
            for p in parts:
                results.append(Product.from_gsx(p))

    if what == "repairs":
        # Looking for GSX repairs
        if param == "serialNumber":
            # ... with a serial number
            try:
                device = gsxws.Product(query)
                #results = device.repairs()
                # @TODO: move the encoding hack to py-gsxws
                for i, p in enumerate(device.repairs()):
                    d = {'purchaseOrderNumber': p.purchaseOrderNumber}
                    d['repairConfirmationNumber'] = p.repairConfirmationNumber
                    d['createdOn'] = p.createdOn
                    d['customerName'] = p.customerName.encode('utf-8')
                    d['repairStatus'] = p.repairStatus
                    results.append(d)
            except gsxws.GsxError, e:
                return render(request, "search/results/gsx_notfound.html")

        elif param == "dispatchId":
            # ... with a repair confirmation number
            repair = gsxws.Repair(number=query)
            try:
                results = repair.lookup()
            except gsxws.GsxError as message:
                return render(request, error_template, locals())

    return render(request, "devices/search_gsx_%s.html" % what, locals())


def search_gsx(request, what, param, query):
    """
    The first phase of a GSX search
    """
    title = _(u'Search results for "%s"') % query

    try:
        act = request.session.get("gsx_account")
        act = None
        if act is None:
            GsxAccount.default(user=request.user)
        else:
            act.connect(request.user)
    except gsxws.GsxError as message:
        return render(request, "devices/search_gsx_error.html", locals())

    if request.is_ajax():
        if what == "parts":
            try:
                dev = Device.from_gsx(query)
                products = dev.get_parts()
                return render(request, "devices/parts.html", locals())
            except gsxws.GsxError as message:
                return render(request, "search/results/gsx_error.html", locals())

        return get_gsx_search_results(request, what, param, query)

    return render(request, "devices/search_gsx.html", locals())


def search(request):
    """
    Searching for devices from the main navbar
    """
    query = request.GET.get("q", '').strip()
    request.session['search_query'] = query

    query = query.upper()
    valid_arg = gsxws.validate(query)

    if valid_arg in ('serialNumber', 'alternateDeviceId',):
        return redirect(search_gsx, "warranty", valid_arg, query)

    devices = Device.objects.filter(
        Q(sn__icontains=query) | Q(description__icontains=query)
    )

    title = _(u'Devices matching "%s"') % query

    return render(request, "devices/search.html", locals())


def find(request):
    """
    Searching for device from devices/find
    """
    title = _("Device search")
    form = DeviceSearchForm()
    results = Device.objects.none()

    if request.method == 'POST':
        form = DeviceSearchForm(request.POST)
        if form.is_valid():
            fdata = form.cleaned_data
            results = Device.objects.all()

            if fdata.get("product_line"):
                results = results.filter(product_line__in=fdata['product_line'])
            if fdata.get("warranty_status"):
                results = results.filter(warranty_status__in=fdata['warranty_status'])
            if fdata.get("description"):
                results = results.filter(description__icontains=fdata['description'])
            if fdata.get("sn"):
                results = results.filter(sn__icontains=fdata['sn'])
            if fdata.get("date_start"):
                results = results.filter(created_at__range=[fdata['date_start'],
                                         fdata['date_end']])

    paginator = Paginator(results, 100)
    page = request.GET.get("page")

    try:
        devices = paginator.page(page)
    except PageNotAnInteger:
        devices = paginator.page(1)
    except EmptyPage:
        devices = paginator.page(paginator.num_pages)

    return render(request, "devices/find.html", locals())


#@cache_page(60*5)
def parts(request, pk, order_id, queue_id):
    """
    Lists available parts for this device/order
    taking into account the order's queues GSX Sold-To
    and the Location's corresponding GSX account
    """
    from decimal import InvalidOperation
    
    device = Device.objects.get(pk=pk)
    order = device.order_set.get(pk=order_id)

    try:
        # remember the right GSX account
        act = GsxAccount.default(request.user, order.queue)
        request.session['gsx_account'] = act.pk
        products = device.get_parts()
    except gsxws.GsxError as message:
        return render(request, "search/results/gsx_error.html", locals())
    except AttributeError:
        message = _('Invalid serial number for parts lookup')
        return render(request, "search/results/gsx_error.html", locals())
    except InvalidOperation:
        message = _('Error calculating prices. Please check your system settings.')
        return render(request, "search/results/gsx_error.html", locals())

    return render(request, "devices/parts.html", locals())


def model_parts(request, product_line=None, model=None):
    """
    Shows parts for this device model
    """
    data = prep_list_view(request, product_line, model)

    if cache.get("slugmap") and model:
        models = cache.get("slugmap")[product_line]
        data['what']        = "parts"
        data['param']       = "productName"
        data['query']       = models[model]
        data['products']    = Product.objects.filter(tags__tag=data['query'])

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


def choose(request, order_id):
    """
    Choosing a device from within an SRO
    Does GSX lookup in case device is not found locally
    """
    context = {'order': order_id}

    if request.method == "POST":

        query = request.POST.get('q').upper()
        results = Device.objects.filter(Q(sn__iexact=query) | Q(imei=query))

        if len(results) < 1:
            try:
                current_order = request.session.get("current_order_id")
                current_order = Order.objects.get(pk=current_order)
                if current_order and current_order.queue:
                    GsxAccount.default(request.user, current_order.queue)
                else:
                    GsxAccount.default(request.user)
                    results = [Device.from_gsx(query)]
            except Exception as e:
                context['error'] = e
                return render(request, "devices/choose-error.html", context)

        context['results'] = results
    return render(request, "devices/choose.html", context)


def upload_devices(request):
    """
    User uploads device DB as tab-delimited CSV file
    SN  USERNAME    PASSWORD    NOTES
    """
    gsx_account = None
    form = DeviceUploadForm()

    if request.method == "POST":
        form = DeviceUploadForm(request.POST, request.FILES)

        if form.is_valid():
            i = 0
            df = form.cleaned_data['datafile'].read()

            if form.cleaned_data.get('do_warranty_check'):
                gsx_account = GsxAccount.default(request.user)

            for l in df.split("\r"):
                l = l.decode("latin-1").encode("utf-8")
                row = l.strip().split("\t")

                if gsx_account:
                    try:
                        device = Device.from_gsx(row[0])
                    except Exception, e:
                        messages.error(request, e)
                        break
                else:
                    device = Device.objects.get_or_create(sn=row[0])[0]

                try:
                    device.username = row[1]
                    device.password = row[2]
                    device.notes = row[3]
                except IndexError:
                    pass

                device.save()
                i += 1

                if form.cleaned_data.get("customer"):
                    customer = form.cleaned_data['customer']
                    customer.devices.add(device)

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

            return redirect(index)

    data = {'form': form, 'action': request.path}
    return render(request, "devices/upload_devices.html", data)


def update_gsx_details(request, pk):
    """
    Updates devices GSX warranty details
    """
    device = get_object_or_404(Device, pk=pk)
    try:
        GsxAccount.default(request.user)
        device.update_gsx_details()
        messages.success(request, _("Warranty status updated successfully"))
    except Exception, e:
        messages.error(request, e)

    if request.session.get('return_to'):
        return redirect(request.session['return_to'])

    return redirect(device)


def get_info(request, pk):
    device = get_object_or_404(Device, pk=pk)
    return render(request, "devices/get_info.html", locals())
        return render(request, "devices/choose-list.html", context)
