from datetime import timedelta

from django.db.models import Avg, Count, Q, F
from django.utils import timezone
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator

from utils.serializers import Round
from utils.web_helper import purchase_event, survey_event, redeem_event
from .models import Customer, Product, SaleRecord, Business, Reward, Redeem, _generate_validation_code


# region Inlines
class BusinessInlineSerializer(serializers.ModelSerializer):
    class Meta:
        model = Business
        fields = ('id', 'created_at', 'name', 'icon', 'loyalty_model', 'google_link')
        read_only_fields = ('id', 'created_at',)


class CustomerInlineSerializer(serializers.ModelSerializer):
    id = serializers.CharField(validators=[], required=False)
    full_name = serializers.CharField(required=False)
    country_code = serializers.CharField(required=False)
    mobile = serializers.CharField(required=False)

    class Meta:
        model = Customer
        exclude = ('business',)


# endregion


class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ('id', 'name', 'created_at', 'industry')
        read_only_fields = ('id', 'created_at',)


class VerifySerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ('validation_code',)


class CustomerLoginSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ('mobile', 'country_code')


class CustomerListSerializer(serializers.ModelSerializer):
    class Meta:
        model = Customer
        fields = ('id', 'business', 'country_code', 'mobile', 'full_name',
                  'gender', 'dob', 'email', 'acquisition_channel')
        read_only_fields = ('id', 'created_at',)
        validators = [
            UniqueTogetherValidator(
                queryset=Customer.objects.all(),
                fields=['business', 'mobile']
            )
        ]


class CustomerRetrieveSerializer(serializers.ModelSerializer):
    business = BusinessInlineSerializer(read_only=True)
    last_sale_id = serializers.SerializerMethodField()
    is_validated = serializers.SerializerMethodField()
    first_sale = serializers.SerializerMethodField()

    class Meta:
        model = Customer
        read_only_fields = ('id', 'created_at',)
        exclude = ('validation_code',)

    def get_is_validated(self, obj):
        return obj.validation_code is None

    def get_last_sale_id(self, obj):
        sale = SaleRecord.objects.filter(customer=obj).order_by('created_at').last()
        return sale.pk if sale and sale.rate is None else None

    def get_first_sale(self, obj):
        sale_count = SaleRecord.objects.filter(customer=obj).count()
        return sale_count == 1


class SaleSerializer(serializers.ModelSerializer):
    is_dashboard = serializers.BooleanField(default=False, required=False)

    class Meta:
        model = SaleRecord
        fields = '__all__'
        read_only_fields = ('id', 'created_at',)

    def create(self, validated_data):
        is_dashboard: bool = validated_data.pop("is_dashboard")

        if validated_data.get("customer"):
            validated_data['is_verified'] = is_dashboard
            is_first = SaleRecord.objects.filter(customer_id=validated_data.get("customer").id).count() < 1
            products = [str(product) for product in validated_data.get("products", list())]
            purchase_event(
                products=products,
                amount=validated_data.get('amount'),
                delivery_date=validated_data.get('delivery_date'),
                customer_id=validated_data.get("customer").id,
                first_purchase=is_first,
                send_source='Business' if is_dashboard else 'Customer',
                is_verified=is_dashboard,
                rate=validated_data.get("rate"),
            )

        return super().create(validated_data)

    def update(self, instance: SaleRecord, validated_data):
        if 'is_dashboard' in validated_data:
            validated_data.pop("is_dashboard")
        if instance.customer:
            survey_event(
                rate=validated_data.get('rate'),
                customer_id=instance.customer.id
            )
        return super().update(instance, validated_data)


class SaleCustomerSerializer(serializers.ModelSerializer):
    is_dashboard = serializers.BooleanField(default=False, required=False)
    approval = serializers.CharField(allow_null=True, required=False)
    customer = CustomerInlineSerializer(required=False, allow_null=True)

    class Meta:
        model = SaleRecord
        fields = ('id', 'is_dashboard', 'approval', 'description', 'rate', 'amount',
                  'delivery_date', 'products', 'customer', 'business')
        read_only_fields = ('id', 'created_at',)

    def create(self, validated_data):
        approval: str = validated_data.pop("approval", None)
        is_dashboard: bool = validated_data.pop("is_dashboard")
        customer_data = validated_data.get('customer')
        business = validated_data.get('business')
        if approval and business.approval_code == approval:
            business.approval_code = _generate_validation_code()
            business.save(update_fields=['approval_code'])
            is_dashboard = True

        validated_data['is_verified'] = is_dashboard
        if customer_data:
            customer_data['business'] = business
            customer = Customer.objects.filter(id=customer_data.get('id')).first()
            if not customer:
                customer = Customer.objects.filter(country_code=customer_data.get(
                    'country_code'), mobile=customer_data.get('mobile'), business=business).first()
                if not customer:
                    self.customer_validation(customer_data)
                    customer = Customer()
            self.update_if_present(customer, customer_data)
            customer.save()
            validated_data['customer'] = customer

        if validated_data.get("customer"):
            products = [str(product) for product in validated_data.get("products", list())]
            is_first = SaleRecord.objects.filter(customer_id=validated_data.get("customer").id).count() < 1
            purchase_event(
                products=products,
                amount=validated_data.get('amount'),
                delivery_date=validated_data.get('delivery_date'),
                customer_id=validated_data.get("customer").id,
                first_purchase=is_first,
                send_source='Business' if is_dashboard else 'Customer',
                is_verified=is_dashboard,
                rate=validated_data.get("rate")
            )
        return super().create(validated_data)

    def update_if_present(self, customer: Customer, customer_data: dict):
        for f in Customer._meta.get_fields():
            if f.name in customer_data:
                setattr(customer, f.name, customer_data.get(f.name))

    def customer_validation(self, customer_data: dict):
        error_list = dict()
        for f in ['country_code', 'mobile', 'full_name']:
            if f not in customer_data:
                error_list[f] = ["This field is required."]
        if len(error_list):
            raise serializers.ValidationError(error_list)


class SaleRetrieveSerializer(serializers.ModelSerializer):
    customer = CustomerRetrieveSerializer(read_only=True)

    class Meta:
        model = SaleRecord
        fields = '__all__'
        read_only_fields = ('id', 'created_at',)

    def create(self, validated_data):
        return


class BusinessSerializer(serializers.ModelSerializer):
    dashboard = serializers.SerializerMethodField()

    class Meta:
        model = Business
        fields = '__all__'
        read_only_fields = ('id', 'created_at',)

    def get_dashboard(self, obj):
        business_id = obj.pk
        by_score, total_percent = self.get_nps_by_score(business_id)

        res = dict(
            customer=dict(
                total_count=self.get_total_customer_count(business_id),
                new_count=self.get_new_customer_count(business_id),
                recurring_count=self.get_recurring_customer_count(business_id),
                new=self.get_daily_new_customer_count(business_id),
                recurring=self.get_daily_recurring_customer_count(business_id),
            ),
            nps=dict(
                by_day=self.get_daily_feedback_avg(business_id),
                count_by_day=self.get_daily_feedback_count(business_id),
                by_score=by_score,
                total_percent=total_percent
            ),
        )
        return res

    def get_daily_new_customer_count(self, business_id):
        today = timezone.now()
        start_date = today - timedelta(days=28)
        end_date = today + timedelta(days=1)

        existing_customer_counts = (
            Customer.objects
            .filter(created_at__gte=start_date, created_at__lt=end_date, business_id=business_id)
            .extra({'day': 'date(created_at)'})
            .values('day')
            .annotate(count=Count('id'))
            .order_by('day')
        )
        # Create a dictionary to store the counts
        counts_by_day = {count['day'].strftime("%Y-%m-%d"): count['count'] for count in existing_customer_counts}
        # Generate a complete list of dates for the last 28 days
        dates = [(start_date.date() + timedelta(days=i)).strftime("%Y-%m-%d")
                 for i in range((end_date.date() - start_date.date()).days)]
        # Populate counts for missing dates with 0
        complete_counts = [{'label': date, 'value': counts_by_day.get(date, 0)} for date in dates]
        return complete_counts

    def get_daily_recurring_customer_count(self, business_id):
        # Calculate the date 28 days ago
        end_date = timezone.now().date()
        start_date = end_date - timedelta(days=28)

        # Retrieve the daily count of customers meeting the criteria
        date_range = end_date - start_date
        daily_counts = list()

        for i in range(date_range.days + 1):
            current_date = start_date + timedelta(days=i)
            next_date = current_date + timedelta(days=1)
            count = Customer.objects.filter(
                business__id=business_id
            ).annotate(sale_count=Count('salerecord')).filter(
                sale_count__gt=1,
                salerecord__created_at__date__gt=F('created_at__date'),
                salerecord__created_at__date__gte=current_date,
                salerecord__created_at__date__lt=next_date,
            ).distinct().count()
            daily_counts.append(dict(
                label=current_date.strftime("%Y-%m-%d"),
                value=count
            ))

        return daily_counts

    def get_total_customer_count(self, business_id):
        customer_counts = (
            Customer.objects
            .filter(business_id=business_id)
            .count()
        )
        return customer_counts

    def get_new_customer_count(self, business_id):
        today = timezone.now()
        start_date = today - timedelta(days=28)
        end_date = today + timedelta(days=1)
        customer_counts = (
            Customer.objects
            .filter(created_at__gte=start_date, created_at__lt=end_date, business_id=business_id)
            .count()
        )
        return customer_counts

    def get_recurring_customer_count(self, business_id):
        today = timezone.now()
        start_date = today - timedelta(days=28)
        customer_counts = Customer.objects.filter(
            business__id=business_id
        ).annotate(sale_count=Count('salerecord')).filter(
            sale_count__gt=1,
            salerecord__created_at__gte=start_date
        ).distinct().count()

        return customer_counts

    def get_daily_feedback_count(self, business_id):
        today = timezone.now()
        start_date = today - timedelta(days=28)
        end_date = today + timedelta(days=1)

        existing_customer_counts = (
            SaleRecord.objects
            .filter(updated_at__gte=start_date,
                    updated_at__lt=end_date,
                    business__id=business_id,
                    rate__isnull=False)
            .extra({'day': 'date(api_salerecord.updated_at)'})
            .values('day')
            .annotate(count=Count('id'))
            .order_by('day')
        )
        # Create a dictionary to store the counts
        counts_by_day = {count['day'].strftime("%Y-%m-%d"): count['count'] for count in existing_customer_counts}
        # Generate a complete list of dates for the last 28 days
        dates = [(start_date.date() + timedelta(days=i)).strftime("%Y-%m-%d")
                 for i in range((end_date.date() - start_date.date()).days)]
        # Populate counts for missing dates with 0
        complete_counts = [{'label': date, 'value': counts_by_day.get(date, 0)} for date in dates]
        return complete_counts

    def get_daily_feedback_avg(self, business_id):
        today = timezone.now()
        start_date = today - timedelta(days=28)
        end_date = today + timedelta(days=1)
        average_rates = (
            SaleRecord.objects
            .filter(updated_at__gte=start_date,
                    updated_at__lt=end_date,
                    business__id=business_id,
                    rate__isnull=False)
            .extra({'day': 'date(api_salerecord.updated_at)'})
            .values('day')
            .annotate(average_rate=Round(Avg('rate')))
            .order_by('day')
        )

        # Create a dictionary to store the average rates
        average_rates_by_day = {rate['day'].strftime("%Y-%m-%d"): rate['average_rate'] for rate in average_rates}

        # Generate a complete list of dates for the last 28 days
        dates = [(start_date.date() + timedelta(days=i)).strftime("%Y-%m-%d")
                 for i in range((end_date.date() - start_date.date()).days)]

        # Populate average rates for missing dates with 0
        complete_average_rates = [{'label': date, 'value': average_rates_by_day.get(date, 0)} for date in dates]
        return complete_average_rates

    def get_nps_by_score(self, business_id):
        rating_counts = (
            SaleRecord.objects
            .filter(business__id=business_id, rate__isnull=False)
            .aggregate(
                detractors=Count('id', filter=Q(rate__range=(0, 6))),
                passives=Count('id', filter=Q(rate__range=(7, 8))),
                promoters=Count('id', filter=Q(rate__range=(9, 10))),
                total=Count('id')
            )
        )
        detractors = rating_counts['detractors']
        promoters = rating_counts['promoters']
        total = rating_counts['total']
        nps = 0 if total == 0 else round((promoters - detractors) * 100 / total, 1)
        return rating_counts, nps


class RewardSerializer(serializers.ModelSerializer):
    business = BusinessInlineSerializer(read_only=True)
    remaining = serializers.SerializerMethodField()
    useable = serializers.SerializerMethodField()

    class Meta:
        model = Reward
        fields = '__all__'

    def get_remaining(self, obj: Reward):
        if obj.capacity == 0:
            return 1
        usage = Redeem.objects.filter(reward=obj).count()
        return obj.capacity - usage

    def get_useable(self, obj: Reward):
        request = self.context['request']
        customer_id = request.query_params.get('customer')
        customer: Customer = Customer.objects.filter(id=customer_id).first()
        customer_usage = Redeem.objects.filter(customer=customer).count()
        reusable = obj.reusable == 0 or obj.reusable > customer_usage
        if customer:
            return self.get_remaining(obj) > 0 \
                and customer.loyalty_weight >= obj.loyalty_weight \
                and reusable
        else:
            return False


class RedeemCreateSerializer(serializers.ModelSerializer):
    class Meta:
        model = Redeem
        fields = '__all__'

    def create(self, validated_data):
        customer = validated_data.get("customer")
        reward = validated_data.get("reward")
        customer.loyalty_weight -= reward.loyalty_weight
        customer.save()
        redeem_obj: Redeem = super().create(validated_data)
        redeem_event(redeem=redeem_obj)
        return redeem_obj


class RedeemListSerializer(serializers.ModelSerializer):
    customer = CustomerInlineSerializer(read_only=True)
    reward = RewardSerializer(read_only=True)

    class Meta:
        model = Redeem
        fields = '__all__'
