# coding: utf-8

from itertools import chain, groupby

from django.core import mail
from django.db.models import Count, F, Q
from django.utils.translation import gettext_lazy as _
from rest_framework.response import Response
from rest_framework.status import HTTP_201_CREATED

from procu.api import models
from procu.api.enums import QS, RISK, ES
from procu.api.push.external import (
    notify_enquiry_published,
    notify_quote_updated,
)
from procu.api.utils import dict_diff, is_readonly, json_dumps, strtobool
from procu.rest import generics
from procu.rest.permissions import StaffOnly
from . import permissions, serializers
from .notify import quote_notify

from rest_framework.exceptions import ValidationError


class QuoteGroupedList(generics.ListAPIView):
    permission_classes = (permissions.ListCreate,)

    def get_queryset(self):
        user = self.request.user
        sort_field = '-updated_at' if user.sort_quotes_by_updated else 'pk'

        queryset = (
            models.Quote.objects.permitted(self.request.user)
            .filter(request__enquiry_id=self.kwargs['enquiry_id'])
            .order_by('status', sort_field)  # sorting by status is important
        )

        if is_readonly():
            queryset = queryset.extra(select={'can_delete': 'false'})
        else:
            queryset = queryset.extra(
                select={'can_delete': '"api_quote"."status" = %s'},
                select_params=(QS.DRAFT,),
            )

        queryset = queryset.values(
            'id', 'supplier', 'status', 'has_offer', 'has_won', 'can_delete'
        )

        return queryset

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

        queryset = self.get_queryset()

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

        supplier_ids = (q['supplier'] for q in queryset)

        suppliers = models.Supplier.objects.values(
            'id',
            'title',
            'risk',
            'is_cold',
            'can_pay_by_card',
            has_warnings=Count(
                F('warnings'), filter=Q(warnings__is_deleted=False)
            ),
        ).filter(id__in=supplier_ids)

        suppliers = {s['id']: s for s in suppliers}

        for supplier in suppliers.values():

            flags = supplier['flags'] = []

            if supplier['risk'] == RISK.HIGH:
                flags.append('high_risk')

            if supplier['has_warnings']:
                flags.append('warnings')

            if supplier['is_cold']:
                flags.append('cold')

            if supplier['can_pay_by_card']:
                flags.append('card')

            for field in ('risk', 'has_warnings', 'is_cold', 'can_pay_by_card'):
                supplier.pop(field, None)

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

        quotes = list(queryset)
        count = len(quotes)

        for quote in quotes:
            quote['supplier'] = suppliers[quote['supplier']]

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

        quotes = {
            status: list(group)
            for status, group in groupby(quotes, lambda x: x.pop('status'))
        }

        grouped = [
            {
                'name': name,
                'key': QS.keys[status],
                'quotes': quotes.get(status, []),
            }
            for status, name in QS.i18n.items()
        ]

        return Response({'results': grouped, 'count': count})


class QuoteCreate(generics.CreateAPIView):
    permission_classes = (permissions.ListCreate,)
    serializer_class = serializers.Create

    lookup_field = 'enquiry_id'
    lookup_url_kwarg = 'enquiry_id'

    def get_queryset(self):
        return models.Request.objects.permitted(self.request.user).filter(
            status__lt=ES.CLOSED
        )

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

        rfx = self.object

        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data

        quote_status = ES.DRAFT if (rfx.status == ES.DRAFT) else ES.BIDDING

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

        # Create the quote only if there is no other quote for the same
        # supplier within the enquiry.

        existing = rfx.quotes.filter(
            supplier__in=data['suppliers']
        ).values_list('supplier__title', flat=True)

        if existing:
            msg = _('QUOTES::ERROR_SUPPLIERS_ALREADY_ADDED{existing}')
            raise ValidationError(
                {'suppliers': [msg.format(existing=', '.join(existing))]}
            )

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

        instances = []

        for supplier in data['suppliers']:
            instances.append(
                models.Quote(
                    request_id=rfx.id,
                    supplier_id=supplier.id,
                    terms=supplier.terms,
                    deadline_at=data['deadline_at'],
                    status=quote_status,
                )
            )

        instances = models.Quote.objects.bulk_create(instances)

        # ----------------------------------------------------------------------
        # Timeline

        title_map = {s.id: s.title for s in data['suppliers']}

        new = [
            {
                'id': q.id,
                'supplier': {
                    'id': q.supplier_id,
                    'title': title_map[q.supplier_id],
                },
            }
            for q in instances
        ]

        models.Log.objects.create(
            enquiry_id=kwargs['enquiry_id'],
            user=request.user,
            type='create_quotes',
            new=json_dumps(new),
        )

        # ----------------------------------------------------------------------
        # Notifications

        messages = chain.from_iterable(
            notify_enquiry_published(obj.id, request.user)
            for obj in instances
            if obj.status > QS.DRAFT
        )

        with mail.get_connection(fail_silently=True) as conn:
            conn.send_messages(messages)

        return Response({'results': new}, status=HTTP_201_CREATED)


class QuoteRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.RetrieveUpdateDestroy,)
    lookup_url_kwarg = 'quote_id'

    def get_serializer_class(self):

        if self.request.method == 'PATCH':
            return serializers.Update
        else:
            return serializers.Retrieve

    def get_queryset(self):
        return (
            models.Quote.objects.permitted(self.request.user)
            .filter(request__enquiry_id=self.kwargs['enquiry_id'])
            .select_related('supplier', 'request')
        )

    def perform_update(self, serializer):

        old = serializers.Log(serializer.instance).data
        quote = serializer.save()
        quote.refresh_from_db()

        new = serializers.Log(quote).data

        diff = dict_diff(old, new, show_unchanged=False)

        if not diff:
            return

        if 'deadline_at' in diff:
            messages = notify_quote_updated(quote.id, self.request.user, diff)

            with mail.get_connection(fail_silently=True) as conn:
                conn.send_messages(messages)

        models.Log.objects.create(
            quote=quote,
            enquiry_id=self.kwargs['enquiry_id'],
            user=self.request.user,
            type='update_quote',
            data=json_dumps(diff),
        )

    def perform_destroy(self, instance):
        old = self.get_serializer(instance).data

        super().perform_destroy(instance)

        models.Log.objects.create(
            enquiry_id=instance.enquiry_id,
            user=self.request.user,
            type='remove_quote',
            old=json_dumps(old),
        )


class QuoteStatusUpdate(generics.UpdateAPIView):
    permission_classes = (StaffOnly, permissions.RetrieveUpdateDestroy)
    lookup_url_kwarg = 'quote_id'
    serializer_class = serializers.UpdateStatus
    output_serializer_class = serializers.Retrieve

    def get_queryset(self):
        return models.Quote.objects.permitted(self.request.user).filter(
            request__enquiry_id=self.kwargs['enquiry_id']
        )

    def update(self, request, *args, **kwargs):
        quote = self.get_object()
        serializer = self.get_serializer(quote, data=request.data)
        serializer.is_valid(raise_exception=True)

        dry_run = strtobool(request.GET.get('dry_run'))
        data = serializer.validated_data

        if dry_run:

            if data['status'] == QS.SHIPPED:
                msgs = [
                    _('STATUS_UPDATE::CONFIRMATION{status}').format(
                        status=QS.i18n[data['status']]
                    ),
                    _('STATUS_UPDATE::SHIPPED_MSG'),
                ]

            else:
                msgs = [
                    _('STATUS_UPDATE::CONFIRMATION{status}').format(
                        status=QS.i18n[data['status']]
                    ),
                    _('STATUS_UPDATE::MSG'),
                ]

            return Response({'results': msgs})

        self.perform_update(serializer)

        if getattr(quote, '_prefetched_objects_cache', None):
            quote = generics.get_object_or_404(self.get_queryset(), id=quote.id)

        serializer = self.get_output_serializer(quote)

        return Response(serializer.data)

    def perform_update(self, serializer):

        old = serializers.Log(serializer.instance).data
        old_status = serializer.instance.status

        quote = serializer.save()

        new = serializers.Log(quote).data
        new_status = quote.status

        messages = quote_notify(
            self.request.user, quote.id, (old_status, new_status), old, new
        )
        with mail.get_connection(fail_silently=True) as conn:
            conn.send_messages(messages)

        diff = dict_diff(old, new, show_unchanged=False)

        if diff:
            models.Log.objects.create(
                quote=quote,
                enquiry_id=self.kwargs['enquiry_id'],
                user=self.request.user,
                type='update_quote',
                data=json_dumps(diff),
                old=json_dumps(old),
                new=json_dumps(new),
            )
