import calendar
from datetime import timedelta

import requests
from django.conf import settings
from django.db.models import F, Count, Sum
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics, permissions, mixins, status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound
from rest_framework.filters import SearchFilter
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response

from external_api.cdek import CDEKClient
from external_api.poizon import PoizonClient
from utils.exceptions import CRMException
from store.filters import GiftFilter, ChecklistFilter
from store.models import Checklist, GlobalSettings, Category, PaymentMethod, Promocode, Gift
from store.serializers import (ChecklistSerializer, CategorySerializer, CategoryFullSerializer,
                               PaymentMethodSerializer, AnonymousGlobalSettingsSerializer, GlobalSettingsSerializer,
                               PromocodeSerializer, ClientUpdateChecklistSerializer, GiftSerializer,
                               ClientCreateChecklistSerializer)
from account.permissions import ReadOnly, IsManager, IsAdmin


def prepare_external_response(r: requests.Response):
    data = {"status_code": r.status_code, "response": None}

    try:
        data["response"] = r.json()
    except:
        data["response"] = r.text

    return Response(data)



"""
- managers can create/edit/delete orders

- auth users can create/edit orders (managers and clients)
- client will have customer field auto-populated
- managers can set customer field manually

- clients can edit orders with customer.id == self.id

- anon users can only get order by id, but can't edit
"""


class ChecklistAPI(viewsets.ModelViewSet):
    serializer_class = ChecklistSerializer
    lookup_field = 'id'
    filterset_class = ChecklistFilter
    filter_backends = [DjangoFilterBackend, SearchFilter]
    search_fields = ['id', 'poizon_tracking', 'buyer_phone']
    # TODO: search by full_price

    def permission_denied(self, request, **kwargs):
        if request.user.is_authenticated and self.action in ["update", "partial_update", "list", "retrieve"]:
            raise NotFound()
        super().permission_denied(request, **kwargs)

    def get_serializer_class(self):
        # Managers have a full set of fields
        if getattr(self.request.user, 'is_manager', False) or self.action == 'retrieve':
            return ChecklistSerializer

        # Clients can create drafts with small set of fields
        if self.action == "create":
            return ClientCreateChecklistSerializer

        # Then, clients can update small set of fields and cancel orders
        elif self.action in ['update', 'partial_update', 'destroy']:
            return ClientUpdateChecklistSerializer

        # Fallback to error
        self.permission_denied(self.request, **self.kwargs)

    def get_permissions(self):
        if self.action in ['list', 'update', 'partial_update']:
            self.permission_classes = [IsManager]
        elif self.action == 'retrieve':
            self.permission_classes = [AllowAny]
        elif self.action in ['create', 'destroy']:
            self.permission_classes = [IsAuthenticated]

        return super().get_permissions()

    def perform_destroy(self, obj: Checklist):
        # Non-managers can cancel orders only in certain statuses
        if (not getattr(self.request.user, 'is_manager', False)
                and obj.status not in Checklist.Status.CANCELLABLE_ORDER_STATUSES):
            raise CRMException("Can't delete the order")

        obj.cancel()

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

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

        # N time maximum in 'neworder' status -> move to drafts
        if obj.status == Checklist.Status.NEW and obj.buy_time_remaining is not None:
            if obj.buy_time_remaining <= timedelta():
                obj.status = Checklist.Status.DRAFT
                obj.save()

        return obj


class CategoryAPI(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
    serializer_class = CategorySerializer
    permission_classes = [IsManager | ReadOnly]
    lookup_field = 'id'
    queryset = Category.objects.all()

    def list(self, request, *args, **kwargs):
        categories_qs = Category.objects.root_nodes()
        return Response(CategoryFullSerializer(categories_qs, many=True).data)


class GlobalSettingsAPI(generics.RetrieveUpdateAPIView):
    serializer_class = GlobalSettingsSerializer
    permission_classes = [IsManager | ReadOnly]

    def get_serializer_class(self):
        if getattr(self.request.user, 'is_manager', False):
            return GlobalSettingsSerializer

        # Anonymous users can view only a certain set of fields
        return AnonymousGlobalSettingsSerializer

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


class PaymentMethodsAPI(mixins.ListModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
    serializer_class = PaymentMethodSerializer
    permission_classes = [IsManager | ReadOnly]
    lookup_field = 'slug'
    queryset = PaymentMethod.objects.all()


class PromoCodeAPI(viewsets.ModelViewSet):
    serializer_class = PromocodeSerializer
    permission_classes = [IsManager]
    lookup_field = 'name'

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

    def perform_destroy(self, instance):
        instance.is_active = False
        instance.save()
        return Response(status=status.HTTP_204_NO_CONTENT)


class GiftAPI(viewsets.ModelViewSet):
    serializer_class = GiftSerializer
    permission_classes = [IsManager | ReadOnly]
    filterset_class = GiftFilter

    def get_queryset(self):
        if getattr(self.request.user, 'is_manager', False):
            return Gift.objects.all()

        # For anonymous users, show only available gifts
        return Gift.objects.filter(available_count__gt=0)


class StatisticsAPI(viewsets.GenericViewSet):
    permission_classes = [IsAdmin]

    def get_queryset(self):
        return Checklist.objects.all() \
            .select_related('customer') \
            .filter(status=Checklist.Status.COMPLETED) \
            .annotate(month=F('status_updated_at__month'))

    # FIXME: stats_by_orders is broken because of _commission_rub annotation
    @action(url_path='orders', detail=False, methods=['get'])
    def stats_by_orders(self, request, *args, **kwargs):
        global_settings = GlobalSettings.load()
        yuan_rate = global_settings.full_yuan_rate

        # Prepare query to collect the stats
        qs = self.get_queryset() \
            .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)

    # FIXME: stats_by_categories is broken because of absence of Category.slug field
    @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: [] for k in options.keys()}

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

        # mapping customer_id -> customer info
        customer_mapping = {}

        for order in Checklist.objects.all().select_related('customer'):
            customer_mapping[order.customer_id] = {
                'phone': str(order.customer.phone),
                'name': order.customer.first_name,
                'telegram': order.customer.telegram
            }

        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 order in qs:
            month_name = calendar.month_name[order['month']]

            # Collect data for each order count: 1/2/3/4/5/10/25/50
            for key, size in reversed(options.items()):
                if order['order_count'] > size:
                    client_info = customer_mapping[order['customer_id']]
                    client_info["order_count"] = order['order_count']
                    result[month_name][key].append(client_info)
                    break

        return Response(result)


# TODO: review permissions
class CDEKAPI(viewsets.GenericViewSet):
    client = CDEKClient(settings.CDEK_CLIENT_ID, settings.CDEK_CLIENT_SECRET)
    permission_classes = [permissions.AllowAny]

    @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 prepare_external_response(r)

    @get_order_info.mapping.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 prepare_external_response(r)

    @get_order_info.mapping.patch
    def edit_order(self, request, *args, **kwargs):
        order_data = request.data
        if not order_data:
            raise CRMException('json data is required')

        r = self.client.edit_order(order_data)
        return prepare_external_response(r)

    @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 prepare_external_response(r)

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


# TODO: review permissions
class PoizonAPI(viewsets.GenericViewSet):
    client = PoizonClient(settings.POIZON_TOKEN)
    permission_classes = [permissions.AllowAny]

    @action(url_path='good', detail=False, methods=['post'])
    def get_good_info(self, request, *args, **kwargs):
        spu_id = None
        if 'url' in request.data:
            spu_id = self.client.get_spu_id(request.data.get('url'))
        if 'spuId' in request.data:
            spu_id = request.data.get('spuId')

        if spu_id is None:
            raise CRMException('url or spuId is required')

        r = self.client.get_good_info(spu_id)
        return prepare_external_response(r)
