from distutils.util import strtobool

from django.db.models import Case, Count, IntegerField, Value, When, Subquery
from django.utils import timezone
from django.utils.decorators import method_decorator
from drf_yasg.utils import no_body, swagger_auto_schema
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.mixins import CreateModelMixin, ListModelMixin
from rest_framework.permissions import (
    IsAdminUser,
    IsAuthenticated,
    IsAuthenticatedOrReadOnly,
)
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet

from verify_trusted.companies.history_types import HistoryType, HistoryTypeSymbol
from verify_trusted.companies.models import Company, Review
from verify_trusted.reviews.api.filters import ReviewSourceChangelogFilter
from verify_trusted.reviews.api.paginations import (
    PlatformResultsSetPagination,
    ReviewSourceHistoryResultsSetPagination,
    ReviewSourceResultsSetPagination,
)
import json
from verify_trusted.reviews.api.params import (
    company_name_param,
    company_param,
    date_after_param,
    date_before_param,
    history_type_param,
    is_popular_param,
    name_param,
    status_param,
    access_token,
    page_id,
    is_add
)
from verify_trusted.reviews.api.serializers import (
    PlatformSerializer,
    ReviewSourceHistorySerializer,
    ReviewSourceSerializer,
    UserPlatformCreateSerializer,
    UserReviewSourceCreateSerializer,
)
from verify_trusted.reviews.models import Platform, ReviewSource
from verify_trusted.reviews.tasks import sync_reviews_task
from verify_trusted.utils.swagger_schemas import NoDjangoFilerBackendFilterInspector

history_search_params = [
    company_name_param,
    company_param,
    history_type_param,
    date_before_param,
    date_after_param,
]


class ReviewSourceViewSet(ModelViewSet):
    queryset = ReviewSource.objects.select_related('platform').all()
    serializer_class = ReviewSourceSerializer
    permission_classes = [
        IsAuthenticatedOrReadOnly,
    ]
    pagination_class = ReviewSourceResultsSetPagination

    def get_serializer_class(self):
        if self.action in ['create', 'update', 'destroy']:
            return UserReviewSourceCreateSerializer
        return super().get_serializer_class()

    def get_queryset(self):
        queryset = super().get_queryset()
        if self.action in ['update', 'partial_update', 'destroy', 'sync_reviews']:
            user = self.request.user
            if not user.is_superuser:
                queryset = queryset.filter(
                    company__user=user,
                    platform__status__in=[
                        Platform.Status.ACTIVE,
                        Platform.Status.HIDE_REVIEWS,
                        Platform.Status.UNAVAILABLE,
                    ],
                )
        return queryset

    def retrieve(self, request: Request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        response_data = serializer.data
        record = instance.history.latest()
        response_data['history_id'] = record.history_id
        return Response(response_data)

    @swagger_auto_schema(request_body=no_body, manual_parameters=[access_token, page_id, is_add])
    @action(detail=True, methods=['POST'], url_path='sync-reviews')
    def sync_reviews(self, request: Request, *args, **kwargs):  # noqa
        instance = self.get_object()
        if 'is_add' in request.query_params.keys():
            is_add_param = json.loads(request.query_params['is_add'])
        else:
            is_add_param = False
        if 'access_token' in request.query_params.keys() and 'page_id' in request.query_params.keys():
            access_token = request.query_params['access_token']
            page_id = request.query_params['page_id']
            task = sync_reviews_task.s(instance.id, access_token, page_id, is_add_param)
        else:
            task = sync_reviews_task.s(instance.id, None, None, is_add_param)
        task.apply_async()
        instance.sync_status = ReviewSource.SyncStatus.SYNCING
        instance.save()
        return Response(status=200)

    # @swagger_auto_schema(request_body=no_body)
    # @action(detail=True, methods=['POST'], url_path='check-fb-token')
    # def check_fb_token(self, request: Request, *args, **kwargs):  # noqa
    #     instance = ReviewSource.objects.filter(id=kwargs['pk'])[0]
    #     info = instance.other
    #     if info is not None and "access_token" in info.keys() and 'page_id' in info.keys():
    #         return Response(status=200)
    #     else:
    #         return Response(status=400)


    def add_changelog(self, serializer):
        company = (
            serializer.instance.company
            if serializer.instance is not None
            else serializer.validated_data['company']
        )
        history = (
            company.history.exclude(history_change_reason=HistoryType.CLAIMED)
                .filter(history_type='~')
                .order_by('-history_date')
                .first()
        )
        if history is not None:
            history.history_date = timezone.now()
            history.save()
        else:
            company.phone = company.phone
            company.save()

    def update_company(self, company_id):
        Company.objects.filter(id=company_id).update(
            average_rating=Subquery(
                Company.objects.filter(id=company_id).with_avg_rating().values('cal_average_rating')[:1]),

            reviews_count=Subquery(
                Company.objects.filter(id=company_id).with_reviews_count().values('cal_reviews_count')[:1])
        )

    def perform_update(self, serializer):
        self.add_changelog(serializer)
        serializer.save()
        company_id = serializer.validated_data['company'].id
        self.update_company(company_id)

    def perform_create(self, serializer):
        self.add_changelog(serializer)
        serializer.save()
        company_id = serializer.validated_data['company'].id
        self.update_company(company_id)

    def perform_destroy(self, pk):
        instance = self.get_object()
        company_id = instance.company_id
        serializer = ReviewSourceSerializer(instance)
        self.add_changelog(serializer)
        instance.delete()
        self.update_company(company_id)


class BasePlatformViewSet(GenericViewSet):
    queryset = Platform.objects.all()
    serializer_class = PlatformSerializer
    pagination_class = PlatformResultsSetPagination

    def get_queryset(self):
        queryset = super().get_queryset()
        if not self.request.user.is_superuser:
            queryset = queryset.filter(
                status__in=[
                    Platform.Status.ACTIVE,
                    Platform.Status.HIDE_REVIEWS,
                    Platform.Status.UNAVAILABLE,

                ]
            )
        queryset = queryset.annotate(
            status_sort_point=Case(
                When(
                    status=Platform.Status.ACTIVE,
                    then=Value(1),
                ),
                When(
                    status=Platform.Status.UNAVAILABLE,
                    then=Value(2),
                ),
                When(
                    status=Platform.Status.INACTIVE,
                    then=Value(3),
                ),
                When(
                    status=Platform.Status.HIDE_REVIEWS,
                    then=Value(4),
                )
                ,
                default=Value(99),
                output_field=IntegerField(),
            )
        ).order_by('-is_popular', 'display_order', 'status_sort_point', 'name')
        if self.action == 'list':
            query_params = self.request.query_params

            if name_filter := query_params.get(name_param.name, None):
                queryset = queryset.filter(name__icontains=name_filter)

            if query_params.get(status_param.name, None) is not None:
                status_filter = query_params.get(status_param.name, None).split(',')
                queryset = queryset.filter(status__in=status_filter)

            if is_popular_filter := query_params.get(is_popular_param.name, None):
                queryset = queryset.filter(
                    is_popular=bool(strtobool(is_popular_filter))
                )
        return queryset


@method_decorator(
    name='list',
    decorator=swagger_auto_schema(
        manual_parameters=[name_param, is_popular_param, status_param]
    ),
)
class PlatformViewSet(
    CreateModelMixin,
    ListModelMixin,
    BasePlatformViewSet,
):
    permission_classes = [
        IsAuthenticatedOrReadOnly,
    ]

    def get_serializer_class(self):
        if self.action == 'create':
            return UserPlatformCreateSerializer
        return super().get_serializer_class()


@method_decorator(
    name='list',
    decorator=swagger_auto_schema(
        manual_parameters=[name_param, is_popular_param, status_param]
    ),
)
class AdminPlatformViewSet(ModelViewSet, BasePlatformViewSet):
    permission_classes = [
        IsAdminUser,
    ]

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        if hasattr(instance, 'review_sources') and instance.review_sources.count():
            if instance.review_sources.count() > 1:
                temps = ReviewSource.objects.filter(platform_id=instance.id).values('company_id').distinct()
                for temp in temps:
                    company_id = temp['company_id']
                    Company.objects.filter(id=company_id).update(
                        average_rating=Subquery(
                            Company.objects.filter(id=company_id).with_avg_rating().values('cal_average_rating')[:1]),

                        reviews_count=Subquery(
                            Company.objects.filter(id=company_id).with_reviews_count().values('cal_reviews_count')[:1])
                    )
                return Response({'is_delete': False}, status=status.HTTP_204_NO_CONTENT)
        try:
            company_id = instance.review_sources.company.id
            Company.objects.filter(id=company_id).update(
                average_rating=Subquery(
                    Company.objects.filter(id=company_id).with_avg_rating().values('cal_average_rating')[:1]),

                reviews_count=Subquery(
                    Company.objects.filter(id=company_id).with_reviews_count().values('cal_reviews_count')[:1])
            )
        except:
            pass
        return Response({'is_delete': True}, status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance: Platform):  # TODO: move to base
        if hasattr(instance, 'review_sources') and instance.review_sources.count():
            instance.status = Platform.Status.INACTIVE
            instance.save()
        else:
            super().perform_destroy(instance)

    @action(detail=True, methods=['GET'])
    def about(self, request: Request, *args, **kwargs):  # noqa
        platform: Platform = self.get_object()
        review_sources_count = ReviewSource.objects.filter(platform_id=platform).count()
        reviews_count = (
            Review.objects.only('id').filter(source__platform_id=platform.id).count()
        )
        company_no_reviews_count = (
            Company.objects.only('id')
                .annotate(cal_reviews_count=Count('review_sources__reviews'))
                .filter(review_sources__platform_id=platform.id, cal_reviews_count=0)
                .count()
        )
        return Response(
            {
                'platform': PlatformSerializer(platform).data,
                'review_sources_count': review_sources_count,
                'reviews_count': reviews_count,
                'company_no_reviews_count': company_no_reviews_count,
            }
        )


@method_decorator(
    name='list',
    decorator=swagger_auto_schema(
        manual_parameters=history_search_params,
        filter_inspectors=[NoDjangoFilerBackendFilterInspector],
    ),
)
class ReviewSourceHistoryViewSet(ReadOnlyModelViewSet):
    serializer_class = ReviewSourceHistorySerializer
    permission_classes = (IsAuthenticated,)
    pagination_class = ReviewSourceHistoryResultsSetPagination
    filterset_class = ReviewSourceChangelogFilter
    lookup_field = 'history_id'

    def get_queryset(self):
        types = [
            HistoryTypeSymbol.CREATED,
            HistoryTypeSymbol.CHANGED,
            HistoryTypeSymbol.DELETED,
        ]
        queryset = (
            ReviewSource.history.prefetch_related('platform', 'company')
                .filter(history_type__in=types)
                .order_by('-history_date')
        )
        user = self.request.user
        if not user.is_superuser and user.companies is not None:
            queryset = queryset.filter(
                id__in=user.companies.values_list('id', flat=True)
            )

        return queryset

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        # https://django-simple-history.readthedocs.io/en/latest/querying_history.html#getting-previous-and-next-historical-record
        prev_record = instance.prev_record
        response = {
            'selected_record': serializer.data,
            'previous_record': self.get_serializer(prev_record).data
            if prev_record is not None
            else None,
        }
        # https://django-simple-history.readthedocs.io/en/latest/history_diffing.html
        if prev_record is not None:
            delta = instance.diff_against(prev_record)
            diff = {
                change.field: {
                    'old': change.old,
                    'new': change.new,
                }
                for change in delta.changes
            }
        else:
            diff = None
        response['diff'] = diff
        return Response(response)
