import calendar
import json

from django.conf import settings
from django.contrib.auth import login
from django.db.models import F, Count, Q, Sum, Value, Subquery
from django.utils import timezone
from rest_framework import generics, permissions, mixins, status, viewsets
from rest_framework.decorators import action
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from cdek.api import CDEKClient
from store.exceptions import CRMException
from store.models import User, Checklist, GlobalSettings, Category, PaymentMethod, Promocode
from store.serializers import (UserSerializer, LoginSerializer, ChecklistSerializer, GlobalSettingsYuanRateSerializer,
                               CategorySerializer, GlobalSettingsPriceSerializer, PaymentMethodSerializer,
                               PromocodeSerializer, GlobalSettingsPickupSerializer)
from utils.permissions import ReadOnly


class UserAPI(mixins.ListModelMixin, mixins.RetrieveModelMixin, generics.GenericAPIView):
    serializer_class = UserSerializer

    def get_queryset(self):
        return User.objects.all()

    def get(self, request, *args, **kwargs):
        if 'pk' in kwargs:
            return self.retrieve(request, *args, **kwargs)
        return self.list(request, *args, **kwargs)

    # Update some data on current user
    def patch(self, request, *args, **kwargs):
        data = json.loads(request.body)
        instance = self.request.user
        serializer = self.get_serializer(instance, data=data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response(serializer.data)


class LoginAPI(generics.GenericAPIView):
    serializer_class = LoginSerializer
    permission_classes = (permissions.AllowAny,)

    def post(self, request, *args, **kwargs):
        data = json.loads(request.body)

        serializer = self.get_serializer(data=data)
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        login(request, user)
        return Response(UserSerializer(user).data)


class ChecklistAPI(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.RetrieveModelMixin, generics.GenericAPIView):
    serializer_class = ChecklistSerializer
    permission_classes = [IsAuthenticated | ReadOnly]
    lookup_field = 'id'
    filterset_fields = ['status', ]
    search_fields = ['id', 'track_id', 'buyer_phone', 'full_price']

    def get_queryset(self):
        return Checklist.objects.all().with_base_related() \
            .annotate_price_rub().annotate_commission_rub() \
            .default_ordering()

    def get_object(self):
        obj: Checklist = super().get_object()

        # 3 hours maximum in 'neworder' status -> move to drafts
        if obj.status == Checklist.Status.NEW:
            diff_hours = (timezone.now() - obj.status_updated_at).seconds / 3600
            if diff_hours > 3:
                obj.status = Checklist.Status.DRAFT
                obj.save()

        return obj

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        if 'id' in kwargs:
            return self.retrieve(request, *args, **kwargs)
        return self.list(request, *args, **kwargs)

    # Update some data on current user
    def patch(self, request, *args, **kwargs):
        data = json.loads(request.body)
        instance = get_object_or_404(self.get_queryset(), id=self.kwargs['id'])
        serializer = self.get_serializer(instance, data=data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response(serializer.data)


class YuanRateAPI(generics.GenericAPIView):
    serializer_class = GlobalSettingsYuanRateSerializer

    def get_object(self):
        return GlobalSettings.load()

    def get(self, request, *args, **kwargs):
        yuan_rate = GlobalSettings.load().yuan_rate
        return Response(data={'currency': yuan_rate})

    def patch(self, request, *args, **kwargs):
        data = json.loads(request.body)
        instance = self.get_object()

        serializer = self.get_serializer(instance, data=data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response(serializer.data)


class CategoryAPI(generics.GenericAPIView):
    serializer_class = CategorySerializer

    def get_queryset(self):
        return Category.objects.all()

    def get(self, request, *args, **kwargs):
        categories_qs = self.get_queryset()
        global_settings = GlobalSettings.load()

        return Response({
            'categories': CategorySerializer(categories_qs, many=True).data,
            'prices': GlobalSettingsPriceSerializer(global_settings).data,
        })

    def patch(self, request, *args, **kwargs):
        data = json.loads(request.body)
        if not all(k in data for k in ("category", "chinarush")):
            raise CRMException('category and chinarush is required')

        instance = get_object_or_404(self.get_queryset(), slug=data['category'])
        serializer = self.get_serializer(instance, data=data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response(serializer.data)


class PricesAPI(generics.GenericAPIView):
    serializer_class = GlobalSettingsPriceSerializer

    def patch(self, request, *args, **kwargs):
        data = json.loads(request.body)

        instance = GlobalSettings.load()
        serializer = self.get_serializer(instance, data=data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response(serializer.data)


class PickupAPI(generics.GenericAPIView):
    serializer_class = GlobalSettingsPickupSerializer
    permission_classes = [IsAuthenticated | ReadOnly]

    def get_object(self):
        return GlobalSettings.load()

    def get(self, request, *args, **kwargs):
        instance = self.get_object()
        return Response(self.get_serializer(instance).data)

    def patch(self, request, *args, **kwargs):
        data = json.loads(request.body)

        instance = GlobalSettings.load()
        serializer = self.get_serializer(instance, data=data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response(serializer.data)


class PaymentMethodsAPI(generics.GenericAPIView):
    serializer_class = PaymentMethodSerializer

    def get_queryset(self):
        return PaymentMethod.objects.all()

    def get(self, request, *args, **kwargs):
        qs = self.get_queryset()
        data = {}
        for obj in qs:
            data[obj.slug] = self.get_serializer(obj).data

        return Response(data)

    def patch(self, request, *args, **kwargs):
        data = json.loads(request.body)
        if 'type' not in data:
            raise CRMException('type is required')

        instance = get_object_or_404(self.get_queryset(), slug=data['type'])
        serializer = self.get_serializer(instance, data=data, partial=True)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response(serializer.data)


class PromoCodeAPI(mixins.CreateModelMixin, generics.GenericAPIView):
    serializer_class = PromocodeSerializer
    lookup_field = 'name'

    def get_queryset(self):
        return Promocode.objects.all()

    def get(self, request, *args, **kwargs):
        qs = self.get_queryset()
        return Response(
            {'promo': self.get_serializer(qs, many=True).data}
        )

    def post(self, request, *args, **kwargs):
        self.create(request, *args, **kwargs)
        return self.get(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        data = json.loads(request.body)
        if 'name' not in data:
            raise CRMException('name is required')

        instance: Promocode = get_object_or_404(self.get_queryset(), name=data['name'])
        instance.is_active = False
        instance.save()
        return Response(status=status.HTTP_204_NO_CONTENT)


class StatisticsAPI(viewsets.GenericViewSet):
    def get_queryset(self):
        return Checklist.objects.all() \
            .filter(status=Checklist.Status.COMPLETED) \
            .annotate(month=F('status_updated_at__month'))

    @action(url_path='orders', detail=False, methods=['get'])
    def stats_by_orders(self, request, *args, **kwargs):
        global_settings = GlobalSettings.load()
        yuan_rate = global_settings.yuan_rate

        # Prepare query to collect the stats
        qs = self.get_queryset() \
            .annotate_price_rub() \
            .annotate_commission_rub() \
            .values('month') \
            .annotate(total=Count('id'),
                      total_completed=Count('id'),
                      total_bought_yuan=Sum('real_price', default=0),
                      total_bought_rub=F('total_bought_yuan') * yuan_rate,
                      total_commission_rub=Sum('_commission_rub', default=0)
                      )

        def _create_stats(data: dict):
            return {
                "CountOrders": data.get('total', 0),
                "CountComplete": data.get('total_completed', 0),
                "SumCommission": data.get('total_commission_rub', 0),
                "SumOrders1": data.get('total_bought_yuan', 0),
                "SumOrders2": data.get('total_bought_rub', 0),
            }

        result = {}
        # Add empty stats
        for i in range(1, 13):
            month_name = calendar.month_name[i]
            result[month_name] = _create_stats(dict())

        # Add actual stats
        for stat in qs:
            month_name = calendar.month_name[stat['month']]
            result[month_name] = _create_stats(stat)

        return Response(result)

    @action(url_path='categories', detail=False, methods=['get'])
    def stats_by_categories(self, request, *args, **kwargs):
        all_categories = Category.objects.values_list('slug', flat=True)
        categories_dict = {c: 0 for c in all_categories}

        qs = self.get_queryset() \
            .select_related('category') \
            .values('month', 'category__slug') \
            .annotate(total_orders=Count('id'))

        result = {}
        # Add empty stats
        for i in range(1, 13):
            month = calendar.month_name[i]
            result[month] = categories_dict.copy()

        # Add actual stats
        for stat in qs:
            month = calendar.month_name[stat['month']]
            category_slug = stat['category__slug']
            total_orders = stat['total_orders']

            result[month][category_slug] = total_orders

        return Response(result)

    @action(url_path='clients', detail=False, methods=['get'])
    def stats_by_clients(self, request, *args, **kwargs):
        options = {
            "moreone": 1,
            "moretwo": 2,
            "morethree": 3,
            "morefour": 4,
            "morefive": 5,
            "moreten": 10,
            "moretwentyfive": 25,
            "morefifty": 50,
        }

        def _create_empty_stats():
            return {k: set() for k in options.keys()}

        qs = self.get_queryset() \
            .filter(buyer_phone__isnull=False) \
            .values('month', 'buyer_phone') \
            .annotate(order_count=Count('id')) \
            .filter(order_count__gt=1) \
            .order_by('month')

        result = {}
        # Add empty stats
        for i in range(1, 13):
            month_name = calendar.month_name[i]
            result[month_name] = _create_empty_stats()

        # Add actual stats
        for stat in qs:
            month_name = calendar.month_name[stat['month']]

            for key, size in reversed(options.items()):
                if stat['order_count'] > size:
                    result[month_name][key].add(stat['buyer_phone'])
                    break

        return Response(result)


class CDEKAPI(viewsets.GenericViewSet):
    client = CDEKClient(settings.CDEK_CLIENT_ID, settings.CDEK_CLIENT_SECRET)

    @action(url_path='orders', detail=False, methods=['get'])
    def get_order_info(self, request, *args, **kwargs):
        im_number = request.query_params.get('im_number')
        if not im_number:
            raise CRMException('im_number is required')

        r = self.client.get_order_info(im_number)
        return Response(r.json())

    @action(url_path='orders', detail=False, methods=['post'])
    def create_order(self, request, *args, **kwargs):
        order_data = request.data
        if not order_data:
            raise CRMException('json data is required')

        r = self.client.create_order(order_data)
        return Response(r.json())

    @action(url_path='calculator/tariff', detail=False, methods=['post'])
    def calculate_tariff(self, request, *args, **kwargs):
        data = request.data
        if not data:
            raise CRMException('json data is required')
        r = self.client.calculate_tariff(data)
        return Response(r.json())
