# coding: utf-8

import mimetypes
from collections import OrderedDict, defaultdict

from django.conf import settings
from django.core.files import File
from django.db.models import Count, F, Q
from django.http import HttpResponse
from django.utils.translation import gettext_lazy as _
from rest_framework import response

from procu.api import models
from procu.api.currency.serializers import CurrencyBriefSerializer
from procu.api.utils import format_price, is_readonly
from procu.rest import generics
from .permissions import EnquirySummaryPermission
from .serializers import QueryParamsSerializer, SupplierSerializer
from .utils import ExcelController

mimetypes.init()


class EnquirySummaryView(generics.GenericAPIView):
    enquiry = None
    quotes = None

    permission_classes = (EnquirySummaryPermission,)
    query_params_serializer_class = QueryParamsSerializer

    def post_initial(self, request):
        enquiry = self.enquiry = generics.get_object_or_404(
            models.Enquiry.objects.permitted(self.request.user),
            pk=self.kwargs['enquiry_id'],
        )
        self.check_object_permissions(request, enquiry)

    def get(self, request, *args, **kwargs):

        rfx = generics.get_object_or_404(
            models.Request.objects.all(), enquiry_id=self.kwargs['enquiry_id']
        )

        params = self.query_params

        ep_qs = models.EnquiryProduct.objects.filter(request=rfx)
        eps = {ep.id: ep for ep in ep_qs}

        snapshots_qs = models.QuoteProductSnapshot.objects.filter(
            enquiry=self.enquiry
        ).order_by('pk')
        snapshots = {s.id: i for i, s in enumerate(snapshots_qs, 1)}

        products = (
            models.QuoteProduct.for_summary_only.prepared(**params)
            .filter(quote__request=rfx, quote__has_offer=True)
            .order_by(
                # Sorting is important for correct representation!
                'enquiry_product_id',
                'is_replacement',
                F('parent').desc(nulls_first=True),
                F('snapshot_id').desc(nulls_first=True),
                'quote',
                'id',
            )
            .select_related('currency', 'quote', 'enquiry_product', 'snapshot')
        )

        if not params['replacements']:
            products = products.filter(is_replacement=False)

        rows_objects = defaultdict(list)

        for ep_id in sorted(eps):
            dummy = rows_objects[(ep_id, False, None, None)]

        for p in products:
            key = (
                p.enquiry_product_id,
                p.is_replacement,
                p.parent_id if p.is_replacement else None,
                p.snapshot_id,
            )
            rows_objects[key].append(p)

        # Sort rows by enquiry_product_id.
        # IMPORTANT: replacement and snapshots remain at the right places
        # because sorting is stable.
        rows_objects = dict(
            sorted(rows_objects.items(), key=lambda x: (x[0][0], x[0][2] or -1))
        )

        # ----------------------------------------------------------------------
        # Precompute lowest prices

        lowest_prices = defaultdict(list)

        for p in products:
            if p.snapshot_id or p.value is None:
                continue

            lowest_prices[p.enquiry_product_id].append(p.value)

        lowest_prices = {
            ep_id: min(prices) for ep_id, prices in lowest_prices.items()
        }
        # ----------------------------------------------------------------------

        rows = []

        for key, objs in rows_objects.items():

            name = None
            snapshot = None

            offers = {}

            for obj in objs:

                if obj.is_replacement:
                    name = obj.name

                if obj.snapshot:
                    snapshot = obj.snapshot

                offers[obj.quote.supplier_id] = {
                    'id': obj.id,
                    'enquiry_product': obj.enquiry_product_id,
                    'supplier': obj.quote.supplier_id,
                    'name': obj.name,
                    'qty': obj.qty,
                    'qty_expected': obj.enquiry_product.qty,
                    'value': obj.formatted_value,
                    'value_': obj.value,
                    'total': obj.total,
                    'approx': obj.approx,
                    'best': (
                        lowest_prices.get(obj.enquiry_product_id) == obj.value
                    ),
                    'tax': obj.tax,
                    'is_tax_included': obj.is_tax_included,
                    'is_per_unit': obj.is_per_unit,
                    'delivery_time': obj.delivery_time,
                    'comment': obj.comment,
                    'is_replacement': obj.is_replacement,
                    'is_snapshot': bool(obj.snapshot_id),
                    'sign': None,
                }

            ep = eps[key[0]]

            snapshot_no = None
            if snapshot:
                snapshot_no = _('SNAPSHOTS::NAME{no}').format(
                    no=snapshots[snapshot.id]
                )

            is_replacement = (
                all(qp.is_replacement for qp in objs) if objs else False
            )
            is_snapshot = (
                all(bool(qp.snapshot) for qp in objs) if objs else False
            )

            rows.append(
                {
                    'id': ep.id,
                    'enquiry_product': ep.id,
                    'name': snapshot_no or (name or ep.name),
                    'comment': ep.comment,
                    'qty': None if (is_replacement or is_snapshot) else ep.qty,
                    'is_replacement': is_replacement,
                    'is_snapshot': is_snapshot,
                    'snapshot_date': snapshot.created_at if snapshot else None,
                    'offers': offers,
                }
            )

        # ----------------------------------------------------------------------

        if params['compare']:

            base = defaultdict(lambda: None)

            for row in reversed(rows):
                if row['is_snapshot']:
                    for supplier_id, offer in row['offers'].items():

                        if offer['value_'] is None:
                            continue

                        if base[supplier_id] is None:
                            base[supplier_id] = offer['value_']
                        else:
                            diff = offer['value_'] - base[supplier_id]

                            offer['approx'] = False
                            offer['sign'] = (diff > 0) - (diff < 0)
                            offer['value'] = format_price(abs(diff))
                else:
                    base.clear()

        # ----------------------------------------------------------------------

        currency = CurrencyBriefSerializer(self.query_params['currency'])

        quotes = (
            models.Quote.objects.select_related('supplier')
            .annotate(
                has_warnings=Count(
                    F('supplier__warnings'),
                    filter=Q(supplier__warnings__is_deleted=False),
                )
            )
            .filter(has_offer=True, request__enquiry=self.enquiry)
            .order_by('pk')
        )

        suppliers = SupplierSerializer(quotes, many=True).data

        for supplier in suppliers:
            supplier['effective_coef'] = None

            terms = supplier['terms']

            # ----------------------------------------------------------

            pre_percent, pre = terms.get('pre', (0, 0))
            post_percent, post = terms.get('post', (0, 0))

            if (pre_percent + post_percent) != 100:
                continue

            try:
                (discount,) = models.Discount.objects.values_list(
                    'value', flat=True
                ).filter(dates__contains=self.enquiry.created_at)

            except ValueError:
                # no discounts objects for the given date
                continue

            discount = float(discount) / 100.

            pre_percent /= 100.
            post_percent /= 100.

            gain = (1 + discount) ** ((pre + post) / 365.) - 1
            loss = (1 + discount) ** (pre / 365.) - 1

            supplier['effective_coef'] = (
                1 - post_percent * gain + pre_percent * loss
            )

        can_write = not is_readonly()

        can_checkout = (
            can_write
            and 'api.checkout_enquiry'
            in self.enquiry.permissions(self.request.user)
        )

        can_make_invoice = (
            can_write and not settings.IS_PRODUCTION and can_checkout
        )

        data = OrderedDict(
            [
                ('currency', currency.data),
                ('suppliers', suppliers),
                ('products', rows),
                ('summary', rfx.summary),
                ('can_checkout', can_checkout),
                ('can_make_invoice', can_make_invoice),
            ]
        )

        if self.query_params['response_type'] == 'xlsx':

            excel = ExcelController(self.query_params, suppliers, rows)
            excel.add_enquiry_summary()

            for supplier in suppliers:
                excel.add_quote_summary(supplier)

            excel.workbook.close()

            # noinspection PyUnresolvedReferences
            file_response = HttpResponse(
                File(excel.output), content_type=mimetypes.types_map['.xlsx']
            )

            filename = 'summary_{}.xlsx'.format(rfx.key)

            file_response['Content-Disposition'] = (
                'attachment; filename="%s' % filename
            )
            return file_response

        else:
            return response.Response(data)
