from datetime import date, datetime

from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models import Avg, Case, Count, F, Prefetch, Q, QuerySet, When, Subquery
from django.db.models.functions import Length
from django.http import HttpResponseRedirect
from django.utils.decorators import method_decorator
from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound
from rest_framework.generics import get_object_or_404
from rest_framework.mixins import (
    CreateModelMixin,
    ListModelMixin,
    RetrieveModelMixin,
    UpdateModelMixin,
)
from rest_framework.permissions import AllowAny, IsAuthenticatedOrReadOnly
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from ruamel.yaml.compat import ordereddict

from verify_trusted.companies.api.filters.company import CompanyFilter
from verify_trusted.companies.api.filters.review import CompanyReviewFilter
from verify_trusted.companies.api.params import (
    end_date_param,
    is_company_profile,
    platform_ids_param,
    rating_param,
    source_ids_param,
    start_date_param,
    is_select_all,
    company_id_param,
)
from verify_trusted.companies.api.serializers import (
    CompanyAllCategorySerializer,
    CompanyFirstLetterSerializer,
    CompanyListSerializer,
    CompanyRemoveUserSerializer,
    CompanySerializer,
    CompanyUpdateSerializer,
    PaymentListSerializer,
    ReviewSerializer,
    SourceSocialSerializer,
)
from verify_trusted.companies.api.serializers.changelog import CompanyHistorySerializer
from verify_trusted.companies.api.viewsets.review import ReviewViewSet
from verify_trusted.companies.models import (
    Company,
    Payment,
    Review,
    SourceSocial,
    Subscription,
)
from verify_trusted.reviews.api.serializers import ReviewSourceSerializer
from verify_trusted.reviews.models import Platform, ReviewSource
from verify_trusted.users.api.paginations import (
    AllCategoriesResultsSetPagination,
    CompanyResultsSetPagination,
    ReviewResultsSetPagination,
)
from verify_trusted.users.api.params import (
    category_main_param,
    category_param,
    domain_param,
    first_letter_param,
    has_reviews_param,
    is_verified_param,
    name_param,
    platform_param,
    rating_star_param,
    user_param,
    has_email_param,
    url_param,
)
from django.db.models import F, Func

search_params = [
    name_param,
    first_letter_param,
    domain_param,
    category_param,
    category_main_param,
    user_param,
    is_verified_param,
    platform_param,
    has_reviews_param,
    rating_star_param,
    has_email_param,
]

company_reviews_filter_params = [
    platform_ids_param,
    source_ids_param,
    rating_param,
    start_date_param,
    end_date_param,
]

company_reviews_urls_params = [
    platform_ids_param,
    source_ids_param,
    rating_param,
    start_date_param,
    end_date_param,
    url_param
]


company_review_sources_filter_params = [
    is_company_profile,
]

company_review_sources_url_params = [
    is_company_profile,
    url_param
]

company_select_all_params = [
    company_id_param,
    is_select_all,
]

User = get_user_model()


class BaseCompanyViewSet(GenericViewSet):
    queryset = (
        Company.objects.order_by('-id')
        .select_related('user')
        .prefetch_related('socials', 'review_sources', 'review_sources__platform')
        .defer(
            'crawled_at',
            'last_review_date',
            'last_synced_at',
            'accreditation',
            'search_vector',
        ).all()

    )
    serializer_class = CompanySerializer


class BaseCompanyRetrieveMixin(RetrieveModelMixin, GenericViewSet):
    def get_queryset(self):
        queryset = super().get_queryset()
        if self.action == 'retrieve':
            queryset = queryset.with_reviews_count().with_avg_rating()
        return queryset

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        response_data = serializer.data
        if (
            instance.logo
            and hasattr(instance.logo, 'name')
            and 'http' in instance.logo.name
        ):
            response_data['logo'] = instance.logo.name
        response_data['is_expire'] = True
        response_data['is_trial'] = False
        response_data['due_date'] = None
        subscription = Subscription.objects.filter(
            company=serializer.data['id']
        ).first()
        if subscription is not None:
            if subscription.due_date is None:
                response_data['is_expire'] = False
            else:
                response_data['is_expire'] = subscription.due_date <= date.today()
            response_data['is_trial'] = subscription.payment is None
            response_data['due_date'] = subscription.due_date
        return Response(response_data)


class BaseCompanyReviewViewSet(GenericViewSet):
    """
    This view contains API for fetching `Review` &
    `ReviewSource` of
    `Company`
    """

    pagination_class = CompanyResultsSetPagination

    def get_reviews_queryset(self) -> QuerySet[Review]:
        queryset = Review.objects.prefetch_related('source__platform').all()
        if self.request.user.is_authenticated and self.request.user.is_superuser:
            return queryset
        else:
            queryset = queryset.filter(
                source__platform__status__in=[
                    Platform.Status.ACTIVE,
                    Platform.Status.UNAVAILABLE,
                ]
            )
        return queryset

    def get_review_sources_queryset(self) -> QuerySet[ReviewSource]:
        queryset = ReviewSource.objects.all()
        if self.request.user.is_authenticated and self.request.user.is_superuser:
            return queryset
        else:
            queryset = queryset.filter(
                platform__status__in=[
                    Platform.Status.ACTIVE,
                    Platform.Status.UNAVAILABLE,
                    Platform.Status.HIDE_REVIEWS,
                ]
            )
        return queryset

    @swagger_auto_schema(
        method='GET',
        manual_parameters=company_reviews_filter_params,
        responses={status.HTTP_200_OK: ReviewSerializer(many=True)},
        pagination_class=ReviewResultsSetPagination,
    )
    @action(detail=True, methods=['GET'], url_path='reviews')
    def reviews(self, request: Request, *args, **kwargs):  # noqa
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
        company: Company = get_object_or_404(
            Company.objects.only('id'), **filter_kwargs
        )
        queryset = (
            self.get_reviews_queryset()
            .annotate(
                platform_logo=F('source__platform__logo'),
                platform_status=F('source__platform__status'),
                sort_display_order=Case(
                    When(display_order__gt=0, then='display_order'), default=None
                ),
            )
            .filter(source__company_id=company.id, is_active=True)
            .order_by('sort_display_order', '-date')
        )
        queryset = CompanyReviewFilter(
            self.request.GET, queryset, request=self.request
        ).qs

        '''
        Filtering
        '''
        query_params = self.request.query_params
        start_date_filter = query_params.get(start_date_param.name, None)
        if start_date_filter:
            end_date_filter = query_params.get(end_date_param.name, None)
            start_date_filter = datetime.strptime(start_date_filter, '%Y-%m-%d')
            end_date_filter = (
                datetime.strptime(end_date_filter, '%Y-%m-%d')
                if end_date_filter is not None
                else date.today()
            )
            queryset = queryset.filter(date__range=[start_date_filter, end_date_filter])
        serializer = ReviewSerializer(queryset, many=True)
        return Response(serializer.data)

    @swagger_auto_schema(
        method='GET',
        manual_parameters=company_review_sources_filter_params,
        responses={status.HTTP_200_OK: ReviewSourceSerializer(many=True)},
    )
    @action(detail=True, methods=['GET'], url_path='review-sources')
    def review_sources(self, request: Request, *args, **kwargs):  # noqa
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
        company: Company = get_object_or_404(
            Company.objects.only('id'), **filter_kwargs
        )
        reviews_prefetch = Prefetch(
            'reviews', queryset=Review.objects.all().only('id', 'rating', 'source')
        )
        review_sources = (
            self.get_review_sources_queryset()
            .prefetch_related(reviews_prefetch, 'platform')
            .annotate(
                cal_reviews_count=Count('reviews'),
                cal_average_rating=Round(Avg('reviews__rating')),
            )
            .filter(company_id=company.id)
            .order_by('-average_rating')
        )
        serializer = ReviewSourceSerializer(review_sources, many=True)
        return Response(serializer.data)

    @swagger_auto_schema(
        method='GET',
        responses={status.HTTP_200_OK: ReviewSerializer(many=True)},
    )
    @action(detail=True, methods=['GET'], url_path='get-sort-review')
    def get_sort_reviews(self, request, pk):
        try:
            company: Company = self.get_object()
            orderbyList = ['display_order', '-date']
            review_source_query = Q(company_id=company.id) & Q(
                platform__status=Platform.Status.ACTIVE
            )
            data = ReviewSource.objects.filter(review_source_query).first()
            serializer = ReviewSerializer(Review.objects.filter(
                source_id__in=Subquery(ReviewSource.objects.filter(review_source_query).values_list('id')),
                is_active=True).order_by(*orderbyList)[:20], many=True)
        except Exception as e:
            print(e)
            return Response(
                status=status.HTTP_501_NOT_IMPLEMENTED)
        return Response(serializer.data)

    @swagger_auto_schema(
        method='PUT',
        request_body=ReviewSerializer(many=True),
        responses={status.HTTP_200_OK: ReviewSerializer(many=True)},
    )
    @action(detail=True, methods=['PUT'], url_path='save-sorted-reviews')
    def save_sorted_reviews(self, request: Request, *args, **kwargs):  # noqa
        company: Company = self.get_object()
        review_ids = []
        for i, review in enumerate(request.data):
            Review.objects.filter(id=review["id"]).update(display_order=i + 1)
            review_ids.append(review['id'])

        Review.objects.filter(
            source_id__in=Subquery(ReviewSource.objects.filter(company_id=company.id).values_list('id'))).exclude(
            id__in=review_ids).update(display_order=None)
        return Response(request.data)

    # @action(detail=True, methods=['GET'], url_path='check-exists')
    #
    # def check_exist(self, request, pk=None):
    #     company: Company = self.get_object()
    #     # r_company = Company.objects.filter(id=company.id)[0]
    #     if company.status == 1:
    #         url = Company.objects.filter(normalized_domain=company.normalized_domain, status=0)[0].url
    #         return Response(status=status.HTTP_302_FOUND, data=f'https://{settings.CLIENT_NAME}/reviews/{url}')
    #     elif company.status == 2:
    #         return Response(status=status.HTTP_302_FOUND, data=f'https://{settings.CLIENT_NAME}/all-company')
    #     else:
    #         return Response(status=status.HTTP_200_OK)

    @swagger_auto_schema(
        method='GET',
    )
    @action(detail=True, methods=['GET'], url_path='check-exists')
    def check_exist(self, request: Request, *args, **kwargs):
        company: Company = self.get_object()
        # r_company = Company.objects.filter(id=company.id)[0]
        if company.status == 1:
            url = Company.objects.filter(normalized_domain=company.normalized_domain, status=0)[0].url
            return Response(status=status.HTTP_302_FOUND, data=f'https://{settings.CLIENT_NAME}/reviews/{url}')
        elif company.status == 2:
            return Response(status=status.HTTP_302_FOUND, data=f'https://{settings.CLIENT_NAME}/all-company')
        else:
            return Response(status=status.HTTP_200_OK)

    @swagger_auto_schema(
        method='GET',
    )
    @action(detail=True, methods=['GET'], url_path='check-exists-place-id')
    def check_exist_place_id(self, request: Request, *args, **kwargs):
        company: Company = self.get_object()
        # r_company = Company.objects.filter(id=company.id)[0]
        company_map = company.map
        if company_map is not None and "place_id" in company_map.keys() and company_map['place_id'] != None and len(
            company_map['place_id']) > 0:
            company.map["place_status"] = 1
            company.save()
            return Response(status=status.HTTP_200_OK, data=1)
        elif company_map is not None and "isUpdated" in company_map.keys() and company_map['isUpdated'] == True:
            company.map["place_status"] = 2
            company.save()
            return Response(status=status.HTTP_200_OK, data=2)
        else:
            company.map["place_status"] = 0
            company.save()
            return Response(status=status.HTTP_200_OK, data=0)


class BaseCompanyPaymentViewSet(GenericViewSet):
    @action(detail=True, methods=['GET'], url_path='payment')
    def payment(self, request: Request, *args, **kwargs):
        company: Company = self.get_object()
        payment = Payment.objects.prefetch_related('plan', 'company').filter(
            company=company
        )
        serializer = PaymentListSerializer(payment, many=True)
        return Response(serializer.data)


class Round(Func):
    function = 'ROUND'
    template = '%(function)s(%(expressions)s, 2)'


class BaseCompanyHistoryViewSet(GenericViewSet):
    @action(detail=True, methods=['GET'], url_path='histories')
    def histories(self, request: Request, *args, **kwargs):  # noqa
        company: Company = self.get_object()
        histories = (
            company.history.only('history_id', 'history_date')
            .all()
            .order_by('-history_date')
            .values('history_id', 'history_date')
        )
        return Response(CompanyHistorySerializer(histories, many=True).data)

    @action(detail=True, methods=['GET'], url_path='histories/(?P<history_id>[^/]+)')
    def history_diff(self, request: Request, history_id, *args, **kwargs):  # noqa
        company: Company = self.get_object()
        history = company.history.filter(history_id=history_id).first()
        if not history:
            raise NotFound()
        return Response(CompanySerializer(history).data)


@method_decorator(
    name='list', decorator=swagger_auto_schema(manual_parameters=search_params)
)
class BaseCompanyListViewSet(ListModelMixin, GenericViewSet):
    pagination_class = CompanyResultsSetPagination
    permission_classes = [IsAuthenticatedOrReadOnly]
    filterset_class = CompanyFilter

    def get_serializer_class(self):
        if self.action == 'remove_user':
            return CompanyRemoveUserSerializer
        if self.action in ['list', 'retrieve']:
            if (
                self.action == 'list'
                and self.request.query_params.get(first_letter_param.name, None)
                is not None
            ):
                return CompanyFirstLetterSerializer
            return CompanyListSerializer
        return super().get_serializer_class()


class CompanyViewSet(
    CreateModelMixin,
    UpdateModelMixin,
    BaseCompanyListViewSet,
    BaseCompanyReviewViewSet,
    BaseCompanyViewSet,
    BaseCompanyPaymentViewSet,
):
    lookup_value_regex = '[0-9]+'  # Must!

    def get_serializer_class(self):
        if self.action == 'update':
            return CompanyUpdateSerializer
        return super().get_serializer_class()

    def get_queryset(self):
        queryset = super().get_queryset()
        if self.action in ['update', 'partial_update']:
            # Only owner can update
            queryset = queryset.filter(user=self.request.user)
        return queryset

    def perform_update(self, serializer):
        if (
            'currency_symbol' in serializer.validated_data
            and self.request.user.is_superuser is False
        ):
            serializer.validated_data.pop('currency_symbol', None)
        super().perform_update(serializer)


class CompanyURLLookUpViewSet(
    BaseCompanyRetrieveMixin,
    BaseCompanyReviewViewSet,
    BaseCompanyViewSet,
):
    lookup_field = 'url'
    lookup_value_regex = '[^/]+'  # Default value: (?P<url>[^/.]+)
    permission_classes = [IsAuthenticatedOrReadOnly]

    def get_reviews_queryset(self) -> QuerySet[Review]:
        return Review.objects.prefetch_related('source__platform').filter(
            source__platform__status=Platform.Status.ACTIVE
        )

    def get_review_sources_queryset(self) -> QuerySet[ReviewSource]:
        return ReviewSource.objects.filter(
            Q(platform__status=Platform.Status.ACTIVE) | Q(platform__status=Platform.Status.HIDE_REVIEWS))

    swagger_auto_schema(manual_parameters=[url_param])
    def get_companies_by_urls(self, request):
        url = self.request.query_params.get('url')
        instance = Company.objects.filter(url=url).first()
        serializer = self.get_serializer(instance)
        if instance is not None:
            response_data = serializer.data
            if (
                instance.logo
                and hasattr(instance.logo, 'name')
                and 'http' in instance.logo.name
            ):
                response_data['logo'] = instance.logo.name
            response_data['is_expire'] = True
            response_data['is_trial'] = False
            response_data['due_date'] = None
            subscription = Subscription.objects.filter(
                company=serializer.data['id']
            ).first()
            if subscription is not None:
                if subscription.due_date is None:
                    response_data['is_expire'] = False
                else:
                    response_data['is_expire'] = subscription.due_date <= date.today()
                response_data['is_trial'] = subscription.payment is None
                response_data['due_date'] = subscription.due_date
            return Response(response_data)
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
    
    @swagger_auto_schema(
        manual_parameters=company_reviews_urls_params,
        responses={status.HTTP_200_OK: ReviewSerializer(many=True)},
        pagination_class=ReviewResultsSetPagination,
    )
    def get_company_reviews_by_urls(self, request):
        url = self.request.query_params.get('url')
        company = Company.objects.filter(url=url).first()
        if company is not None:
            queryset = (
                self.get_reviews_queryset()
                .annotate(
                    platform_logo=F('source__platform__logo'),
                    platform_status=F('source__platform__status'),
                    sort_display_order=Case(
                        When(display_order__gt=0, then='display_order'), default=None
                    ),
                )
                .filter(source__company_id=company.id, is_active=True)
                .order_by('sort_display_order', '-date')
            )
            queryset = CompanyReviewFilter(
                self.request.GET, queryset, request=self.request
            ).qs

            '''
            Filtering
            '''
            query_params = self.request.query_params
            start_date_filter = query_params.get(start_date_param.name, None)
            if start_date_filter:
                end_date_filter = query_params.get(end_date_param.name, None)
                start_date_filter = datetime.strptime(start_date_filter, '%Y-%m-%d')
                end_date_filter = (
                    datetime.strptime(end_date_filter, '%Y-%m-%d')
                    if end_date_filter is not None
                    else date.today()
                )
                queryset = queryset.filter(date__range=[start_date_filter, end_date_filter])
            serializer = ReviewSerializer(queryset, many=True)
            return Response(serializer.data)
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
    
    @swagger_auto_schema(
        manual_parameters=company_review_sources_url_params,
        responses={status.HTTP_200_OK: ReviewSourceSerializer(many=True)},
    )
    def get_company_review_sources_by_urls(self, request: Request, *args, **kwargs):  # noqa
        url = self.request.query_params.get('url')
        company = Company.objects.filter(url=url).first()
        if company is not None:
            reviews_prefetch = Prefetch(
                'reviews', queryset=Review.objects.all().only('id', 'rating', 'source')
            )
            review_sources = (
                self.get_review_sources_queryset()
                .prefetch_related(reviews_prefetch, 'platform')
                .annotate(
                    cal_reviews_count=Count('reviews'),
                    cal_average_rating=Round(Avg('reviews__rating')),
                )
                .filter(company_id=company.id)
                .order_by('-average_rating')
            )
            serializer = ReviewSourceSerializer(review_sources, many=True)
            return Response(serializer.data)
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
    
@method_decorator(
    name='list', decorator=swagger_auto_schema(manual_parameters=[first_letter_param])
)
class CompanyCategoryViewSet(ListModelMixin, GenericViewSet):
    queryset = Company.objects.only('category_main')
    serializer_class = CompanyAllCategorySerializer
    permission_classes = [AllowAny]
    pagination_class = AllCategoriesResultsSetPagination

    def get_queryset(self):
        queryset = super().get_queryset()
        if self.action == 'list':
            query_params = self.request.query_params

            first_letter = query_params.get(first_letter_param.name, None)
            if first_letter:
                first_letter: str = first_letter[:1]
                first_letter_query = Q(
                    category_main__startswith=first_letter.lower()
                ) | Q(category_main__startswith=first_letter.upper())
                queryset = (
                    Company.objects.only(
                        'category_main',
                    )
                    .filter(first_letter_query)
                    .exclude(category_main='Others')
                    .distinct('category_main')
                    .order_by('category_main')
                )
                return queryset
        return (
            queryset.annotate(text_len=Length('category_main'))
            .filter(text_len__lte=20)
            .exclude(category_main='Others')
            .distinct("category_main")
            .order_by('category_main')
        )


class SourceSocialViewSet(
    ListModelMixin,
    CreateModelMixin,
    RetrieveModelMixin,
    GenericViewSet,
    UpdateModelMixin,
):
    permission_classes = [IsAuthenticatedOrReadOnly]
    queryset = SourceSocial.objects.all().order_by('display_order')
    serializer_class = SourceSocialSerializer
