import warnings
from http import HTTPStatus

from django.db.models import Q
from django.http import HttpResponse
from django.http import JsonResponse
from rest_framework.decorators import action
from rest_framework.decorators import api_view
from rest_framework.decorators import permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ReadOnlyModelViewSet

from postamates.settings import AGE_DAY_BORDER
from postamates.settings import EXCEL_EXPORT_FILENAME
from postamates.settings import JSON_EXPORT_FILENAME, STATUS_TASK_NAME
from service import models
from service import pagination
from service import serializers
from service import utils
from service.enums import PointStatus, MatchingStatus
from service.permissions import UserPermission
from service.service import PointService
from service.tasks import raschet, load_post_and_pvz, load_other_objects, load_data
from rest_framework.permissions import AllowAny
from django.shortcuts import redirect
from django.contrib import messages
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from service.utils import CustomReadOnlyModelViewSet
from django.db.models import Min, Max
import os
from django.forms.models import model_to_dict


class AOViewSet(CustomReadOnlyModelViewSet):
    serializer_class = serializers.AOSerializer
    queryset = models.AO.objects
    permission_classes = [AllowAny]


class PostAndPVZCategoryViewSet(CustomReadOnlyModelViewSet):
    serializer_class = serializers.PostAndPVZCategorySerializer
    queryset = models.Post_and_pvzCategory.objects


class OtherObjectsCategoryViewSet(CustomReadOnlyModelViewSet):
    serializer_class = serializers.OtherObjectsCategorySerializer
    queryset = models.OtherObjectsCategory.objects


class PlacementPointViewSet(ReadOnlyModelViewSet):
    serializer_class = serializers.PlacementPointSerializer
    queryset = models.PlacementPoint.objects
    pagination_class = pagination.MyPagination
    permission_classes = [UserPermission]
    filter_backends = [DjangoFilterBackend, filters.OrderingFilter, filters.SearchFilter]

    @property
    def filterset_fields(self):
        model_cls = self.queryset.model
        fieldset = {
            field.name: [
                "exact",
                "gt",
                "gte",
                "lt",
                "lte",
                "in",
                "iexact",
                "startswith",
                "istartswith",
                "endswith",
                "iendswith",
                "regex",
                "iregex",
                "isnull",
                "contains",
                "icontains",
            ]
            for field in [
                field
                for field in model_cls._meta.get_fields()
                if field.get_internal_type()
                   not in [
                       "JSONField",
                       "ForeignKey",
                       "ManyToManyField",
                       "OneToOneField",
                       "PointField"
                   ]
            ]
        }

        # filters for model relations
        for field in [
            field
            for field in model_cls._meta.get_fields()
            if field.get_internal_type()
               in [
                   "ForeignKey",
                   "ManyToManyField",
                   "OneToOneField",
               ]
        ]:
            fieldset[field.name] = [
                "exact",
                "gt",
                "gte",
                "lt",
                "lte",
            ]

        return fieldset

    def get_queryset(self):
        qs = self.queryset.all().order_by('id')
        location_ids = self.request.GET.get('location_ids[]')
        prediction_first = self.request.GET.get('prediction_first[]')
        prediction_current = self.request.GET.get('prediction_current[]')
        age_day = self.request.GET.get('age_day[]')
        categories = self.request.GET.get('categories[]')
        status = self.request.GET.get('status[]')
        delta = self.request.GET.get('delta[]')
        fact = self.request.GET.get('fact[]')
        included = self.request.GET.get('included[]')
        excluded = self.request.GET.get('excluded[]')
        plan_first = self.request.GET.get('plan_first[]')
        plan_current = self.request.GET.get('plan_current[]')
        delta_first = self.request.GET.get('delta_first[]')
        delta_current = self.request.GET.get('delta_current[]')
        rayons = self.request.GET.get('area[]')
        aos = self.request.GET.get('district[]')
        group_dists_lt = self.request.GET.getlist('dist_to_group__lt')
        group_dists_gt = self.request.GET.getlist('dist_to_group__gt')
        if location_ids:
            location_ids = list(location_ids.split(','))
            qs = qs.filter(pk__in=location_ids)
        if prediction_first:
            prediction_first = list(prediction_first.split(','))
            qs = qs.filter(prediction_first__range=prediction_first)
        if prediction_current:
            prediction_current = list(prediction_current.split(','))
            qs = qs.filter(prediction_current__range=prediction_current)
        if categories:
            categories = list(categories.split(','))
            qs = qs.filter(category__in=categories)
        if status:
            status = list(status.split(','))
            qs = qs.filter(status__in=status)
        if delta:
            delta = list(delta.split(','))
            qs = qs.filter(delta__range=delta)
        if fact:
            fact = list(fact.split(','))
            qs = qs.filter(fact__range=fact)
        if plan_first:
            plan_first = list(plan_first.split(','))
            qs = qs.filter(plan_first__range=plan_first)
        if plan_current:
            plan_current = list(plan_current.split(','))
            qs = qs.filter(plan_current__range=plan_current)
        if age_day:
            age_day = list(age_day.split(','))
            qs = qs.filter(age_day__range=age_day)
        if delta_current:
            delta_current = list(delta_current.split(','))
            qs = qs.filter(delta_current__range=delta_current)
        if delta_first:
            delta_first = list(delta_first.split(','))
            qs = qs.filter(delta_first__range=delta_first)
        if rayons:
            rayons = list(rayons.split(','))
            qs = qs.filter(area_id__in=rayons)
        if aos:
            aos = list(aos.split(','))
            qs = qs.filter(district_id__in=aos)
        if excluded:
            excluded = list(excluded.split(','))
            qs = qs.filter(~Q(pk__in=excluded))
        if included:
            inclded = list(included.split(','))
            qs2 = self.queryset.filter(pk__in=inclded).all()
            qs = (qs | qs2).distinct()
        if group_dists_lt:
            g_d = [list(g.split(',')) for g in group_dists_lt]
            for group in g_d:
                filtered_points = list(
                    models.PlacementPointPVZDistance.objects.filter(pvz_postamates_group__id=int(group[0]),
                                                                    dist__lt=int(group[1])).values_list(
                        'placement_point__id', flat=True))
                qs = qs.filter(id__in=filtered_points)
        if group_dists_gt:
            g_d = [list(g.split(',')) for g in group_dists_gt]
            for group in g_d:
                filtered_points = list(
                    models.PlacementPointPVZDistance.objects.filter(pvz_postamates_group__id=int(group[0]),
                                                                    dist__gt=int(group[1])).values_list(
                        'placement_point__id', flat=True))
                qs = qs.filter(id__in=filtered_points)
        return qs

    @action(methods=['get'], detail=False)
    def get_filterset_fields(self, request, *args, **kwargs):
        return Response(self.filterset_fields, status=HTTPStatus.OK)

    @action(detail=False, methods=['get'])
    def filters(self, request):
        def get_filter_data():
            qs = self.get_queryset()
            keys = (
                'age_day', 'prediction_first', 'prediction_current',
                'plan_first', 'plan_current', 'fact', 'delta_first',
                'delta_current', 'flat_cnt', 'year_bld', 'levels',
                'doors', 'flats_cnt', 'popul_home', 'popul_job',
                'other_post_cnt', 'target_post_cnt', 'yndxfood_cnt',
                'yndxfood_sum', 'yndxfood_cnt_cst', 'rival_post_cnt', 'rival_pvz_cnt', 'tc_cnt', 'culture_cnt',
                'mfc_cnt', 'public_stop_cnt'
                , 'supermarket_cnt', 'target_dist', 'metro_dist'
            )
            temp_data = {
                key: [
                    x for x in list(set(qs.values_list(key, flat=True))) if
                    x is not None
                ]
                for key in keys
            }
            data = {
                key: [
                    min(temp_data[key]), max(temp_data[key]),
                ] if temp_data[key] else [0, 100] for key in keys
            }
            data['dist_to_groups'] = [{'group_id': d['pvz_postamates_group'], 'dist': [d['min_dist'], d['max_dist']]}
                                      for d in list(
                    models.PlacementPointPVZDistance.objects.values('pvz_postamates_group').annotate(
                        min_dist=Min('dist'), max_dist=Max('dist')))]

            return data

        data = get_filter_data()

        return Response(data, status=HTTPStatus.OK)

    @action(detail=False, methods=['get'])
    def search_address(self, request):
        address = self.request.GET.get('address')
        qs = self.get_queryset()
        if address:
            qs = qs.filter(address__icontains=address)
        qs = qs.distinct()
        page = self.paginate_queryset(qs)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(qs, many=True)
        return Response(serializer.data)

    @action(detail=False, methods=['put'])
    def update_status(self, request):
        qs = self.get_queryset()
        new_status = self.request.GET.get('status')
        if not new_status or new_status not in [tag.name for tag in PointStatus]:
            return Response({'message': 'No status'}, HTTPStatus.BAD_REQUEST)
        PointService.update_points_in_radius(qs, new_status)
        PointService.update_status(qs, new_status)
        return Response(
            {'message': 'status updated'},
            status=HTTPStatus.OK,
        )

    @action(detail=False, methods=['put'])
    def update_fact(self, request):
        point_id = request.GET.get('postamat_id')
        fact = request.GET.get('fact')
        if not point_id or not fact or not fact.isdigit():
            return Response(status=HTTPStatus.BAD_REQUEST)
        qs = models.PlacementPoint.objects.filter(postamat_id=point_id)
        if not qs:
            return Response(status=HTTPStatus.NOT_FOUND)
        for q in qs:
            if q.age_day < AGE_DAY_BORDER:
                qs.update(**{'fact': fact, 'fact_raw': fact})
            else:
                new_fact = fact // (q.age_day / AGE_DAY_BORDER)
                qs.update(fact=new_fact, fact_raw=fact)
        qs.update(**{'fact_raw': fact})
        return Response({'message': 'fact updated'}, status=HTTPStatus.OK)

    @action(detail=False, methods=['put'])
    def update_postamat_id(self, request):
        postamat_id = request.GET.get('postamat_id')
        point_id = request.GET.get('id')
        if not point_id or not postamat_id:
            return Response(status=HTTPStatus.BAD_REQUEST)
        qs = PointService.get_point_by_id(point_id)
        if not qs:
            return Response(status=HTTPStatus.NOT_FOUND)
        PointService().update_postamat_id(point_id, postamat_id)
        return Response({'message': 'Postamat id updated'}, status=HTTPStatus.OK)

    @action(detail=False, methods=['get'])
    def to_excel(self, request):
        qs = self.get_queryset()
        serializer = self.serializer_class(qs, many=True)
        filename = EXCEL_EXPORT_FILENAME
        res = HttpResponse(
            PointService.to_excel(serializer),
            content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        )
        res['Content-Disposition'] = f'attachment; filename={filename}'
        return res

    @action(detail=False, methods=['get'])
    def to_json(self, request):
        qs = self.get_queryset()
        serializer = self.serializer_class(qs, many=True)
        filename = JSON_EXPORT_FILENAME
        response = HttpResponse(PointService.to_json(serializer), content_type='application/json')
        response['Content-Disposition'] = f'attachment; filename={filename}'
        return response

    @action(detail=False, methods=['get'])
    def get_10k(self, request):
        pred = PointService.get_first_10_k()
        return Response({'prediction_current': pred}, status=HTTPStatus.OK)

    @action(detail=False, methods=['get'])
    def start(self, request):
        raschet.delay()
        return Response('Sucess', status=HTTPStatus.OK)

    @action(detail=False, methods=['get'])
    def last_time_ml_run(self, request):
        status = models.TaskStatus.objects.filter(task_name=STATUS_TASK_NAME).first()
        st = None
        if status:
            st = status.status
        return Response({'last_time': models.LastMLCall.objects.first().dt, 'task_status': st}, status=HTTPStatus.OK)


class PrePlacementPointViewSet(PlacementPointViewSet):
    queryset = models.PrePlacementPoint.objects
    serializer_class = serializers.PrePlacementPointSerializer

    def get_queryset(self):
        qs = super().get_queryset()
        matching_status = self.request.GET.get('matching_status')
        if matching_status:
            qs = qs.filter(matching_status=matching_status)
        return qs

    @action(detail=False, methods=['post'])
    def load_matching_file(self, request):
        file = request.FILES['file'].file
        file_bytes = file.read()
        excel_base64 = base64.b64encode(file_bytes).decode()
        obj = models.TempFiles.objects.create(data=excel_base64)
        return Response(
            {'id': obj.id},
            status=HTTPStatus.OK,
        )

    @action(detail=False, methods=['post'])
    def start_matching(self, request):
        file_id = request.POST['id']
        total, matched, problem = PointService().start_mathing(file_id)
        PointService().make_enrichment()
        raschet('service_preplacementpoint')
        return Response(
            {'message': {'total': total, 'matched': matched, 'error': problem, 'unmatched': total - matched - problem}},
            status=HTTPStatus.OK,
        )

    @action(detail=False, methods=['post'])
    def move_points(self, request):
        qs = self.get_queryset()
        qs = qs.filter(matching_status=MatchingStatus.New.name)
        for q in qs:
            obj = model_to_dict(q)
            obj.pop('matching_status')
            obj.pop('id')
            ao = obj.pop('district')
            rayon = obj.pop('area')
            obj['district']=models.AO.objects.get(id=ao)
            obj['area'] = models.Rayon.objects.get(id=rayon)
            obj['age_day'] = 1
            models.PlacementPoint.objects.create(**obj)
        models.PrePlacementPoint.objects.all().delete()
        return Response(status=HTTPStatus.OK, )

    @action(detail=False, methods=['delete'])
    def delete_points(self, request):
        ids = request.POST['ids'].split(',')
        PointService.delete_preplacement_points(ids)
        return Response(status=HTTPStatus.OK, )

    @action(detail=False, methods=['get'])
    def download_template(self, request):
        image_buffer = open('PrePlacementPoints.xlsx', "rb").read()
        response = HttpResponse(image_buffer, content_type='xlsx')
        response['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename('preplacementpoints.xlsx')
        return response


class refresh_placement_points(APIView):
    @staticmethod
    def post(request):
        warnings.filterwarnings('ignore')
        file = request.FILES['file'].file
        file_bytes = file.read()
        csv_base64 = base64.b64encode(file_bytes).decode()
        obj = models.TempFiles.objects.create(data=csv_base64)
        load_data.delay(obj.id)
        messages.success(request, 'Файл точек успешно загружен')
        return redirect('/admin')


class load_ao_and_rayons(APIView):
    @staticmethod
    def post(request):
        warnings.filterwarnings('ignore')
        file_ao = request.FILES['file_ao']
        file_rayon = request.FILES['file_rayon']
        utils.load_ao_and_rayons(file_ao, file_rayon)
        messages.success(request, 'Файл АО и Районов успешно загружен')
        return redirect('/admin')


import base64


@api_view(['POST'])
def upload_post_and_pvz(request):
    warnings.filterwarnings('ignore')
    file_rivals = request.FILES['file_post_and_pvz'].file
    file_bytes = file_rivals.read()
    excel_base64 = base64.b64encode(file_bytes).decode()
    obj = models.TempFiles.objects.create(data=excel_base64)
    load_post_and_pvz.delay(obj.id)
    messages.success(request, 'Загрузка ПВЗ и Постаматов началась. Отслеживайте выполнение в Статусе фоновых задач')
    return redirect('/admin')


@api_view(['POST'])
def upload_other_objects(request):
    warnings.filterwarnings('ignore')
    file = request.FILES['file_other_objects']
    file_bytes = file.read()
    excel_base64 = base64.b64encode(file_bytes).decode()
    obj = models.TempFiles.objects.create(data=excel_base64)
    load_other_objects.delay(obj.id)
    messages.success(request, 'Загрузка Прочих объектов началась. Отслеживайте выполнение в Статусе фоновых задач')
    return redirect('/admin')


@api_view(['POST'])
def upload_dist(request):
    warnings.filterwarnings('ignore')
    file_dist = request.FILES['file_dist']
    utils.load_dist(file_dist)
    messages.success(request, 'Файл расстояний успешно загружен')
    return redirect('/admin')


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_current_user(request):
    return JsonResponse(
        {'groups': [gr.name for gr in request.user.groups.all()]},
    )


def download_pvz_template(self):
    image_buffer = open('Постоматы и ПВЗ.xlsx', "rb").read()
    response = HttpResponse(image_buffer, content_type='xlsx')
    response['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename('pvz_and_postomats.xlsx')
    return response


def download_other_template(self):
    image_buffer = open('Другие объекты.xlsx', "rb").read()
    response = HttpResponse(image_buffer, content_type='xlsx')
    response['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename('other.xlsx')
    return response
