from decimal import Decimal
import traceback
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login as auth_login, logout
from django.contrib.auth.decorators import user_passes_test, login_required
from rest_framework.decorators import api_view
from django.views.decorators.http import require_POST
from django.forms.models import inlineformset_factory
from django.db.models import Sum, Avg, DateTimeField
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django.db.models import F
import razorpay
import hmac
import hashlib
import json
import twilio
from django.views.decorators.csrf import csrf_exempt
from datetime import datetime, date, timezone

from .signals.custom_signals import shop_add_status_change
import requests, random
from itertools import chain

from django.shortcuts import get_object_or_404
from django.conf import settings
from django.http import JsonResponse, HttpResponse
from django.contrib import messages
from django.utils import timezone
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.apps import apps

from cms.models import FAQ, AboutUs, AppUrl, ContactUs, PrivacyPolicy, ReturnPolicy, TermsAndConditions
from .forms import AboutUsForm, AppUrlForm, ContactUsForm, FAQForm, PrivacyPolicyForm, RefundPolicyForm, ShopForm, ShopSlobIdForm, StoreUserForm, \
    TermsAndConditionsForm, UserEditForm, UserForm, UserModalForm, BankDetailsForm, CategoryForm, \
    ProductSubCategoryForm, ProductForm, TagForm, DynamicFilterForm, SpecialListForm, DeliveryBoyForm, SKUForm, \
    CourierDetailsForm, CouponForm, DeliverySlotForm, DiscountForm, AdForm, CustomProductForm, ProductionUnitForm, \
    CustomOrderForm, MessageForm, YouMayAlsoLikeForm, DeliverySettingsForm, DeliveryDiscountForm, CustomCouponSettingsForm
from shops.models import Shop, BankDetails, ProductionUnit, ShopSlobId
from products.models import ProductCategory, ProductReview, ProductSubCategory, Products, SalesUnitProductSelection, ProductImage, \
    ProductVideo, Tags, DynamicFiltering, SpecialList, SKU, CustomProduct, CustomProductImage, CustomProductVideo, \
    YouMayAlsoLike
from accounts.models import *
from orders.models import AdditionalDetails, BillEdited, CustomOrderTracking, Delhivery, DeliveryBoys, DeliveryOTP, OrderProductCustomization, OrderProducts, Orders, OrderDelivery, Coupons, DeliverySlot, Discount, Ads, Payment, \
    Communication, Message, DeliveryDiscount, DeliverySettings, CustomCouponSettings, CustomCoupon, PaymentModes
from django.core.mail import send_mail
from NavyaBackers.settings import EMAIL_HOST_USER
import string, secrets
from datetime import timedelta
# from accounts.views import send_notification
from accounts.views import initialize_fcm_app

from datetime import datetime
from django.urls import reverse
from django.contrib.auth.models import Group
from django.db.models import Count, Q, F,Prefetch
from django.template.loader import render_to_string
from dal import autocomplete
import requests

from rest_framework.permissions import IsAuthenticated
import jwt
from django.conf import settings
from django.core.mail import send_mail
from django.urls import reverse
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import logging

from django.utils.dateparse import parse_date
from django.core.mail import EmailMessage
import os
from celery import chain
from accounts.models import LoggingOperation
from adminportal.tasks import send_delivery_assign_email, send_whatsapp_message_delivery_boy, email_sending, send_notification, send_whatsapp_message, send_whatsapp_message_delivery_partner, sms, shop_admin_status_update, shop_admin_status_update_only_one_shop,\
    category_based_shop_admin_status_celery, sub_category_based_shop_admin_status_celery, category_product_list, product_availability_shop_status_change_celery, master_product_edit_celery_task_to_update_shop_availability,\
    update_sku_long_distance_status_check_celery, long_distance_status_check_celery, sku_delete_long_distance_celery, long_distance_master_product_edit
from celery import group
from orders.delhivery import create_delhivery_shipment, calculate_cart_weight, check_pincode




# send email with the map

def EmailSend(subject, message, email_list, fail_silently):
    # change to celery
    # email_data = email_sending.s(subject=subject,
    #                              message=message,
    #                              from_email=settings.EMAIL_HOST_USER,
    #                              recipient_list=[delivery_boy.email],
    #                              fail_silently=False, )

    try:
        logging.info(f"Sending Email to ({email_list}) with subject ({subject})")
        print(f"Sending Email to ({email_list}) with subject ({subject})")
        email_data = email_sending.apply_async(args=[subject, message, settings.EMAIL_HOST_USER, email_list, False],
                                               countdown=5)
        return email_data
    except Exception as e:
        logging.info(f"Email Send Failed for ({subject}): {e} ")




# notification sending function

def NotificationInit(fcm_token, order, title, description, user_uuid):
    try:
        #initialize_fcm_app()
        send_notification.apply_async(args=[fcm_token, order.order_ID, title, description, user_uuid.pk, order.uuid], countdown=5)
        return JsonResponse({"status": 1 , "msg": "notification send successfully"})
    except Exception as e:
        logging.error(f"Notification send Failed on ({description}): {e}")
        return None




# whatsapp message sending function
# not implemented or tested yet
def WhatsappMessage(number,orderID, name, template_name):

    try:
        logging.info("Sending Whatsapp Message to Shipper")
        send_whatsapp_message.apply_async(args=[number, orderID, name, template_name],countdown=5)
    except Exception as e:
        logging.error(f"Send Whatsapp message to Shipper Failed: {e}")
        return None



# generate random password for account login
def generate_random_password(length=8):
    characters = string.ascii_letters + string.digits
    return ''.join(secrets.choice(characters) for _ in range(length))



# admin side login logic
# we have 4 types of login super admin nbc adminn and pu admin and shop admin
def login(request):
    error = {}
    if request.method == "POST":
        email = request.POST.get("username", "")
        password = request.POST.get("password", "")

        # fetch email and password and authenticate

        user = authenticate(request, username=email, password=password)
        if user is not None:
            auth_login(request, user)
            return redirect("homepage")
        else:
            error = {"message": "invalid username or password"}
    return render(request, "login.html", error)


# function to check the user role
# user roles are shop admin, pu admin, nbc admin
def check_user_role(allowed_roles):
    def inner_function(user):
        return user.user_type in allowed_roles

    return inner_function



#get avergae failed orders with number of failed orders and orders today
def get_average_failed_orders(orders_today, failed_order_today):
    if orders_today:
        avg_failed_orders = round((failed_order_today / orders_today) * 100, 2)
    else:
        avg_failed_orders = 0

    return avg_failed_orders



# percent of week revenue with the last week revenue
def get_week_difference_output(week_revenue, week_before_last_week_revenue):
    revenue_difference_output = {}
    if week_revenue['week_revenue'] and week_before_last_week_revenue['last_week_revenue']:
        week_revenue_percent_with_week = round(
            (week_revenue['week_revenue'] / week_before_last_week_revenue['last_week_revenue']) * 100, 2)
        if week_revenue_percent_with_week > 100:
            week_revenue_percent_with_week = week_revenue_percent_with_week - 100
            revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
            revenue_difference_output['flag'] = 'increase'
        else:
            week_revenue_percent_with_week = 100 - week_revenue_percent_with_week
            revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
            revenue_difference_output['flag'] = 'decrease'

    else:
        if week_revenue['week_revenue'] and not week_before_last_week_revenue['last_week_revenue']:
            week_revenue_percent_with_week = 100
            revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
            revenue_difference_output['flag'] = 'increase'
        else:
            week_revenue_percent_with_week = 0
            revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
            revenue_difference_output['flag'] = 'increase'
    return revenue_difference_output


# today's  average with last week revenue
def get_revenue_percent_data(today_revenue, last_week_revenue_avg):
    revenue_percent_data = {}
    if today_revenue['today_revenue'] and last_week_revenue_avg['last_week_avg_revenue']:
        today_revenue_percent_with_week = round((today_revenue['today_revenue'] / last_week_revenue_avg[
            'last_week_avg_revenue']) * 100, 2)
        if today_revenue_percent_with_week > 100:
            today_revenue_percent_with_week = today_revenue_percent_with_week - 100
            revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
            revenue_percent_data['flag'] = 'increase'
        else:
            today_revenue_percent_with_week = 100 - today_revenue_percent_with_week
            revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
            revenue_percent_data['flag'] = 'decrease'

    else:
        if today_revenue['today_revenue'] and not last_week_revenue_avg['last_week_avg_revenue']:
            today_revenue_percent_with_week = 100
            revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
            revenue_percent_data['flag'] = 'increase'
        else:
            today_revenue_percent_with_week = 0
            revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
            revenue_percent_data['flag'] = 'increase'
    return revenue_percent_data

# count average data
# average of orders today with last week count
def get_revenue_count_data(orders_today, last_week_revenue_count):
    revenue_count_data = {}
    if orders_today and last_week_revenue_count:
        today_count_percent_with_week = round((orders_today / last_week_revenue_count), 2) * 100
        if today_count_percent_with_week > 100:
            today_count_percent_with_week = today_count_percent_with_week - 100
            revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
            revenue_count_data['flag'] = 'increase'
        else:
            today_count_percent_with_week = 100 - today_count_percent_with_week
            revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
            revenue_count_data['flag'] = 'decrease'
    else:
        if orders_today and not last_week_revenue_count:
            today_count_percent_with_week = 100
            revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
            revenue_count_data['flag'] = 'increase'
        else:
            today_count_percent_with_week = 0
            revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
            revenue_count_data['flag'] = 'increase'
    return revenue_count_data




# monthly revenue calculation
def get_month_revenue(today, shop_id):
    month_revenue = Orders.objects.filter(created_date__year=today.year,
                                          created_date__month=today.month, store_uuid=shop_id.uuid).exclude(
        order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
        month_revenue=Sum('grand_total'))
    month_revenue['month_revenue'] = round(
        month_revenue['month_revenue'] if month_revenue['month_revenue'] else 0, 2)
    return month_revenue



# shop admin homepage
def shop_admin_homepage(request, today):

    shop_id_verify = None


    shop_id_verify = Shop.objects.prefetch_related('unit_admin_user').filter(
        unit_admin_user__uuid=request.user.uuid)
    if shop_id_verify:

        shop_id = shop_id_verify[0]
        request.session['product_view'] = 'Yes'

        # order today data
        orders_today = Orders.objects.exclude(order_status__in=["Failed", "New Order" , "Cancelled"]).filter(
            created_date__date=today, store_uuid=shop_id.uuid).count()

        # new orders today
        new_order_data = list(Orders.objects.filter(created_date__date=today, order_status='Confirmed',
                                                    store_uuid=shop_id.uuid).values("order_ID", ))
        new_order_today = len(new_order_data)

        # failed orders today

        failed_order_today = Orders.objects.filter(created_date__date=today, order_status="Failed",
                                                   store_uuid=shop_id.uuid).count()

        avg_failed_orders = get_average_failed_orders(orders_today, failed_order_today)


        # delivered orders today

        fulfiled_order = Orders.objects.filter(created_date__date=today, order_status='Delivered',
                                               store_uuid=shop_id.uuid).count()

        # unassigned orders today
        currently_unassigned_order = Orders.objects.filter(created_date__date=today,
                                                           order_status__in=["Confirmed", "Viewed", "Order Packed"],
                                                           store_uuid=shop_id.uuid).count()

        # total orders today and revenue
        total_orders = Orders.objects.filter(store_uuid=shop_id.uuid).exclude(
            order_status__in=["New Order"]).count()
        today_revenue = Orders.objects.filter(created_date__date=today, store_uuid=shop_id.uuid).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
            today_revenue=Sum('grand_total'))
        today_revenue_data = 0
        if today_revenue['today_revenue']:
            today_revenue_data = round(today_revenue['today_revenue'] if today_revenue['today_revenue'] else 0, 2)
        last_week_start = today - timedelta(days=7)
        week_before_last_week = last_week_start - timedelta(days=7)
        week_revenue = Orders.objects.filter(created_date__date__gte=last_week_start,
                                             created_date__date__lte=today, store_uuid=shop_id.uuid).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
            week_revenue=Sum('grand_total'))

        week_before_last_week_revenue = Orders.objects.filter(created_date__date__gte=week_before_last_week,
                                                              created_date__date__lte=last_week_start,
                                                              store_uuid=shop_id.uuid).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
            last_week_revenue=Sum('grand_total'))

        revenue_difference_output = get_week_difference_output(week_revenue, week_before_last_week_revenue)


        last_week_revenue_avg = Orders.objects.filter(store_uuid=shop_id.uuid,
                                                      created_date__date__gte=last_week_start,
                                                      created_date__date__lte=today).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(last_week_avg_revenue=Avg('grand_total'))
        revenue_percent_data = get_revenue_percent_data(today_revenue, last_week_revenue_avg)

        last_week_revenue_count = Orders.objects.filter(store_uuid=shop_id.uuid,
                                                        created_date__date__gte=last_week_start,
                                                        created_date__date__lte=today).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).count()
        revenue_count_data = get_revenue_count_data(orders_today, last_week_revenue_count)

        month_revenue = get_month_revenue(today, shop_id)
        delivery_success_percentage = 0
        if fulfiled_order and orders_today:
            delivery_success_percentage = round((fulfiled_order / orders_today) * 100, 2)
        avg_today_revenue = Orders.objects.filter(store_uuid=shop_id.uuid, created_date__date=today).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
            today_revenue=Avg('grand_total'))
        avg_today_revenue_data = 0
        if avg_today_revenue['today_revenue']:
            avg_today_revenue_data = round(avg_today_revenue['today_revenue'], 2)
        results = Orders.objects.filter(store_uuid=shop_id.uuid, created_date__date=today).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).values('store_uuid__unit_name').annotate(
            total_amount=Sum('grand_total')).order_by(
            '-total_amount')[:5]

        for i in results:
            if i['total_amount']:
                i['total_amount'] = round(i['total_amount'], 2)
        context = {"orders_today": orders_today, 'new_order_today': new_order_today,
                   "fulfiled_order": fulfiled_order,
                   'currently_unassigned_order': currently_unassigned_order,
                   "failed_order": failed_order_today,
                   "avg_failed_order": avg_failed_orders,
                   'total_orders': total_orders, 'today_revenue': today_revenue_data,
                   'month_revenue': month_revenue, "grand_total_by_store": results,
                   'today_count_percent_with_week': revenue_count_data,
                   'today_revenue_percent_with_week': revenue_percent_data,
                   'delivery_success_percentage': delivery_success_percentage,
                   'avg_today_revenue': avg_today_revenue_data,
                   'revenue_difference_output': revenue_difference_output, 'home_active': "active",
                   "new_order_data": new_order_data}
        return context


def nbc_admin_homepage(request, today):


        request.session["product_view"] = 'No'
        orders_today = Orders.objects.exclude(order_status__in=["Failed", "New Order" , "Cancelled"]).filter(
            created_date__date=today).count()
        new_order_data = list(Orders.objects.filter(created_date__date=today,order_status='Confirmed',).values("order_ID", ))
        new_order_today = len(new_order_data)
        failed_order_today = Orders.objects.filter(created_date__date=today, order_status="Failed").count()
        avg_failed_orders = get_average_failed_orders(orders_today, failed_order_today)
        fulfiled_order = Orders.objects.filter(created_date__date=today, order_status='Delivered').count()
        currently_unassigned_order = Orders.objects.filter(order_status__in=["Confirmed", "Viewed", "Order Packed"],
                                                           created_date__date=today).count()
        last_week_start = today - timedelta(days=7)

        week_before_last_week = last_week_start - timedelta(days=7)
        week_revenue = Orders.objects.filter(created_date__date__gte=last_week_start,
                                             created_date__date__lte=today).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
            week_revenue=Sum('grand_total'))
        week_before_last_week_revenue = Orders.objects.filter(created_date__date__gte=week_before_last_week,
                                                              created_date__date__lte=last_week_start).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
            last_week_revenue=Sum('grand_total'))
        revenue_difference_output = {}

        if week_revenue['week_revenue'] and week_before_last_week_revenue['last_week_revenue']:
            week_revenue_percent_with_week = round(
                (week_revenue['week_revenue'] / week_before_last_week_revenue['last_week_revenue']) * 100, 2)
            if week_revenue_percent_with_week > 100:
                week_revenue_percent_with_week = week_revenue_percent_with_week - 100
                revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
                revenue_difference_output['flag'] = 'increase'
            else:
                week_revenue_percent_with_week = 100 - week_revenue_percent_with_week
                revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
                revenue_difference_output['flag'] = 'decrease'

        else:
            if week_revenue['week_revenue'] and not week_before_last_week_revenue['last_week_revenue']:
                week_revenue_percent_with_week = 100
                revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
                revenue_difference_output['flag'] = 'increase'
            else:
                week_revenue_percent_with_week = 0
                revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
                revenue_difference_output['flag'] = 'increase'

        last_week_revenue_avg = Orders.objects.filter(created_date__date__gte=last_week_start,
                                                      created_date__date__lte=today).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
            last_week_avg_revenue=Avg('grand_total'))
        today_revenue = Orders.objects.filter(created_date__date=today).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(today_revenue=Sum('grand_total'))
        today_revenue_data = 0
        if today_revenue['today_revenue']:
            today_revenue_data = round(today_revenue['today_revenue'], 2)
        revenue_percent_data = {}
        if today_revenue['today_revenue'] and last_week_revenue_avg['last_week_avg_revenue']:
            today_revenue_percent_with_week = round(
                (today_revenue['today_revenue'] / last_week_revenue_avg['last_week_avg_revenue']) * 100, 2)
            if today_revenue_percent_with_week > 100:
                today_revenue_percent_with_week = today_revenue_percent_with_week - 100
                revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
                revenue_percent_data['flag'] = 'increase'
            else:
                today_revenue_percent_with_week = 100 - today_revenue_percent_with_week
                revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
                revenue_percent_data['flag'] = 'decrease'

        else:
            if today_revenue['today_revenue'] and not last_week_revenue_avg['last_week_avg_revenue']:
                today_revenue_percent_with_week = 100
                revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
                revenue_percent_data['flag'] = 'increase'
            else:
                today_revenue_percent_with_week = 0
                revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
                revenue_percent_data['flag'] = 'increase'
        last_week_revenue_count = Orders.objects.filter(created_date__date__gte=last_week_start,
                                                        created_date__date__lte=today).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).count()
        revenue_count_data = {}
        if orders_today and last_week_revenue_count:
            today_count_percent_with_week = round((orders_today / last_week_revenue_count) * 100, 2)
            if today_count_percent_with_week > 100:
                today_count_percent_with_week = today_count_percent_with_week - 100
                revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
                revenue_count_data['flag'] = 'increase'
            else:
                today_count_percent_with_week = 100 - today_count_percent_with_week
                revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
                revenue_count_data['flag'] = 'decrease'
        else:
            if orders_today and not last_week_revenue_count:
                today_count_percent_with_week = 100
                revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
                revenue_count_data['flag'] = 'increase'
            else:
                today_count_percent_with_week = 0
                revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
                revenue_count_data['flag'] = 'increase'

        delivery_success_percentage = 0

        if fulfiled_order and orders_today:
            delivery_success_percentage = round((fulfiled_order / orders_today) * 100, 2)
        avg_today_revenue = Orders.objects.filter(created_date__date=today).exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(today_revenue=Avg('grand_total'))

        avg_today_revenue_data = 0
        if avg_today_revenue['today_revenue']:
            avg_today_revenue_data = round(avg_today_revenue['today_revenue'], 2)

        total_orders = Orders.objects.exclude(order_status__in=["New Order"]).count()

        month_revenue = Orders.objects.filter(created_date__year=today.year,
                                              created_date__month=today.month, order_status="Delivered").aggregate(
            month_revenue=Sum('grand_total'))
        month_revenue['month_revenue'] = round(month_revenue['month_revenue'] if month_revenue['month_revenue'] else 0,
                                               2)
        results = Orders.objects.filter(created_date__date=today, order_type="Local Orders").exclude(
            order_status__in=["New Order", "Failed" , "Cancelled"]).values('store_uuid__unit_name').annotate(
            total_amount=Sum('grand_total')).order_by(
            '-total_amount')[:5]
        for i in results:
            if i['total_amount']:
                i['total_amount'] = round(i['total_amount'], 2)
        context = {"orders_today": orders_today, 'new_order_today': new_order_today, "fulfiled_order": fulfiled_order,
                   'currently_unassigned_order': currently_unassigned_order,
                   # 'orders_over_5_hours': orders_over_5_hours,
                   "failed_order": failed_order_today,
                   "avg_failed_orders": avg_failed_orders,
                   'total_orders': total_orders, 'today_revenue': today_revenue_data,
                   'month_revenue': month_revenue, "grand_total_by_store": results,
                   'today_count_percent_with_week': revenue_count_data,
                   'today_revenue_percent_with_week': revenue_percent_data,
                   'delivery_success_percentage': delivery_success_percentage,
                   'avg_today_revenue': avg_today_revenue_data, 'revenue_difference_output': revenue_difference_output,
                   'home_active': "active", "new_order_data": new_order_data}

        return context


def pu_admin_home_page(request, today):
    request.session["product_view"] = 'No'
    orders_today = Orders.objects.exclude(order_status__in=["Failed", "New Order" , "Cancelled"]).filter(
        order_type="Long Distance Orders",
        created_date__date=today).count()

    new_order_data = list(Orders.objects.filter(created_date__date=today, order_status='Confirmed',
                                                order_type="Long Distance Orders").values("order_ID", ))
    new_order_today = len(new_order_data)

    fulfiled_order = Orders.objects.filter(created_date__date=today, order_status='Delivered',
                                           order_type="Long Distance Orders").count()
    failed_order_today = Orders.objects.filter(created_date__date=today, order_status="Failed",
                                               order_type="Long Distance Orders").count()

    avg_failed_orders = get_average_failed_orders(orders_today, failed_order_today)
    currently_unassigned_order = Orders.objects.filter(order_status__in=["Confirmed", "Viewed", "Order Packed"],
                                                       created_date__date=today,
                                                       order_type="Long Distance Orders").count()
    last_week_start = today - timedelta(days=7)

    week_before_last_week = last_week_start - timedelta(days=7)
    week_revenue = Orders.objects.filter(order_type="Long Distance Orders", created_date__date__gte=last_week_start,
                                         created_date__date__lte=today).exclude(
        order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
        week_revenue=Sum('grand_total'))
    week_before_last_week_revenue = Orders.objects.filter(order_type="Long Distance Orders",
                                                          created_date__date__gte=week_before_last_week,
                                                          created_date__date__lte=last_week_start).exclude(
        order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
        last_week_revenue=Sum('grand_total'))
    revenue_difference_output = {}
    if week_revenue['week_revenue'] and week_before_last_week_revenue['last_week_revenue']:
        week_revenue_percent_with_week = round(
            (week_revenue['week_revenue'] / week_before_last_week_revenue['last_week_revenue']) * 100, 2)
        if week_revenue_percent_with_week > 100:
            week_revenue_percent_with_week = week_revenue_percent_with_week - 100
            revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
            revenue_difference_output['flag'] = 'increase'
        else:
            week_revenue_percent_with_week = 100 - week_revenue_percent_with_week
            revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
            revenue_difference_output['flag'] = 'decrease'

    else:
        if week_revenue['week_revenue'] and not week_before_last_week_revenue['last_week_revenue']:
            week_revenue_percent_with_week = 100
            revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
            revenue_difference_output['flag'] = 'increase'
        else:
            week_revenue_percent_with_week = 0
            revenue_difference_output['revenue_difference'] = round(week_revenue_percent_with_week, 2)
            revenue_difference_output['flag'] = 'increase'

    last_week_revenue_avg = Orders.objects.filter(order_type="Long Distance Orders",
                                                  created_date__date__gte=last_week_start,
                                                  created_date__date__lte=today).exclude(
        order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
        last_week_avg_revenue=Avg('grand_total'))
    today_revenue = Orders.objects.filter(order_type="Long Distance Orders", created_date__date=today).exclude(
        order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(today_revenue=Sum('grand_total'))
    today_revenue_data = 0
    if today_revenue['today_revenue']:
        today_revenue_data = round(today_revenue['today_revenue'], 2)
    revenue_percent_data = {}
    if today_revenue['today_revenue'] and last_week_revenue_avg['last_week_avg_revenue']:
        today_revenue_percent_with_week = round((today_revenue['today_revenue'] / last_week_revenue_avg[
            'last_week_avg_revenue']) * 100, 2)
        if today_revenue_percent_with_week > 100:
            today_revenue_percent_with_week = today_revenue_percent_with_week % 100
            revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
            revenue_percent_data['flag'] = 'increase'
        else:
            today_revenue_percent_with_week = 100 % today_revenue_percent_with_week
            revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
            revenue_percent_data['flag'] = 'decrease'

    else:
        if today_revenue['today_revenue'] and not last_week_revenue_avg['last_week_avg_revenue']:
            today_revenue_percent_with_week = 100
            revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
            revenue_percent_data['flag'] = 'increase'
        else:
            today_revenue_percent_with_week = 0
            revenue_percent_data['today_revenue_percent_with_week'] = round(today_revenue_percent_with_week, 2)
            revenue_percent_data['flag'] = 'increase'
    last_week_revenue_count = Orders.objects.filter(order_type="Long Distance Orders",
                                                    created_date__date__gte=last_week_start,
                                                    created_date__date__lte=today, order_status="Delivered").count()
    revenue_count_data = {}
    if orders_today and last_week_revenue_count:
        today_count_percent_with_week = round((orders_today / last_week_revenue_count) * 100, 2)
        if today_count_percent_with_week > 100:
            today_count_percent_with_week = today_count_percent_with_week - 100
            revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
            revenue_count_data['flag'] = 'increase'
        else:
            today_count_percent_with_week = 100 - today_count_percent_with_week
            revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
            revenue_count_data['flag'] = 'decrease'
    else:
        if orders_today and not last_week_revenue_count:
            today_count_percent_with_week = 100
            revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
            revenue_count_data['flag'] = 'increase'
        else:
            today_count_percent_with_week = 0
            revenue_count_data['today_count_percent_with_week'] = round(today_count_percent_with_week, 2)
            revenue_count_data['flag'] = 'increase'

    delivery_success_percentage = 0

    if fulfiled_order and orders_today:
        delivery_success_percentage = round((fulfiled_order / orders_today) * 100, 2)
    avg_today_revenue = Orders.objects.filter(order_type="Long Distance Orders", created_date__date=today).exclude(
        order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(today_revenue=Avg('grand_total'))
    avg_today_revenue_data = 0
    if avg_today_revenue['today_revenue']:
        avg_today_revenue_data = round(avg_today_revenue['today_revenue'], 2)

    total_orders = Orders.objects.filter(order_type="Long Distance Orders").exclude(
        order_status__in=["New Order"]).count()

    month_revenue = Orders.objects.filter(order_type="Long Distance Orders", created_date__year=today.year,
                                          created_date__month=today.month).exclude(
        order_status__in=["New Order", "Failed" , "Cancelled"]).aggregate(
        month_revenue=Sum('grand_total'))
    month_revenue['month_revenue'] = round(month_revenue['month_revenue'] if month_revenue['month_revenue'] else 0,
                                           2)
    results = Orders.objects.filter(created_date__date=today, order_type="Long Distance Orders").exclude(
        order_status__in=["New Order", "Failed" , "Cancelled"]).values('pu_uuid__pu_name').annotate(
        total_amount=Sum('grand_total')).order_by(
        '-total_amount')[:5]
    for i in results:
        if i['total_amount']:
            i['total_amount'] = round(i['total_amount'], 2)
    context = {"orders_today": orders_today, 'new_order_today': new_order_today, "fulfiled_order": fulfiled_order,
               'currently_unassigned_order': currently_unassigned_order,
               "failed_order": failed_order_today,
               "avg_failed_orders": avg_failed_orders,
               'total_orders': total_orders, 'today_revenue': today_revenue_data,
               'month_revenue': month_revenue, "grand_total_by_pu": results,
               'today_count_percent_with_week': revenue_count_data,
               'today_revenue_percent_with_week': revenue_percent_data,
               'delivery_success_percentage': delivery_success_percentage,
               'avg_today_revenue': avg_today_revenue_data, 'revenue_difference_output': revenue_difference_output,
               'home_active': "active", "new_order_data": new_order_data}
    return context



# homepage redirect
# homepage for every roles are handled here
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin", "Shop Admin", "PU Admin"]))
def home_page(request):
    context = {}
    today = timezone.now().date()

    # shop admin homepage context
    if request.user.user_type == "Shop Admin":
        context = shop_admin_homepage(request, today)

    # nbc admin homepage context
    elif request.user.user_type == "NBC Admin" or request.user.user_type == "Super Admin":
        context = nbc_admin_homepage(request, today)

    # pu admin homepage context
    elif request.user.user_type == "PU Admin":
        context = pu_admin_home_page(request, today)

    return render(request, "index.html", context)

#...............................start shop views .................................................#


# shop listing on nbc admin, super admin dashboard
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def shop_list(request):
    shops = Shop.objects.order_by("-created_at")
    paginator = Paginator(shops, 10)
    page = request.GET.get("page")
    try:
        shops = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        shops = paginator.get_page(1)
    except EmptyPage:
        shops = paginator.get_page(paginator.num_pages)
    context = {"shop_data": shops, "sales_unit_active": "active"}
    return render(request, "shop_list.html", context)


# on creating shop assign a default shop status
def shop_status_initialization(shop):
    product_enter_list = []
    # shop status assigning for newly created shop....
    for i in SKU.objects.filter():
        sales_status = SalesUnitProductSelection.objects.filter(sku=i).first()
        if sales_status:
            status = sales_status.status
        else:
            status = i.sku_status

        if i.sku_status == "Visible":
            product_enter_list.append(
                SalesUnitProductSelection(sales_unit=shop, sku=i, status=status, shop_admin_status=status))
        elif i.sku_status == "Disabled":
            product_enter_list.append(
                SalesUnitProductSelection(sales_unit=shop, sku=i, status=status, shop_admin_status=status))
        elif i.sku_status == "Out of Stock":
            product_enter_list.append(
                SalesUnitProductSelection(sales_unit=shop, sku=i, status=status, shop_admin_status=status))
    return product_enter_list

def shop_add_error_page_find(shop_form, bank_form=None):
    """
    Determines which page/step has errors in the multi-step form
    Returns: page index (0 for personal details, 1 for location details)
    """
    shop_error_columns = list(shop_form.errors.keys())
    
    # Define fields for each step
    step1_columns = ["unit_name", "unit_code", "unit_location", "email", "contact_no", 
                     "status", "unit_admin_user", "delivery_mode", "delivery_radius", "gst"]
    step2_columns = ["latitude", "longitude", "street", "city", "state_or_province", 
                     "district", "pin_code"]
    
    # Find which step has errors (using set intersection)
    step1 = set(shop_error_columns) & set(step1_columns)
    step2 = set(shop_error_columns) & set(step2_columns)
    
    # Return the first step that has errors
    page = 0
    if step1:
        page = 0
    elif step2:
        page = 1
    
    return page


# shop adding from shop list page
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def shop_add(request):
    errors = []
    user_modal_form = UserModalForm()
    if request.method == "POST":
        shop_form = ShopForm(request.POST)
        # bank_form = BankDetailsForm(request.POST)
        
        # Debug: Print form data and errors
        print("POST Data:", request.POST)
        print("Form is valid:", shop_form.is_valid())
        print("Form errors:", shop_form.errors)
        
        # saving shop details and bank details with the form
        if shop_form.is_valid():
            shop = shop_form.save()
            # bank_details = bank_form.save(commit=False)
            # bank_details.shop = shop
            # bank_details.save()
            
            # on creating shop assign a default shop status
            product_enter_list = shop_status_initialization(shop)
            SalesUnitProductSelection.objects.bulk_create(product_enter_list)
            
            # sending notification for unit admin users for shop creation
            subject = "New Shop Assignment - Navya Bakers"

            message = f"""Dear User,

            You have been assigned as a Unit Admin for a new shop.

            Shop Name: {shop.unit_name}

            Please log in to the system to manage the shop.

            If you were not expecting this assignment, please contact support.

            Best regards,  
            Navya Bakers Team
            """
            admin_email = "e-commerce@navyabakers.com" 
            for user in shop.unit_admin_user.all(): 
                recipient_list = [admin_email] 
                if user.email: 
                    recipient_list.append(user.email) 
                    send_mail( subject, message, EMAIL_HOST_USER, recipient_list, fail_silently=True )
            
            try:
                log_user = request.user
                log_type = "shop"
                message = "Shop Created"
                description = f"Shop {shop.unit_name} created with code {shop.unit_code}"
                LoggingOperation.objects.create(
                    user=log_user, 
                    log_type=log_type, 
                    message=message,
                    description=description
                )
            except Exception as e:
                print(f"Logging error: {e}")
                pass

            return redirect("shop_list")
        else:
            # Form is not valid, collect errors
            errors.append(shop_form.errors)
            
            # shop creation is in multiple form pages so on error we need to redirect to the error page
            # logic for same
            # Since bank_form is commented out, pass None or update the function
            # Option 1: Pass None for bank_form
            page = shop_add_error_page_find(shop_form, None)
            
            # Option 2: Or determine page based only on shop_form errors
            # page = 0 if shop_form.errors else 1

            context = {
                "errors": errors,
                "shop_form": shop_form,
                # "bank_form": bank_form,
                "user_modal_form": user_modal_form,
                "show_modal": "modal_hide",
                "sales_unit_active": "active",
                "page": page
            }
            return render(request, "shop_add.html", context)
    else:
        # GET request
        shop_form = ShopForm()
        # bank_form = BankDetailsForm()
        errors = []
        
    context = {
        "errors": errors,
        "shop_form": shop_form,
        # "bank_form": bank_form,
        "user_modal_form": user_modal_form,
        "show_modal": "modal_hide",
        "sales_unit_active": "active",
        "page": 0,
    }
    return render(request, "shop_add.html", context)


# shop delete logic
@require_POST
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def shop_delete(request, shop_id):
    shop = Shop.objects.filter(pk=shop_id)
    if shop:
        # if shop exist delete the shop
        # if shop delete then its status information should also delete
        # status information and product assign to a shop is in the Sales unit product selection table
        SalesUnitProductSelection.objects.filter(sales_unit=shop_id).delete()
        shop_data = shop.first()
        shop.delete()
        try:
            try:
                # shop delete status change information
                shop_add_status_change.send(sender="Shop Delete", shop_id=shop_data.uuid)
            except Exception as e:
                logging.error(f"Signal Status Updation on Shop Delete Failed: {e}")

            log_user = request.user
            log_type = "shop"
            message = "Shop Deleted"
            description = f"Shop {shop_data.unit_name}  with code {shop_data.unit_code} deleted"
            LoggingOperation.objects.create(user=log_user, log_type=log_type, message=message, description=description)
        except Exception as e:
            # print(e)
            logging.error(f"Shop Deletion Error: {e}")
            pass
    return redirect("shop_list")


def log(log_user, log_type, message, description):
    try:
        LoggingOperation.objects.create(user=log_user, log_type=log_type, message=message, description=description)
    except:
        pass


# category delete logic...

@require_POST
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def category_delete(request, category_id):
    category = get_object_or_404(ProductCategory, uuid=category_id)
    sub_category = ProductSubCategory.objects.filter(category=category)
    # if sub category exist restrict delete logic and alert user
    if sub_category:
        messages.warning(request, "Couldn't delete subcategories exists")

    elif category:
        category.delete()

        # log details
        log_user = request.user
        log_type = "product"
        message = "Category Deleted"
        description = f"Cagegory {category.category_name}  with code {category.category_code} deleted"
        log(log_user, log_type, message, description)

        messages.success(request, "Category deleted successfully")
    else:
        messages.warning(request, "Something went wrong")
    return redirect("list_category")


def sub_category_delete(request, sub_category_id):
    sub_category = ProductSubCategory.objects.get(uuid=sub_category_id)
    category_id = sub_category.category.uuid
    if sub_category:
        # if sub category exists delete sub category
        sub_category.delete()

        # log details
        try:
            log_user = request.user
            log_type = "product"
            message = "Sub category deleted"
            description = f"Sub category {sub_category.sub_category_name}  with code {sub_category.sub_category_code} deleted for category {sub_category.category.category_name} with code {sub_category.category.category_code}"
            log(log_user, log_type, message, description)
        except:
            pass

    return redirect(f"/adminportal/add-sub-category/{str(category_id)}/")

def send_email_on_status_change(existing_shop_status, shop):
    if existing_shop_status != shop.status:
        for user in shop.unit_admin_user.all():
            subject = "Shop status updated"
            message = f"Your shop {shop.unit_name} changed their status to {shop.status}"
            send_mail(
                subject, message, EMAIL_HOST_USER, [user.email], fail_silently=True
            )

# shop edit
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def shop_edit(request, shop_id=None):
    # fetch shop details
    shop = get_object_or_404(Shop, pk=shop_id)
    existing_shop_status = shop.status
    
    if request.method == "POST":
        shop_form = ShopForm(request.POST, instance=shop)
        print("POST Data:", request.POST)
        
        if shop_form.is_valid():
            shop = shop_form.save()
            
            # if shop status change on editing
            # change the status and send email to unit admin user
            send_email_on_status_change(existing_shop_status, shop)

            # log details
            try:
                log_user = request.user
                log_type = "shop"
                message = "Shop Updated"
                description = f"Shop {shop.unit_name} with code {shop.unit_code} updated"
                log(log_user, log_type, message, description)
            except Exception as e:
                logging.error(f"Shop Updation Error(logging section): {e}")
            
            return redirect("shop_list")
        else:
            # Find which page to show based on form errors
            page = shop_add_error_page_find(shop_form)
            
            context = {
                "shop_form": shop_form, 
                "sales_unit_active": "active", 
                "page": page
            }
            return render(request, "shop_edit.html", context)
    else:
        shop_form = ShopForm(instance=shop)
    
    context = {
        "shop_form": shop_form, 
        "sales_unit_active": "active", 
        "page": 0
    }
    return render(request, "shop_edit.html", context)
#...............................end shop views .................................................#


#..............................start user views .................................................#


# listing users
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def users(request):
    users = Users.objects.exclude(
        user_type__in=["Customer", "Super Admin"]
    ).order_by("-created_date")    
    paginator = Paginator(users, 10)
    page = request.GET.get("page")
    try:
        users = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        users = paginator.get_page(1)
    except EmptyPage:
        users = paginator.get_page(paginator.num_pages)

    context = {"users": users, "users_active": "active"}
    return render(request, "users.html", context)

# edit users
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def user_edit(request, user_id):
    errors = []
    user = get_object_or_404(Users, pk=user_id)
    if request.method == "POST":
        form = UserEditForm(request.POST, instance=user)
        if form.is_valid():
            form.save()
            return redirect("users")

        else:
            errors.append(form.errors)
    else:
        form = UserEditForm(instance=user)
    context = {"errors": errors, "form": form, "public_users_active": "active", "user_id": user_id}
    return render(request, "edit_users.html", context)


# user adding
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def user_add(request):
    errors = []
    if request.method == "POST":
        user_form = UserForm(request.POST)
        if user_form.is_valid():
            first_name = user_form.cleaned_data["first_name"]
            last_name = user_form.cleaned_data["last_name"]
            email = user_form.cleaned_data["email"]
            user_type = user_form.cleaned_data["user_type"]
            phone_number = user_form.cleaned_data["phone_number"]
            status = user_form.cleaned_data["status"]
            user_obj = Users(
                first_name=first_name,
                last_name=last_name,
                email=email,
                user_type=user_type,
                phone_number=phone_number,
                status=status
            )

            # password creation
            my_password = generate_random_password()
            user_obj.set_password(my_password)
            subject = "Welcome to Navya Bakers 🎉"

            message = f"""
            Dear {first_name} {last_name},

            Welcome to Navya Bakers!

            We are pleased to inform you that your account has been successfully created. Please find your login details below:

            Email: {email}
            Password: {my_password}

            Note: For security reasons, please do not share your password with anyone.

            Best regards,  
            Navya Bakers Team
            """
            recepient = str(user_obj.email)
            # send email
            try:
                send_mail(
                    subject, message, EMAIL_HOST_USER, [recepient], fail_silently=False
                )

                user_obj.save()
                return redirect("users")
            except:

                errors = [{'email': ['Please enter valid email']}]

                context = {
                    "errors": errors,
                    "user_form": user_form,
                }
                return render(request, "add_user.html", context)

        else:
            errors.append(user_form.errors)
    else:
        user_form = UserForm()

        errors = []

    context = {
        "errors": errors,
        "user_form": user_form,
    }
    return render(request, "add_user.html", context)


# deleting the user
@require_POST
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def user_delete(request, user_id):
    user = Users.objects.filter(pk=user_id)
    if user:
        user.delete()
    return redirect("users")


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def user_add_modal(request):
    errors = []
    shop_form = ShopForm()
    if request.method == "POST":

        user_modal_form = UserModalForm(request.POST)
        # validating form
        if user_modal_form.is_valid():
            user_obj = user_modal_form.save()

            # generating password
            my_password = generate_random_password()
            user_obj.password = my_password
            first_name = user_obj.first_name
            last_name = user_obj.last_name
            email = user_obj.email

            subject = "Welcome to Navya Bakers 🎉"

            message = f"""
            Dear {first_name} {last_name},

            Welcome to Navya Bakers!

            Your account has been successfully created. Below are your login details:

            Email: {email}
            Password: {my_password}

            For security reasons, we strongly recommend that you change your password after your first login.

            If you did not request this account, please contact our support team immediately.

            Best regards,  
            Navya Bakers Team
            """
            recepient = str(user_obj.email)
            send_mail(
                subject, message, EMAIL_HOST_USER, [recepient], fail_silently=True
            )

            # resetting the form
            user_modal_form = UserModalForm()
            context = {
                "show_modal": "modal_hide",
                "shop_form": shop_form,
                "user_modal_form": user_modal_form,
            }
            return render(request, "shop_add.html", context)
        else:
            errors.append(user_modal_form.errors)
    else:
        user_modal_form = UserModalForm()
        errors = []

    context = {
        "shop_form": shop_form,
        "user_modal_form": user_modal_form,
        "errors": errors,
        "show_modal": "modal_show",
    }
    return render(request, "shop_add.html", context)


# logout from dashboard
@login_required
def admin_logout(request):
    logout(request)
    return redirect('/adminportal/login/')

# resetting password logic
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def reset_password(request, user_id):
    errors = []
    user = get_object_or_404(Users, pk=user_id)

    # generating new password and setting it
    my_password = generate_random_password()
    user.set_password(my_password)
    user.save()

    # send the new password to the user
    subject = "Your Password Has Been Reset - Navya Bakers"
    message = f"""Dear {user.first_name} {user.last_name},

    Your password has been successfully reset.

    Your new login Password are:

    Password: {my_password}

    Note: Please do not share your password with anyone.

    If you did not request this change, please contact our support team immediately.

    Best regards,  
    Navya Bakers Team
    """

    recipient = str(user.email)
    admin_email = "e-commerce@navyabakers.com"
    send_mail(subject, message, EMAIL_HOST_USER, [recipient, admin_email], fail_silently=False)

    # add a notification with django flash
    messages.add_message(request, messages.SUCCESS,
                         'Password Reset Successfully. You will receive a mail with the password.')
    # ressetting the form
    form = UserEditForm(instance=user)
    context = {"errors": errors, "form": form, "public_users_active": "active", "user_id": user_id}
    return render(request, "edit_users.html", context)



#..............................end user views .................................................#



#..............................start product views .................................................#

# category listing
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def category_list(request):


    # fetch category data
    category_data = ProductCategory.objects.order_by("category_name")
    paginator = Paginator(category_data, 10)
    page = request.GET.get("page")
    try:
        category = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        category = paginator.get_page(1)
    except EmptyPage:
        category = paginator.get_page(paginator.num_pages)
    context = {"category_list": category, "category_active": "active"}
    return render(request, "category_list.html", context)


# editing category data
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def category_edit(request, category_id):
    errors = []
    category = get_object_or_404(ProductCategory, pk=category_id)
    sub_category_list = ProductSubCategory.objects.filter(category=category)

    sub_category_form = ProductSubCategoryForm()
    if request.method == "POST":
        form = CategoryForm(request.POST, request.FILES, instance=category)
        if form.is_valid():
            if request.POST.get('image1', ''):
                form.instance.icon = None
            if request.POST.get('image2', ''):
                form.instance.standard_image = None
            if request.POST.get('image3', ''):
                form.instance.banner_image = None
            category = form.save()
            try:
                # logging the operation
                log_user = request.user
                log_type = "product"
                message = "Category updated"
                description = f"Category {category.category_name}  with code {category.category_code} updated"
                log(log_user, log_type, message, description)

            except:
                pass
            return redirect("list_category")

        else:
            errors.append(form.errors)
            context = {"category_form": form, "subcategory_status": True, "category_id": category.uuid,
                       "sub_category_form": sub_category_form, "subcategory_add": True,
                       "sub_category": list(sub_category_list)}
            return render(request, "category_add.html", context)
    else:
        form = CategoryForm(instance=category)
    context = {"category_form": form, "subcategory_status": True, "category_id": category.uuid,
               "sub_category_form": sub_category_form}
    return render(request, "category_add.html", context)


#creating category record
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def add_category(request, category_id=None):
    errors = []
    if request.method == "POST":
        category_form = CategoryForm(request.POST, request.FILES)
        sub_category_form = ProductSubCategoryForm()
        if category_form.is_valid():
            category = category_form.save()
            # logging operations
            try:
                log_user = request.user
                log_type = "product"
                message = "Category created"
                description = f"Category {category.category_name}  with code {category.category_code} created"
                log(log_user, log_type, message, description)
            except:
                pass
            category_instance = get_object_or_404(ProductCategory, pk=category.uuid)
            category_form = CategoryForm(instance=category_instance)
            context = {"category_form": category_form, "subcategory_status": True, "category_id": category.uuid,
                       "sub_category_form": sub_category_form}
            return render(request, "category_add.html", context)
        else:
            errors.append(category_form.errors)
            context = {
                "errors": errors,
                "category_form": category_form,
                "category_id": ""

            }
            return render(request, "category_add.html", context)
    else:
        category_form = CategoryForm()
        errors = []
    context = {
        "errors": errors,
        "category_form": category_form,
        "subcategory_status": False,
        "category_id": ""
    }
    return render(request, "category_add.html", context)


# add subcategory from the same category adding screen
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def add_sub_category(request, category_id):
    errors = []
    # fetch the category
    category = get_object_or_404(ProductCategory, uuid=category_id)
    if request.method == "POST":
        # creating the sub category
        sub_category_form = ProductSubCategoryForm(request.POST)

        # fetch the subcategory associated with the category for listing it in the category listing screen

        sub_category_list = ProductSubCategory.objects.filter(category=category)
        category_form = CategoryForm(instance=category)
        if sub_category_form.is_valid():
            sub_category = sub_category_form.save(commit=False)
            sub_category.category = category
            sub_category.save()

            # logging operation
            try:
                log_user = request.user
                log_type = "product"
                message = "Sub category created"
                description = f"Sub category {sub_category.sub_category_name}  with code {sub_category.sub_category_code} created for category {sub_category.category.category_name} with code {sub_category.category.category_code}"
                log(log_user, log_type, message, description)
            except:
                pass
            sub_category_form = ProductSubCategoryForm()

            context = {"sub_category_form": sub_category_form, "subcategory_status": True,
                       "category_form": category_form, "category_id": category_id, "subcategory_add": True,
                       "sub_category": list(sub_category_list)}
            return render(request, "category_add.html", context)
        else:

            # showing the error in the same viewing page
            errors.append(sub_category_form.errors)
            if sub_category_list:
                context = {
                    "errors": errors,
                    "sub_category_form": sub_category_form,
                    "subcategory_status": True, "category_form": category_form, "category_id": category_id,
                    "subcategory_add": True, "sub_category": list(sub_category_list)

                }
            else:
                context = {
                    "errors": errors,
                    "sub_category_form": sub_category_form,
                    "subcategory_status": True, "category_form": category_form, "category_id": category_id

                }

            return render(request, "category_add.html", context)
    else:
        # viewing the category data
        category_form = CategoryForm(instance=category)
        sub_category_form = ProductSubCategoryForm()
        sub_category_list = ProductSubCategory.objects.filter(category=category)
        errors = []
        context = {"sub_category_form": sub_category_form, "subcategory_status": True, "category_form": category_form,
                   "category_id": category_id, "subcategory_add": True, "sub_category": list(sub_category_list)}

    return render(request, "category_add.html", context)


# editing created sub category data
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def edit_sub_category_row(request, sub_category_id):
    # on clicking on the edit button in sub category list
    # new screen opens up
    # we can edit details from there
    sub_category = get_object_or_404(ProductSubCategory, uuid=sub_category_id)
    category_id = sub_category.category.uuid
    errors = []
    if request.method == "POST":
        form = ProductSubCategoryForm(request.POST, request.FILES, instance=sub_category)
        if form.is_valid():
            form.save()
            try:
                # logging operation
                log_user = request.user
                log_type = "product"
                message = "Sub category updated"
                description = f"Sub category {sub_category.sub_category_name}  with code {sub_category.sub_category_code} updated for category {sub_category.category.category_name} with code {sub_category.category.category_code}"
                log(log_user, log_type, message, description)
            except:
                pass
            return redirect(f"/adminportal/add-sub-category/{str(category_id)}/")

        else:
            errors.append(form.errors)
    else:
        form = ProductSubCategoryForm(instance=sub_category)
    context = {"errors": errors, "sub_category_form": form, "public_users_active": "active",
               "sub_category_id": sub_category_id, "category_id": category_id}
    return render(request, "edit_sub_category.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def master_product_add(request):
    errors = []
    sku_active = False
    details_active = True
    product = None

    if request.method == "POST":
        product_id = request.POST.get('product_id')
        
        if product_id:
            product = get_object_or_404(Products, pk=product_id)
            product_form = ProductForm(request.POST, request.FILES, instance=product)
        else:
            product_form = ProductForm(request.POST, request.FILES)
            
        if product_form.is_valid():
            product = product_form.save()  # Form's save handles ProductDescription
            
            # Handle image uploads (if not already handled in form)
            images = request.FILES.getlist('images')
            for image in images:
                ProductImage.objects.create(product=product, image=image)
            
            videos = request.FILES.getlist('videos')
            for video in videos:
                ProductVideo.objects.create(product=product, video=video)
            
            sku_active = True
            details_active = False
            
            # Logging
            try:
                log_user = request.user
                log_type = "product"
                message = "Master Product Added" if not product_id else "Master Product Updated"
                description = f"Product {product.item_name} with code {product.item_code} added"
                log(log_user, log_type, message, description)
            except:
                pass
            
            return JsonResponse({
                'success': True,
                'sku_active': sku_active,
                'details_active': details_active,
                'product': {'id': product.id}
            })
        else:
            return JsonResponse({'success': False, 'errors': product_form.errors})
    else:
        product_form = ProductForm()
        
    context = {
        "errors": errors,
        "product_form": product_form,
        "category_active": "active",
        "sku_active": sku_active,
        "details_active": details_active,
        "product": product
    }
    
    return render(request, "product_add.html", context)

# adding the sku
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def add_sku(request, product_id):
    """
    Sku is different sub category of product.
    for eg: if we consider a biscuit. Same biscuit have 50g, 60g packet etc......
    We can add sku from submitting a modal from from master product create and edit form
    in master product listing

    :param request:
    :param product_id:
    :return:
    """
    # we will add a sku to a particular product

    # fetch product details
    product = get_object_or_404(Products, pk=product_id)

    if request.method == "POST":

        # add the sku

        # create the sku with  "sku_name", "sku_code", "sku_quantity","sku_unit", "sku_mrp",
        #             "sku_expiry_duration", "sku_bulk_qty_limit", "sku_status"
        form = SKUForm(request.POST)
        if form.is_valid():
            sku = form.save(commit=False)
            # check expiry date if > 30 check availability
            if sku.sku_expiry_duration > 30 and sku.sku_status == "Visible":
                sku.long_distance_availability = True

            sku.product = product
            sku_data = sku.save()

            # if a sku is created we should assign it to every shop initially
            # for each shop there is a shop admin status
            # 3 status visible, disabled, out of stock
            # for default we mark it as visible
            # a sku status is also there

            for i in Shop.objects.all():
                sales_product, _ = SalesUnitProductSelection.objects.get_or_create(
                    sales_unit=i, sku=sku,
                    defaults={'shop_admin_status': sku.sku_status, 'status': sku.sku_status}
                )

                # from the iteration we will get the shop id
                # since visible we assign the shop id to the category, sub category, sku, product
                # fetch the product sku and category and sub category instance from sku assign the shop
                # print(f"SKU: {sku_data}")
                # print(f"SKU Product: {sku.product}")
                if sku.sku_status == "Visible":
                    sku.product.sales_unit.add(i)  # adding shop to product
                    sku.sales_unit.add(i)  # adding shop to sku
                    sku.product.item_category.sales_unit.add(i)  # adding shop to item_category
                    sku.product.item_sub_category.sales_unit.add(i)  # adding shop to sub category

                if sku.long_distance_availability:
                    Products.objects.filter(id=sku.product.id).update(long_distance_availability=True)
                    ProductCategory.objects.filter(uuid=sku.product.item_category.uuid).update(
                        long_distance_availability=True)
                    ProductSubCategory.objects.filter(uuid=sku.product.item_sub_category.uuid).update(
                        long_distance_availability=True)
                # check if already also
                # not needed duplicate skipped
                sku.save()

            # log the operation for future needs
            try:
                log_user = request.user
                log_type = "product"
                message = "SKU Added"
                description = f"SKU {sku.sku_name}  with code {sku.sku_code} added for product {sku.product.item_name}({sku.product.item_code}) "
                log(log_user, log_type, message, description)
            except:
                pass
            return JsonResponse({
                'status': 'success',
                'sku_name': sku.sku_name,  # Assuming your model has these fields
                'sku_code': sku.sku_code,
                'sku_quantity': sku.sku_quantity,
                'sku_unit': sku.sku_unit,
                'sku_mrp': sku.sku_mrp,
                'sku_id': sku.id
            })
        else:
            return JsonResponse({'status': 'error', 'errors': form.errors})
    return JsonResponse({'status': 'error', 'message': 'Invalid request method'})


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def get_skus(request, product_id):
    """
    Used to list the skus in master product add and edit form.
    Master product add : product_add.html,
    Master product edit : product_edit_01.html
    :param request:
    :param product_id:
    :return:
    """
    skus = SKU.objects.filter(product_id=product_id).order_by('created_at')  # Adjust according to your SKU model
    sku_list = [{
        'sku_id': sku.id,  # type: ignore
        'sku_name': sku.sku_name,
        'sku_code': sku.sku_code,
        'sku_quantity': sku.sku_quantity,  # Adjust according to your model
        'sku_unit': sku.sku_unit,  # Adjust according to your model
        'sku_mrp': sku.sku_mrp  # Adjust according to your model
    } for sku in skus]

    return JsonResponse({'status': 'success', 'skus': sku_list})


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def delete_sku(request):
    """
    Delete Sku function used to delete an sku from product edit and product add section.
    product edit : product_edit_01.html,
    product add : product_add.html
    :param request:
    :return:
    """
    if request.method == "POST":
        # get sku id
        sku_id = request.POST.get('sku_id')
        sku = get_object_or_404(SKU, id=sku_id)  # Adjust according to your SKU model

        # print(f"SKU_TO_DELETE: {sku}")

        sku_product_id  = sku.product.id  # getting product from sku
        # print(f"SKU PRODUCT: {sku.product}")
        sku_category_id = sku.product.item_category.uuid  # getting category from sku
        # print(f"SKU CATEGORY : {sku_category}")
        sku_sub_category_id = sku.product.item_sub_category.uuid  # getting sub category from sku
        # print(f"SKU SUB CATEGORY: {sku_sub_category}")

        sku.delete()

        # product exists looking for availablility
        long_distance_status_check_celery.apply_async(args=[sku_product_id,sku_sub_category_id, sku_category_id ], countdown=5)
        sku_delete_long_distance_celery.apply_async(args=[sku_product_id, sku_sub_category_id, sku_category_id], countdown=5)


        # log information
        try:
            log_user = request.user
            log_type = "product"
            message = "SKU Deleted"
            description = f"SKU {sku.sku_name}  with code {sku.sku_code} deleted for product {sku.product.item_name}({sku.product.item_code}) "
            log(log_user, log_type, message, description)
        except:
            pass
        return JsonResponse({'status': 'success'})
    return JsonResponse({'status': 'error', 'message': 'Invalid request'})


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def get_sku(request, sku_id):
    """
    Used to get a sku detail with the sku id.
    """
    try:

        sku = SKU.objects.get(id=sku_id)

        data = {
            'status': 'success',
            'sku': {
                'sku_name': sku.sku_name,
                'sku_code': sku.sku_code,
                'sku_quantity': sku.sku_quantity,
                'sku_unit': sku.sku_unit,
                'sku_mrp': sku.sku_mrp,
                'sku_expiry_duration': sku.sku_expiry_duration,
                'sku_bulk_qty_limit': sku.sku_bulk_qty_limit,
                'same_day_delivery': sku.same_day_delivery,
                'customization_available': sku.customization_available,
                'sku_status': sku.sku_status,
            }
        }
        return JsonResponse(data)
    except SKU.DoesNotExist:
        return JsonResponse({'status': 'error', 'message': 'SKU not found.'})



def sku_update_status_changes(sku_data):
    """
    On sku change we need to update the status to every shop the sku is assigned
    to.
    Based on the sku and shop admin status we are checking the availability of product.
    If shop_admin status is not visible we check for long distance availability based on
    sku status.
    :param sku:
    :param sku_data:
    :return:
    """

    # iterate on each shop
    for i in Shop.objects.all():

        # update or creating  status
        sales_product, _ = SalesUnitProductSelection.objects.get_or_create(
            sales_unit=i, sku=sku_data,
            defaults={'shop_admin_status': sku_data.sku_status, 'status': sku_data.sku_status}
        )

        sales_product.shop_admin_status = sku_data.sku_status
        sales_product.status = sku_data.sku_status
        sales_product.save()
        update_sku_long_distance_status_check_celery.apply_async(args=[sku_data.id], countdown=5)

        # check the current sku status
        if sku_data.sku_status == "Visible":
            for shop_data in Shop.objects.all():
                sales_unit_status_check = SalesUnitProductSelection.objects.filter(sku=sku_data,
                                                                                   sales_unit=shop_data).first()
                if sales_unit_status_check:

                    # check shop admin status

                    if sales_unit_status_check.shop_admin_status == "Visible":

                        # if visible add the shop to the sku.
                        sku_data.product.sales_unit.add(shop_data)  # adding shop to product
                        sku_data.sales_unit.add(shop_data)  # adding shop to sku
                        sku_data.product.item_category.sales_unit.add(shop_data)  # adding shop to item_category
                        sku_data.product.item_sub_category.sales_unit.add(shop_data)  # adding shop to sub category
                    else:
                        sku_data.sales_unit.remove(shop_data)  # removing shop from product

                        shop_admin_status_update.apply_async(args=[sku_data.id], countdown=5)
                else:
                    for shop in Shop.objects.all():
                        sku_data.sales_unit.remove(shop)  # removing shop from product
                    shop_admin_status_update.apply_async(args=[sku_data.id], countdown=5)
        # if sku status not visible
        else:
            for shop_data in Shop.objects.all():
                sku_data.sales_unit.remove(shop_data)  # adding shop to product
            shop_admin_status_update.apply_async(args=[sku_data.id], countdown=5)


# update sku
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def update_sku(request, sku_id):
    """
    Update an sku listed in the master product listing.
    update the sku in edit or add sku page.
    product edit : product_edit_01.html,
    product add : product_add.html

    :param request:
    :param sku_id:
    :return:
    """
    sku = get_object_or_404(SKU, id=sku_id)  # get sku instance

    if request.method == 'POST':
        form = SKUForm(request.POST, instance=sku)  # Bind the form to the SKU instance
        if form.is_valid():
            sku_data = form.save()  # Save the updated SKU

            # if status change on update the sku we need to change the
            # shop status and long distance availability status
            sku_update_status_changes(sku_data)

            try:
                # log operation
                log_user = request.user
                log_type = "product"
                message = "SKU Updated"
                description = f"SKU {sku_data.sku_name}  with code {sku_data.sku_code} updated for product {sku_data.product.item_name}({sku_data.product.item_code}) "
                log(log_user, log_type, message, description)
            except Exception as e:
                # print(e)
                logging.error(f"SKU Updated Error (logging): {e}")

                pass

            return JsonResponse({'status': 'success', 'message': 'SKU updated successfully.'})
        else:
            return JsonResponse({'status': 'error', 'errors': form.errors})

    return JsonResponse({'status': 'error', 'message': 'Invalid request method'})


def update_sku_from_product_list(request, sku_id):
    """
    Sku status update from product listing.
    When we click on the drop down on product listing, sku will list.
    On the sku list we have the option to change the sku status.

    :param request:
    :param sku_id:
    :return:
    """
    sku = get_object_or_404(SKU, id=sku_id)

    if request.method == 'POST':
        status = request.POST.get('sku_status')

        # updating the sku status from here
        if status is not None:
            sku.sku_status = status
            sku.save()

            # if status change on update the sku we need to change the
            # shop status and long distance availability status
            sku_update_status_changes(sku)
            try:
                # log the operation
                log_user = request.user
                log_type = "product"
                message = "SKU Status Updated"
                description = f"SKU {sku.sku_name}  with code {sku.sku_code} updated status to {status} for product {sku.product.item_name}({sku.product.item_code}) "
                log(log_user, log_type, message, description)
            except:
                pass

            return JsonResponse({'status': 'success', 'message': 'SKU status updated successfully.'})
        else:
            return JsonResponse({'status': 'error', 'message': 'No status provided in the request.'})

    return JsonResponse({'status': 'error', 'message': 'Invalid request method. Only POST is allowed.'})



@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def master_product_edit(request, product_id):
    """
        Editing the product from the product listing page.
        In the side bar we have master product section
        On opening the master product we have the option to edit
        the product.
    :param request:
    :param product_id:
    :return:
    """
    product = get_object_or_404(Products, pk=product_id)  # get product using product id
    errors = []
    sku_active = False
    details_active = True

    # for removing the shop list if we change the category and sub category during editing
    existing_category = product.item_category
    existing_sub_category = product.item_sub_category
    to_category = request.POST.get("item_category")
    to_sub_category = request.POST.get("item_sub_category")
    # print(existing_category)
    # print(existing_sub_category)
    # print(to_category)
    # print(to_sub_category)

    if request.method == "POST":
        product_form = ProductForm(request.POST, request.FILES, instance=product)
        if product_form.is_valid():
            product = product_form.save()

            # Handle image uploads
            images = request.FILES.getlist('images')
            for image in images:
                ProductImage.objects.create(product=product, image=image)

            # Handle video uploads
            videos = request.FILES.getlist('videos')
            for video in videos:
                ProductVideo.objects.create(product=product, video=video)

            # Change active tabs
            sku_active = True
            details_active = False

            # before updated

            # after updated

            long_distance_master_product_edit.apply_async(args=[existing_category.uuid,existing_sub_category.uuid,product.id])
            product_availability_shop_status_change_celery.apply_async(args = [existing_sub_category.uuid,existing_category.uuid,to_sub_category, to_category ], countdown=5)

            try:
                log_user = request.user
                log_type = "product"
                message = "Master Product Updated"
                description = f"Product {product.item_name}  with code {product.item_code} updated for category {product.item_category.category_name} ({product.item_category.category_code}) and sub category {product.item_sub_category.sub_category_name} ({product.item_sub_category.sub_category_code}) "
                log(log_user, log_type, message, description)
            except:
                pass

            return JsonResponse({
                'success': True,
                'sku_active': sku_active,
                'details_active': details_active,
                'product': {
                    'id': product.id
                }
            })
        else:
            errors.append(product_form.errors)
            return JsonResponse({'success': False, 'errors': product_form.errors})

    product_form = ProductForm(instance=product)

    context = {
        "errors": errors,
        "product_form": product_form,
        "category_active": "active",
        "sku_active": sku_active,
        "details_active": details_active,
        "product": product,
        "product_id": product_id
    }

    return render(request, "product_edit_01.html", context)

#tomarrow
@login_required
def delete_image(request, image_id):
    """
    deleting product image
    :param request:
    :param image_id:
    :return:
    """
    image = get_object_or_404(ProductImage, id=image_id)
    product_id = image.product.id  # type: ignore
    image.delete()
    # logging the operation
    try:
        log_user = request.user
        log_type = "product"
        message = "Master Product Image deleted"
        description = f"Product {image.product.item_name}  with code {image.product.item_code} deleted its image for category {image.product.item_category.category_name} ({image.product.item_category.category_code}) and sub category {image.product.item_sub_category.sub_category_name} ({image.product.item_sub_category.sub_category_code}) "
        log(log_user, log_type, message, description)
    except:
        pass
    return redirect('edit_master_product', product_id=product_id)


@login_required
def delete_video(request, video_id):
    """
    deleting the product video
    :param request:
    :param video_id:
    :return:
    """
    video = get_object_or_404(ProductVideo, id=video_id)
    product_id = video.product.id  # type: ignore
    video.delete()
    try:
        log_user = request.user
        log_type = "product"
        message = "Master Product Video deleted"
        description = f"Product {video.product.item_name}  with code {video.product.item_code} deleted its video for category {video.product.item_category.category_name} ({video.product.item_category.category_code}) and sub category {video.product.item_sub_category.sub_category_name} ({video.product.item_sub_category.sub_category_code}) "
        log(log_user, log_type, message, description)
    except:
        pass
    return redirect('edit_master_product', product_id=product_id)



def long_distance_master_product_delete(long_dist_sku_sub_category, product_category, product_sub_category):
    """
    long distance product availability check and update the availability
    :param long_dist_sku_sub_category:
    :param product_category:
    :param product_sub_category:
    :return:
    """
    if not long_dist_sku_sub_category:
        ProductSubCategory.objects.filter(uuid=product_sub_category.uuid).update(
            long_distance_availability=False)
    else:

        long_dist_sub_category_flag = False
        for i in long_dist_sku_sub_category:
            long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=i, status="Visible").first()
            if long_dist_sales_check_sub:
                ProductSubCategory.objects.filter(uuid=product_sub_category.uuid).update(
                    long_distance_availability=True)
                long_dist_sub_category_flag = True
                break
        if not long_dist_sub_category_flag:
            ProductSubCategory.objects.filter(uuid=product_sub_category.uuid).update(
                long_distance_availability=False)

    # category exists looking for availablility
    long_dist_sku_category = SKU.objects.filter(product__item_category=product_category,
                                                sku_status="Visible", sku_expiry_duration__gt=30)
    if not long_dist_sku_category:
        ProductCategory.objects.filter(uuid=product_category.uuid).update(
            long_distance_availability=False)
    else:
        long_dist_sku_category_flag = False
        for i in long_dist_sku_category:
            long_dist_sales_check_cat = SalesUnitProductSelection.objects.filter(sku=i, status="Visible").first()
            if long_dist_sales_check_cat:
                ProductCategory.objects.filter(uuid=product_category.uuid).update(
                    long_distance_availability=True)
                long_dist_sku_category_flag = True
                break
        if not long_dist_sku_category_flag:
            ProductCategory.objects.filter(uuid=product_category.uuid).update(
                long_distance_availability=False)
    return


from django.contrib import messages
from django.shortcuts import redirect
import logging

@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def master_product_delete(request, product_id):
    """
    deleting the master product
    """
    product = Products.objects.filter(pk=product_id)
    product_data = product.first()

    if not product_data:
        messages.error(request, "Product not found.")
        return redirect("list_master_product")

    product_category = product_data.item_category
    product_sub_category = product_data.item_sub_category

    # Check if product is used in ads
    ads = Ads.objects.filter(Product=product_id)
    if ads.exists():
        messages.warning(
            request,
            "Your Product is used in ads! Couldn't delete the product."
        )
        return redirect("list_master_product")

    # ✅ DELETE PRODUCT
    product.delete()

    # ✅ SUCCESS MESSAGE (THIS WAS MISSING)
    messages.success(
        request,
        f"Product '{product_data.item_name}' deleted successfully."
    )

    # Check SKU availability after delete
    long_dist_sku_sub_category = SKU.objects.filter(
        product__item_sub_category=product_sub_category,
        sku_status="Visible",
        sku_expiry_duration__gt=30
    )

    long_distance_master_product_delete(
        long_dist_sku_sub_category,
        product_category,
        product_sub_category
    )

    product_availability_shop_status_change_celery.apply_async(
        kwargs={
            "existing_sub_category": product_sub_category.uuid,
            "existing_category": product_category.uuid,
            "to_sub_category": None,
            "to_category": None,
        },
        countdown=5
    )

    # Logging
    try:
        log_user = request.user
        log_type = "product"
        message = "Master Product deleted"
        description = (
            f"Product {product_data.item_name} with code {product_data.item_code} "
            f"deleted for category {product_category.category_name} "
            f"({product_category.category_code}) and sub category "
            f"{product_sub_category.sub_category_name} "
            f"({product_sub_category.sub_category_code})"
        )
        log(log_user, log_type, message, description)
    except Exception as e:
        logging.error(f"Master Product Delete Error(logging): {e}")

    return redirect("list_master_product")



from django.http import JsonResponse




def subcategory_product_list(request):
    """
    this is a function to filter and list the products based on sub category
    we have this drop down menu having the list of sub categories
    if we select one sub category based on that sub category product will list on the below table.
    :param request:
    :return:
    """
    # print("Sub category product list here")
    category_id = request.GET.get('category_id')
    subcategory_id = request.GET.get('subcategory_id')
    shop_id = request.GET.get('shop_id')
    product_type = request.GET.get('product_type')
    # filter the products
    product_list = Products.objects.filter(product_type=product_type).order_by('-created_date')
    if subcategory_id:
        product_list = product_list.filter(item_category=category_id, item_sub_category=subcategory_id)
    else:
        product_list = product_list.filter(item_category=category_id)
    list_data = []
    for i in product_list.filter():
        product_data = {"pk": i.pk, "item_name": i.item_name, "item_code": i.item_code,
                        "category_name": i.item_category.category_name,
                        "sub_category_name": i.item_sub_category.sub_category_name, "sku": []}
        sku_list = []
        for j in i.skus.all():
            sku_data = {"pk": j.pk, "sku_name": j.sku_name, "sku_code": j.sku_code, "sku_quantity": j.sku_quantity,
                        "sku_unit": j.sku_unit, "sku_mrp": j.sku_mrp, "sku_status": j.sku_status}
            try:
                # check whether shop status created if not create one with default status
                sales_product, _ = SalesUnitProductSelection.objects.get_or_create(
                    sales_unit=shop_id, sku=j,
                    defaults={'shop_admin_status': 'Visible', 'status': 'Visible'}
                )
                # add the shop status for easy reference
                if sales_product.shop_admin_status == "Visible":  # remove not included not needed in listing
                    j.sales_unit.add(sales_product.sales_unit)
                    j.product.sales_unit.add(sales_product.sales_unit)
                    j.product.item_category.sales_unit.add(sales_product.sales_unit)
                    j.product.item_sub_category.sales_unit.add(sales_product.sales_unit)
                else:
                    shop_admin_status_update_only_one_shop.apply_async(args=[j.id, sales_product.sales_unit.uuid], countdown=5)


                if sales_product.status == "Visible":
                    if j.sku_expiry_duration > 30:
                        j.long_distance_availability = True
                        j.save()
                        Products.objects.filter(id=j.product.id).update(long_distance_availability=True)
                        ProductCategory.objects.filter(uuid=j.product.item_category.uuid).update(
                            long_distance_availability=True)
                        ProductSubCategory.objects.filter(uuid=j.product.item_sub_category.uuid).update(
                            long_distance_availability=True)
                    else:
                        SKU.objects.filter(id=j.id).update(long_distance_availability=False)
                        # product exists looking for availablility
                        long_dist_sku_product = SKU.objects.filter(product=j.product,
                                                                   sku_status="Visible",
                                                                   sku_expiry_duration__gt=30)
                        if not long_dist_sku_product:
                            Products.objects.filter(id=j.product.id).update(
                                long_distance_availability=False)
                        else:
                            long_dist_product_flag = False
                            for x in long_dist_sku_product:
                                long_dist_sales_check = SalesUnitProductSelection.objects.filter(sku=x, status= "Visible").first()
                                if long_dist_sales_check:
                                    Products.objects.filter(id=j.product.id).update(
                                        long_distance_availability=True)
                                    long_dist_product_flag = True
                                    break
                            if not long_dist_product_flag:
                                Products.objects.filter(id=j.product.id).update(
                                    long_distance_availability=False)

                        # sub category exists looking for availablility
                        long_dist_sku_sub_category = SKU.objects.filter(
                            product__item_sub_category=j.product.item_sub_category,
                            sku_status="Visible", sku_expiry_duration__gt=30)
                        if not long_dist_sku_sub_category:
                            ProductSubCategory.objects.filter(
                                uuid=j.product.item_sub_category.uuid).update(
                                long_distance_availability=False)
                        else:

                            long_dist_sub_category_flag = False
                            for a in long_dist_sku_sub_category:
                                long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=a, status="Visible").first()
                                if long_dist_sales_check_sub:
                                    ProductSubCategory.objects.filter(
                                        uuid=j.product.item_sub_category.uuid).update(
                                        long_distance_availability=True)
                                    long_dist_sub_category_flag = True
                                    break
                            if not long_dist_sub_category_flag:
                                ProductSubCategory.objects.filter(
                                    uuid=j.product.item_sub_category.uuid).update(
                                    long_distance_availability=False)

                        # category exists looking for availablility
                        long_dist_sku_category = SKU.objects.filter(
                            product__item_category=j.product.item_category,
                            sku_status="Visible", sku_expiry_duration__gt=30)
                        if not long_dist_sku_category:
                            ProductCategory.objects.filter(
                                uuid=j.product.item_category.uuid).update(
                                long_distance_availability=False)
                        else:
                            long_dist_sku_category_flag = False
                            for b in long_dist_sku_category:
                                long_dist_sales_check_cat = SalesUnitProductSelection.objects.filter(sku=b, status = "Visible").first()
                                if long_dist_sales_check_cat:
                                    ProductCategory.objects.filter(
                                        uuid=j.product.item_category.uuid).update(
                                        long_distance_availability=True)
                                    long_dist_sku_category_flag = True
                                    break
                            if not long_dist_sku_category_flag:
                                ProductCategory.objects.filter(
                                    uuid=j.product.item_category.uuid).update(
                                    long_distance_availability=False)
                else:
                    SKU.objects.filter(id=j.id).update(long_distance_availability=False)
                    # product exists looking for availablility
                    long_dist_sku_product = SKU.objects.filter(product=j.product,
                                                               sku_status="Visible",
                                                               sku_expiry_duration__gt=30)
                    if not long_dist_sku_product:
                        Products.objects.filter(id=j.product.id).update(
                            long_distance_availability=False)
                    else:
                        long_dist_product_flag = False
                        for c in long_dist_sku_product:
                            long_dist_sales_check = SalesUnitProductSelection.objects.filter(sku=c, status = "Visible").first()
                            if long_dist_sales_check:
                                Products.objects.filter(id=j.product.id).update(
                                    long_distance_availability=True)
                                long_dist_product_flag = True
                                break
                        if not long_dist_product_flag:
                            Products.objects.filter(id=j.product.id).update(
                                long_distance_availability=False)

                    # sub category exists looking for availablility
                    long_dist_sku_sub_category = SKU.objects.filter(
                        product__item_sub_category=j.product.item_sub_category,
                        sku_status="Visible", sku_expiry_duration__gt=30)
                    if not long_dist_sku_sub_category:
                        ProductSubCategory.objects.filter(
                            uuid=j.product.item_sub_category.uuid).update(
                            long_distance_availability=False)
                    else:

                        long_dist_sub_category_flag = False
                        for d in long_dist_sku_sub_category:
                            long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=d, status = "Visible").first()
                            if long_dist_sales_check_sub:
                                ProductSubCategory.objects.filter(
                                    uuid=j.product.item_sub_category.uuid).update(
                                    long_distance_availability=True)
                                long_dist_sub_category_flag = True
                                break
                        if not long_dist_sub_category_flag:
                            ProductSubCategory.objects.filter(
                                uuid=j.product.item_sub_category.uuid).update(
                                long_distance_availability=False)

                    # category exists looking for availablility
                    long_dist_sku_category = SKU.objects.filter(
                        product__item_category=j.product.item_category,
                        sku_status="Visible", sku_expiry_duration__gt=30)
                    if not long_dist_sku_category:
                        ProductCategory.objects.filter(
                            uuid=j.product.item_category.uuid).update(
                            long_distance_availability=False)
                    else:
                        long_dist_sku_category_flag = False
                        for e in long_dist_sku_category:
                            long_dist_sales_check_cat = SalesUnitProductSelection.objects.filter(sku=e, status = "Visible").first()
                            if long_dist_sales_check_cat:
                                ProductCategory.objects.filter(
                                    uuid=j.product.item_category.uuid).update(
                                    long_distance_availability=True)
                                long_dist_sku_category_flag = True
                                break
                        if not long_dist_sku_category_flag:
                            ProductCategory.objects.filter(
                                uuid=j.product.item_category.uuid).update(
                                long_distance_availability=False)

                # assign the status information since it is not available on sku table
                # we list the shop status information on product list page
                # we can change the status also
                sku_data["status"] = sales_product.status
                # j.status = sales_product.status
                # j.shop_admin_status = sales_product.shop_admin_status
                sku_data["shop_admin_status"] = sales_product.shop_admin_status
                # sku_list.append(j)
                sku_list.append(sku_data)
            except:
                # j.status = "Disabled"
                sku_data["status"] = "Disabled"
                sku_data["shop_admin_status"] = "Disabled"
                sku_list.append(sku_data)
        # i.sku = sku_list
        product_data["sku"] = sku_list
        # list_data.append(i)
        list_data.append(product_data)
    paginator = Paginator(list_data, 10)

    products = paginator.get_page(1)  # try doing the above statuses after pagination ............
    # print(products)
    pagination = render_to_string('pagination.html', {'product_data': products})
    if request.user.user_type == 'Shop Admin':
        tbody = render_to_string('product_list_page.html', {"product_data": products})
    else:
        tbody = render_to_string('product_page.html', {"product_data": products})

    return JsonResponse({"tbody": tbody, "pagination": pagination}, safe=False)


# get the list of subcategories from a category
# on selecting the category from drop down a list of sub category loaded on drop down subcategory
# could be more efficient with processing limit to the page

# change that...........................................................
def load_subcategories(request):
    """
    get the list of subcategories from a category
    on selecting the category from drop down a list of sub category loaded on drop down subcategory
    could be more efficient with processing limit to the page

    :param request:
    :return:
    """
    category_id = request.GET.get('category_id')
    shop_id = request.GET.get('shop_id')
    product_type = request.GET.get('product_type')
    product_list = Products.objects.filter(product_type=product_type).order_by('-created_date')
    subcategories = []
    if category_id:
        product_list = product_list.filter(item_category=category_id)
        subcategories = ProductSubCategory.objects.filter(category_id=category_id).values('uuid', 'sub_category_name')

    list_data = []
    for i in product_list.filter():
        sku_list = []
        product_data = {"pk": i.pk, "item_name": i.item_name, "item_code": i.item_code,
                        "category_name": i.item_category.category_name,
                        "sub_category_name": i.item_sub_category.sub_category_name, "sku": []}
        for j in i.skus.all():
            sku_data = {"pk": j.pk, "sku_name": j.sku_name, "sku_code": j.sku_code, "sku_quantity": j.sku_quantity,
                        "sku_unit": j.sku_unit, "sku_mrp": j.sku_mrp, "sku_status": j.sku_status}
            try:
                sales_product, _ = SalesUnitProductSelection.objects.get_or_create(
                    sales_unit=shop_id, sku=j,
                    defaults={'shop_admin_status': 'Visible', 'status': 'Visible'}
                )

                if sales_product.shop_admin_status == "Visible":  # remove not included not needed in listing
                    j.sales_unit.add(sales_product.sales_unit)
                    j.product.sales_unit.add(sales_product.sales_unit)
                    j.product.item_category.sales_unit.add (sales_product.sales_unit)
                    j.product.item_sub_category.sales_unit.add(sales_product.sales_unit)
                else:

                    shop_admin_status_update_only_one_shop.apply_async(args=[j.id, sales_product.sales_unit.uuid], countdown=5)

                if sales_product.status == "Visible":
                    if j.sku_expiry_duration > 30:
                        SKU.objects.filter(id=j.id).update(long_distance_availability=True)
                        Products.objects.filter(id=j.product.id).update(long_distance_availability=True)
                        ProductCategory.objects.filter(uuid=j.product.item_category.uuid).update(
                            long_distance_availability=True)
                        ProductSubCategory.objects.filter(uuid=j.product.item_sub_category.uuid).update(
                            long_distance_availability=True)
                    else:
                        SKU.objects.filter(id=j.id).update(long_distance_availability=False)
                        # product exists looking for availablility
                        long_dist_sku_product = SKU.objects.filter(product=j.product,
                                                                   sku_status="Visible",
                                                                   sku_expiry_duration__gt=30)
                        if not long_dist_sku_product:
                            Products.objects.filter(id=j.product.id).update(
                                long_distance_availability=False)
                        else:
                            long_dist_product_flag = False
                            for r in long_dist_sku_product:
                                long_dist_sales_check = SalesUnitProductSelection.objects.filter(sku=r, status = "Visible").first()
                                if long_dist_sales_check:
                                    Products.objects.filter(id=j.product.id).update(
                                        long_distance_availability=True)
                                    long_dist_product_flag = True
                                    break
                            if not long_dist_product_flag:
                                Products.objects.filter(id=j.product.id).update(
                                    long_distance_availability=False)

                        # sub category exists looking for availablility
                        long_dist_sku_sub_category = SKU.objects.filter(
                            product__item_sub_category=j.product.item_sub_category,
                            sku_status="Visible", sku_expiry_duration__gt=30)
                        if not long_dist_sku_sub_category:
                            ProductSubCategory.objects.filter(
                                uuid=j.product.item_sub_category.uuid).update(
                                long_distance_availability=False)
                        else:

                            long_dist_sub_category_flag = False
                            for n in long_dist_sku_sub_category:
                                long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=n, status = "Visible").first()
                                if long_dist_sales_check_sub:
                                    ProductSubCategory.objects.filter(
                                        uuid=j.product.item_sub_category.uuid).update(
                                        long_distance_availability=True)
                                    long_dist_sub_category_flag = True
                                    break
                            if not long_dist_sub_category_flag:
                                ProductSubCategory.objects.filter(
                                    uuid=j.product.item_sub_category.uuid).update(
                                    long_distance_availability=False)

                        # category exists looking for availablility
                        long_dist_sku_category = SKU.objects.filter(
                            product__item_category=j.product.item_category,
                            sku_status="Visible", sku_expiry_duration__gt=30)
                        if not long_dist_sku_category:
                            ProductCategory.objects.filter(
                                uuid=j.product.item_category.uuid).update(
                                long_distance_availability=False)
                        else:
                            long_dist_sku_category_flag = False
                            for l in long_dist_sku_category:
                                long_dist_sales_check_cat = SalesUnitProductSelection.objects.filter(sku=l, status = "Visible").first()
                                if long_dist_sales_check_cat:
                                    ProductCategory.objects.filter(
                                        uuid=j.product.item_category.uuid).update(
                                        long_distance_availability=True)
                                    long_dist_sku_category_flag = True
                                    break
                            if not long_dist_sku_category_flag:
                                ProductCategory.objects.filter(
                                    uuid=j.product.item_category.uuid).update(
                                    long_distance_availability=False)
                else:
                    SKU.objects.filter(id=j.id).update(long_distance_availability=False)
                    # product exists looking for availablility
                    long_dist_sku_product = SKU.objects.filter(product=j.product,
                                                               sku_status="Visible",
                                                               sku_expiry_duration__gt=30)
                    if not long_dist_sku_product:
                        Products.objects.filter(id=j.product.id).update(
                            long_distance_availability=False)
                    else:
                        long_dist_product_flag = False
                        for g in long_dist_sku_product:
                            long_dist_sales_check = SalesUnitProductSelection.objects.filter(sku=g, status = "Visible").first()
                            if long_dist_sales_check:
                                Products.objects.filter(id=j.product.id).update(
                                    long_distance_availability=True)
                                long_dist_product_flag = True
                                break
                        if not long_dist_product_flag:
                            Products.objects.filter(id=j.product.id).update(
                                long_distance_availability=False)

                    # sub category exists looking for availablility
                    long_dist_sku_sub_category = SKU.objects.filter(
                        product__item_sub_category=j.product.item_sub_category,
                        sku_status="Visible", sku_expiry_duration__gt=30)
                    if not long_dist_sku_sub_category:
                        ProductSubCategory.objects.filter(
                            uuid=j.product.item_sub_category.uuid).update(
                            long_distance_availability=False)
                    else:

                        long_dist_sub_category_flag = False
                        for o in long_dist_sku_sub_category:
                            long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=o, status = "Visible").first()
                            if long_dist_sales_check_sub:
                                ProductSubCategory.objects.filter(
                                    uuid=j.product.item_sub_category.uuid).update(
                                    long_distance_availability=True)
                                long_dist_sub_category_flag = True
                                break
                        if not long_dist_sub_category_flag:
                            ProductSubCategory.objects.filter(
                                uuid=j.product.item_sub_category.uuid).update(
                                long_distance_availability=False)

                    # category exists looking for availablility
                    long_dist_sku_category = SKU.objects.filter(
                        product__item_category=j.product.item_category,
                        sku_status="Visible", sku_expiry_duration__gt=30)
                    if not long_dist_sku_category:
                        ProductCategory.objects.filter(
                            uuid=j.product.item_category.uuid).update(
                            long_distance_availability=False)
                    else:
                        long_dist_sku_category_flag = False
                        for w in long_dist_sku_category:
                            long_dist_sales_check_cat = SalesUnitProductSelection.objects.filter(sku=w, status = "Visible").first()
                            if long_dist_sales_check_cat:
                                ProductCategory.objects.filter(
                                    uuid=j.product.item_category.uuid).update(
                                    long_distance_availability=True)
                                long_dist_sku_category_flag = True
                                break
                        if not long_dist_sku_category_flag:
                            ProductCategory.objects.filter(
                                uuid=j.product.item_category.uuid).update(
                                long_distance_availability=False)

                # j.status = sales_product.status
                sku_data["status"] = sales_product.status
                # j.shop_admin_status = sales_product.shop_admin_status
                sku_data["shop_admin_status"] = sales_product.shop_admin_status
                sku_list.append(j)
            except:
                # j.status = "Disabled"
                sku_data["status"] = "Disabled"
                sku_data["shop_admin_status"] = "Disabled"
                sku_list.append(sku_data)
        # i.sku = sku_list
        product_data["sku"] = sku_list
        # list_data.append(i)
        list_data.append(product_data)
    paginator = Paginator(list_data, 10)
    products = paginator.get_page(1)
    pagination = render_to_string('pagination.html', {'product_data': products})
    if request.user.user_type == 'Shop Admin':
        tbody = render_to_string('product_list_page.html', {"product_data": products})
    else:
        tbody = render_to_string('product_page.html', {"product_data": products})

    if subcategories:
        return JsonResponse({"subcategories": list(subcategories), "tbody": tbody, "pagination": pagination},
                            safe=False)
    else:
        return JsonResponse({"subcategories": [], "tbody": tbody, "pagination": pagination}, safe=False)

    list_data = []
    # for i in list(subcategories):
    #     data = {}
    #     data["id"] = i.uuid
    #     data["name"] = i.sub_category_name
    #     list_data.append(data)


def load_subcategories_product(request):
    category_id = request.GET.get('category_id')
    if category_id:
        subcategories = ProductSubCategory.objects.filter(category_id=category_id).values('uuid', 'sub_category_name')
        return JsonResponse(list(subcategories), safe=False)
    else:
        return JsonResponse([], safe=False)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def master_product_list(request):

    products = Products.objects.order_by("-created_at")
    paginator = Paginator(products, 10)
    page = request.GET.get("page")
    try:
        products = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        products = paginator.get_page(1)
    except EmptyPage:
        products = paginator.get_page(paginator.num_pages)
    context = {"product_data": products, "category_active": "active"}
    return render(request, "product_list.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def master_product_list(request, product_split=False):
    """
    In the product section in side bar. if we click on the master product.
    We will get the list of master products.
    :param request:
    :return:
    """

    # get the filters
    search_query = request.GET.get('search', "")
    product_date = request.GET.get('product_date', "")
    product_status = request.GET.get('product_status', "")

    # fetch the product list
    product_list = Products.objects.filter(product_type="Master Product").order_by('-created_date')


    # apply the filters
    if search_query:
        product_list = Products.objects.filter(
            Q(item_name__icontains=search_query) |
            Q(item_code__icontains=search_query) |
            Q(item_category__category_name__icontains=search_query) |
            Q(item_sub_category__sub_category_name__icontains=search_query)
        ).order_by('-created_date')
    if product_date:
        formatted_date = datetime.strptime(product_date, "%Y-%m-%d")
        product_list = product_list.filter(created_date__date=formatted_date)
    if product_status:
        product_list = product_list.filter(product_status=product_status)

    paginator = Paginator(product_list, 10)

    # get the page number and retrieve the page
    page = request.GET.get('page')

    try:
        products = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        products = paginator.get_page(1)
    except EmptyPage:
        products = paginator.get_page(paginator.num_pages)

    base_url = reverse('list_master_product')
    query_params = request.GET.copy()
    if 'page' in query_params:
        del query_params['page']
    pagination_links = [
        {
            'page_number': page_number,
            'url': f"{base_url}?{query_params.urlencode(safe='/')}&page={page_number}",
        }
        for page_number in products.paginator.page_range
    ]
    context = {
        "product_data": products,
        'search_query': search_query,
        'date': product_date,
        'product_status': product_status,
        'pagination_links': pagination_links,
        "category_active": "active"

    }

    return render(request, "product_list.html", context)



@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin", "Shop Admin"]))
def product_list(request, shop_id=None, product_type="Master Product"):
    """
    get the product list on shop admin product listing.
    we can view the product menu on the side bar.
    :param request:
    :param shop_id:
    :param product_type:
    :return:
    """
    search_query = request.GET.get('search', "")

    # fetch products from the table
    product_list = Products.objects.filter(product_type=product_type).order_by('-created_date')

    # if shop admin we only need the products assigned to the shop
    # get shop id
    if request.user.user_type == 'Shop Admin':
        shop_id = Shop.objects.prefetch_related('unit_admin_user').filter(
            unit_admin_user__uuid=request.user.uuid).first().uuid

    category_list = ProductCategory.objects.all().order_by("-created_at")

    # fetch the shop details

    shop_details = get_object_or_404(Shop, uuid=shop_id)
    shop_name = shop_details.unit_name
    shop_location = shop_details.unit_location

    # filter using search query
    if search_query:
        product_list = Products.objects.filter(product_type=product_type).filter(
            Q(item_name__icontains=search_query) |
            Q(item_code__icontains=search_query) |
            Q(item_category__category_name__icontains=search_query) |
            Q(item_sub_category__sub_category_name__icontains=search_query)
        ).order_by('-created_date')

    category_page = request.GET.get('category_page')

    # filter the products based on category
    if category_page:
        product_list = product_list.filter(item_category=category_page)

    # filter the products using sub category
    sub_category_page = request.GET.get('subcategory_page')
    if sub_category_page:
        product_list = product_list.filter(item_sub_category=sub_category_page)

    # paginator = Paginator(product_list, 10)
    # # print(list_data)
    # page = request.GET.get('page')
    #
    #
    #
    # try:
    #     products = paginator.get_page(page)
    # except (PageNotAnInteger, TypeError):
    #     products = paginator.get_page(1)
    # except EmptyPage:
    #     products = paginator.get_page(paginator.num_pages)

    # shop admin status is not in product table we need to fetch from Sales unit product selection table
    # in the product listing page we have a provision to change the shop admin status for shop admin user
    list_data = []
    for i in product_list:
        product_data = {"pk": i.pk, "item_name": i.item_name, "item_code": i.item_code,
                        "category_name": i.item_category.category_name,
                        "sub_category_name": i.item_sub_category.sub_category_name, "sku": []}
        sku_list = []
        for j in i.skus.all():
            try:
                sku_data = {"pk": j.pk, "sku_name": j.sku_name, "sku_code": j.sku_code, "sku_quantity": j.sku_quantity,
                            "sku_unit": j.sku_unit, "sku_mrp": j.sku_mrp, "sku_status": j.sku_status}
                # sales_product = SalesUnitProductSelection.objects.get(sales_unit=shop_details.uuid, sku=j.id)
                sales_product, _ = SalesUnitProductSelection.objects.get_or_create(
                    sales_unit=shop_details, sku=j,
                    defaults={'shop_admin_status': 'Visible', 'status': 'Visible'}
                )

                if sales_product.shop_admin_status == "Visible":  # remove not included not needed in listing
                    j.sales_unit.add(shop_id)
                    j.product.sales_unit.add(shop_id)
                    j.product.item_category.sales_unit.add(shop_id)
                    j.product.item_sub_category.sales_unit.add(shop_id)
                else:
                    shop_admin_status_update_only_one_shop.apply_async(args=[j.id, sales_product.sales_unit.uuid], countdown=5)


                # print("condition failed")
                if sales_product.status == "Visible":
                    if j.sku_expiry_duration > 30:
                        sku_update = SKU.objects.filter(id=j.id).first()
                        sku_update.long_distance_availability = True
                        sku_update.save()
                        product_update = Products.objects.filter(id=j.product.id).first()
                        product_update.long_distance_availability = True
                        product_update.save()
                        # Products.objects.filter(id=j.product.id).update(long_distance_availability=True)
                        product_category_update = ProductCategory.objects.filter(
                            uuid=j.product.item_category.uuid).first()
                        product_category_update.long_distance_availability = True
                        product_category_update.save()
                        product_sub_category_update = ProductSubCategory.objects.filter(
                            uuid=j.product.item_sub_category.uuid).first()
                        product_sub_category_update.long_distance_availability = True
                        product_sub_category_update.save()
                        # ProductCategory.objects.filter(uuid=j.product.item_category.uuid).update(
                        # long_distance_availability=True)
                        # ProductSubCategory.objects.filter(uuid=j.product.item_sub_category.uuid).update(
                        # long_distance_availability=True)
                    else:
                        SKU.objects.filter(id=j.id).update(long_distance_availability=False)
                        # product exists looking for availablility
                        long_dist_sku_product = SKU.objects.filter(product=j.product,
                                                                   sku_status="Visible",
                                                                   sku_expiry_duration__gt=30)
                        if not long_dist_sku_product:
                            Products.objects.filter(id=j.product.id).update(
                                long_distance_availability=False)
                        else:
                            long_dist_product_flag = False
                            for w in long_dist_sku_product:
                                long_dist_sales_check = SalesUnitProductSelection.objects.filter(sku=w, status = "Visible").first()
                                if long_dist_sales_check:
                                    Products.objects.filter(id=j.product.id).update(
                                        long_distance_availability=True)
                                    long_dist_product_flag = True
                                    break
                            if not long_dist_product_flag:
                                Products.objects.filter(id=j.product.id).update(
                                    long_distance_availability=False)

                        # sub category exists looking for availablility
                        long_dist_sku_sub_category = SKU.objects.filter(
                            product__item_sub_category=j.product.item_sub_category,
                            sku_status="Visible", sku_expiry_duration__gt=30)
                        if not long_dist_sku_sub_category:
                            ProductSubCategory.objects.filter(
                                uuid=j.product.item_sub_category.uuid).update(
                                long_distance_availability=False)
                        else:

                            long_dist_sub_category_flag = False
                            for v in long_dist_sku_sub_category:
                                long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=v, status ="Visible").first()
                                if long_dist_sales_check_sub:
                                    ProductSubCategory.objects.filter(
                                        uuid=j.product.item_sub_category.uuid).update(
                                        long_distance_availability=True)
                                    long_dist_sub_category_flag = True
                                    break
                            if not long_dist_sub_category_flag:
                                ProductSubCategory.objects.filter(
                                    uuid=j.product.item_sub_category.uuid).update(
                                    long_distance_availability=False)

                        # category exists looking for availablility
                        long_dist_sku_category = SKU.objects.filter(
                            product__item_category=j.product.item_category,
                            sku_status="Visible", sku_expiry_duration__gt=30)
                        if not long_dist_sku_category:
                            ProductCategory.objects.filter(
                                uuid=j.product.item_category.uuid).update(
                                long_distance_availability=False)
                        else:
                            long_dist_sku_category_flag = False
                            for r in long_dist_sku_category:
                                long_dist_sales_check_cat = SalesUnitProductSelection.objects.filter(sku=r, status = "Visible").first()
                                if long_dist_sales_check_cat:
                                    ProductCategory.objects.filter(
                                        uuid=j.product.item_category.uuid).update(
                                        long_distance_availability=True)
                                    long_dist_sku_category_flag = True
                                    break
                            if not long_dist_sku_category_flag:
                                ProductCategory.objects.filter(
                                    uuid=j.product.item_category.uuid).update(
                                    long_distance_availability=False)
                else:

                    SKU.objects.filter(id=j.id).update(long_distance_availability=False)
                    # product exists looking for availablility
                    long_dist_sku_product = SKU.objects.filter(product=j.product,
                                                               sku_status="Visible",
                                                               sku_expiry_duration__gt=30)
                    if not long_dist_sku_product:
                        Products.objects.filter(id=j.product.id).update(
                            long_distance_availability=False)
                    else:
                        long_dist_product_flag = False
                        for s in long_dist_sku_product:
                            long_dist_sales_check = SalesUnitProductSelection.objects.filter(sku=s, status = "Visible").first()
                            if long_dist_sales_check:
                                Products.objects.filter(id=j.product.id).update(
                                    long_distance_availability=True)
                                long_dist_product_flag = True
                                break
                        if not long_dist_product_flag:
                            Products.objects.filter(id=j.product.id).update(
                                long_distance_availability=False)

                    # sub category exists looking for availablility
                    long_dist_sku_sub_category = SKU.objects.filter(
                        product__item_sub_category=j.product.item_sub_category,
                        sku_status="Visible", sku_expiry_duration__gt=30)
                    if not long_dist_sku_sub_category:
                        ProductSubCategory.objects.filter(
                            uuid=j.product.item_sub_category.uuid).update(
                            long_distance_availability=False)
                    else:

                        long_dist_sub_category_flag = False
                        for t in long_dist_sku_sub_category:
                            long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=t, status ="Visible").first()
                            if long_dist_sales_check_sub:
                                ProductSubCategory.objects.filter(
                                    uuid=j.product.item_sub_category.uuid).update(
                                    long_distance_availability=True)
                                long_dist_sub_category_flag = True
                                break
                        if not long_dist_sub_category_flag:
                            ProductSubCategory.objects.filter(
                                uuid=j.product.item_sub_category.uuid).update(
                                long_distance_availability=False)

                    # category exists looking for availablility
                    long_dist_sku_category = SKU.objects.filter(
                        product__item_category=j.product.item_category,
                        sku_status="Visible", sku_expiry_duration__gt=30)
                    if not long_dist_sku_category:
                        ProductCategory.objects.filter(
                            uuid=j.product.item_category.uuid).update(
                            long_distance_availability=False)
                    else:
                        long_dist_sku_category_flag = False
                        for q in long_dist_sku_category:
                            long_dist_sales_check_cat = SalesUnitProductSelection.objects.filter(sku=q, status = "Visible").first()
                            if long_dist_sales_check_cat:
                                ProductCategory.objects.filter(
                                    uuid=j.product.item_category.uuid).update(
                                    long_distance_availability=True)
                                long_dist_sku_category_flag = True
                                break
                        if not long_dist_sku_category_flag:
                            ProductCategory.objects.filter(
                                uuid=j.product.item_category.uuid).update(
                                long_distance_availability=False)

                # j.status = sales_product.status
                sku_data["status"] = sales_product.status
                # j.shop_admin_status = sales_product.shop_admin_status
                sku_data["shop_admin_status"] = sales_product.shop_admin_status
                sku_list.append(sku_data)

                # list_data.append(i)
            except Exception as e:
                logging.error(f"SKU Status Issue(display as disabled): {e}")
                # print(e)
                # print("Exception ")
                # j.status = "Disabled"
                sku_data["status"] = "Disabled"
                sku_data["shop_admin_status"] = "Disabled"
                # sku_list.append(j)
                sku_list.append(sku_data)
        # print(sku_list)
        # i.sku = sku_list
        product_data["sku"] = sku_list
        list_data.append(product_data)
        # print(list_data)

    paginator = Paginator(list_data, 10)
    # print(list_data)
    page = request.GET.get('page')

    try:
        products = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        products = paginator.get_page(1)
    except EmptyPage:
        products = paginator.get_page(paginator.num_pages)

    base_url = reverse('list_shop_product', kwargs={'shop_id': shop_id, 'product_type': 'Master Product'})
    query_params = request.GET.copy()
    if 'page' in query_params:
        del query_params['page']
    pagination_links = [
        {
            'page_number': page_number,
            'url': f"{base_url}?{query_params.urlencode(safe='/')}&page={page_number}",
        }
        for page_number in products.paginator.page_range
    ]

    context = {
        "product_data": products,
        'search_query': search_query,
        'pagination_links': pagination_links,
        'product_type': product_type,
        "sales_unit_active": "active",
        "shop_id": shop_id,
        "shop_name": shop_name,
        "shop_location": shop_location,
        "category_list": category_list

    }
    if request.user.user_type == 'Shop Admin':
        if request.headers.get('x-requested-with') == 'XMLHttpRequest':
            tbody = render_to_string('product_list_page.html',
                                     {"product_data": products, 'pagination_links': pagination_links})
            pagination = render_to_string('pagination.html', {'product_data': products})
            return JsonResponse({'tbody': tbody, "pagination": pagination})
        return render(request, "product_list_shop_admin.html", context)
    if request.headers.get('x-requested-with') == 'XMLHttpRequest':
        tbody = render_to_string('product_page.html', {"product_data": products, 'pagination_links': pagination_links})
        pagination = render_to_string('pagination.html', {'product_data': products})
        return JsonResponse({'tbody': tbody, "pagination": pagination})
    # print(products)
    # print("product_list_shop.html")
    return render(request, "product_list_shop.html", context)


# it is the status which can be changed with the admin dashboard not shop admin
# we have the status details in the salesproductselectiontable
# in that table we have a field status
# with this view we can change that status
# with this change of status shop admin status also change to the new status.

@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin", "Shop Admin"]))
def update_product_sales_status(request):
    """
    it is the status which can be changed with the admin dashboard not shop admin
    we have the status details in the sales product selection table
    in that table we have a field status
    with this view we can change that status
    with this change of status shop admin status also change to the new status.

    :param request:
    :return:
    """
    # print("Logging : Updating Product status on Admin")
    if request.method == 'POST':
        sku_id = request.POST.get('sku_id')
        shop_id = request.POST.get('shop_id')
        new_status = request.POST.get('status')

        try:
            sales_unit_product = SalesUnitProductSelection.objects.get(sales_unit=shop_id, sku=sku_id)
        except SalesUnitProductSelection.DoesNotExist:
            # if not found record
            return JsonResponse({'success': False, 'error': 'Operation not allowed.'})
        try:
            # assigning the new status
            sales_unit_product.status = new_status
            sales_unit_product.shop_admin_status = new_status
            sales_unit_product.save()

            # if visible add the shop availablility
            if new_status == "Visible" and sales_unit_product.sku.sku_expiry_duration > 30:
                SKU.objects.filter(id=sku_id).update(long_distance_availability=True)
                Products.objects.filter(id=sales_unit_product.sku.product.id).update(long_distance_availability=True)
                ProductCategory.objects.filter(uuid=sales_unit_product.sku.product.item_category.uuid).update(
                    long_distance_availability=True)
                ProductSubCategory.objects.filter(uuid=sales_unit_product.sku.product.item_sub_category.uuid).update(
                    long_distance_availability=True)
            else:
                # print("Logging: Status updating to not visible statuses")
                SKU.objects.filter(id=sku_id).update(long_distance_availability=False)
                # product exists looking for availablility
                long_dist_sku_product = SKU.objects.filter(product=sales_unit_product.sku.product, sku_status="Visible",
                                                           sku_expiry_duration__gt=30)
                if not long_dist_sku_product:
                    Products.objects.filter(id=sales_unit_product.sku.product.id).update(
                        long_distance_availability=False)
                else:
                    long_dist_product_flag = False
                    for i in long_dist_sku_product:
                        long_dist_sales_check = SalesUnitProductSelection.objects.filter(sku=i, status = "Visible").first()
                        if long_dist_sales_check:
                            Products.objects.filter(id=sales_unit_product.sku.product.id).update(
                                long_distance_availability=True)
                            long_dist_product_flag = True
                            break
                    if not long_dist_product_flag:
                        Products.objects.filter(id=sales_unit_product.sku.product.id).update(
                            long_distance_availability=False)

                # sub category exists looking for availablility
                long_dist_sku_sub_category = SKU.objects.filter(
                    product__item_sub_category=sales_unit_product.sku.product.item_sub_category,
                    sku_status="Visible", sku_expiry_duration__gt=30)
                if not long_dist_sku_sub_category:
                    ProductSubCategory.objects.filter(
                        uuid=sales_unit_product.sku.product.item_sub_category.uuid).update(
                        long_distance_availability=False)
                else:

                    long_dist_sub_category_flag = False
                    for i in long_dist_sku_sub_category:
                        long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=i, status = "Visible").first()
                        if long_dist_sales_check_sub:
                            ProductSubCategory.objects.filter(
                                uuid=sales_unit_product.sku.product.item_sub_category.uuid).update(
                                long_distance_availability=True)
                            long_dist_sub_category_flag = True
                            break
                    if not long_dist_sub_category_flag:
                        ProductSubCategory.objects.filter(
                            uuid=sales_unit_product.sku.product.item_sub_category.uuid).update(
                            long_distance_availability=False)

                # category exists looking for availablility
                long_dist_sku_category = SKU.objects.filter(
                    product__item_category=sales_unit_product.sku.product.item_category,
                    sku_status="Visible", sku_expiry_duration__gt=30)
                if not long_dist_sku_category:
                    ProductCategory.objects.filter(uuid=sales_unit_product.sku.product.item_category.uuid).update(
                        long_distance_availability=False)
                else:
                    long_dist_sku_category_flag = False
                    for i in long_dist_sku_category:
                        long_dist_sales_check_cat = SalesUnitProductSelection.objects.filter(sku=i, status = "Visible").first()
                        if long_dist_sales_check_cat:
                            ProductCategory.objects.filter(
                                uuid=sales_unit_product.sku.product.item_category.uuid).update(
                                long_distance_availability=True)
                            long_dist_sku_category_flag = True
                            break
                    if not long_dist_sku_category_flag:
                        ProductCategory.objects.filter(uuid=sales_unit_product.sku.product.item_category.uuid).update(
                            long_distance_availability=False)

            if new_status == "Visible":
                sales_unit_product.sku.sales_unit.add(sales_unit_product.sales_unit)
                sales_unit_product.sku.product.sales_unit.add(sales_unit_product.sales_unit)
                sales_unit_product.sku.product.item_category.sales_unit.add(sales_unit_product.sales_unit)
                sales_unit_product.sku.product.item_sub_category.sales_unit.add(sales_unit_product.sales_unit)

                # else remove shop availablility
            else:

                sales_unit_product.sku.sales_unit.remove(sales_unit_product.sales_unit)

                # check sku exists for the product if any don't remove the shop from product
                shop_admin_status_update_only_one_shop.apply_async(args=[sales_unit_product.sku.id, sales_unit_product.sales_unit.uuid], countdown=5)


            try:
                log_user = request.user
                log_type = "product"
                message = f"Product Status Updated for Shop {sales_unit_product.sales_unit.unit_name}({sales_unit_product.sales_unit.unit_code})"
                description = f"SKU {sales_unit_product.sku.sku_name}  with code {sales_unit_product.sku.sku_code} updated its status to {new_status} for product {sales_unit_product.sku.product.item_name} ({sales_unit_product.sku.product.item_code} "
                LoggingOperation.objects.create(user=log_user, log_type=log_type, message=message,
                                                description=description)
            except:
                pass
            return JsonResponse({'status': 'success'})
        except:
            return JsonResponse({'error': 'Invalid request.'})


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def category_based_update_status(request, shop_id):
    """

    :param request:
    :param shop_id:
    :return:
    """
    category_id = request.POST.get('category')
    sub_category_id = request.POST.get('subcategory')
    new_status = request.POST.get('status_category_select')
    shop = Shop.objects.filter(uuid=shop_id).first()
    product_list = Products.objects.order_by('-created_date')
    if category_id:
        product_list = product_list.filter(item_category__uuid=category_id)
    if sub_category_id:
        product_list = product_list.filter(item_sub_category__uuid=sub_category_id)
    product_selection = SalesUnitProductSelection.objects.filter(sales_unit__uuid=shop_id)
    update_list = []
    show_list = []
    for i in product_list:
        product_data = {"pk": i.pk, "item_name": i.item_name, "item_code": i.item_code,
                        "category_name": i.item_category.category_name,
                        "sub_category_name": i.item_sub_category.sub_category_name, "sku": []}
        sku_list = []
        for j in i.skus.all():
            sku_data = {"pk": j.pk, "sku_name": j.sku_name, "sku_code": j.sku_code, "sku_quantity": j.sku_quantity,
                        "sku_unit": j.sku_unit, "sku_mrp": j.sku_mrp, "sku_status": j.sku_status}
            if j.sku_status == "Visible":
                check = product_selection.filter(sku=j).first()
                check.status = new_status
                check.shop_admin_status = new_status
                # check.save()

                sku_data["status"] = new_status
                sku_data["shop_admin_status"] = new_status
                # j.status = new_status
                # j.shop_admin_status = new_status
                update_list.append(check)
                # sku_list.append(j)
                sku_list.append(sku_data)
                if new_status == "Visible":
                    j.sales_unit.add(shop)
                    if j.sku_expiry_duration > 30:
                        j.long_distance_availability = True
                    else:
                        j.long_distance_availability = False
                else:
                    j.sales_unit.remove(shop)
                    j.long_distance_availability = False

            else:
                check = product_selection.filter(sku=j).first()
                # j.status = check.status
                sku_data["status"] = check.status
                if sku_data["status"] == "Visible" and j.sku_expiry_duration > 30:
                    j.long_distance_availability = True
                else:
                    j.long_distance_availability = False

                # j.shop_admin_status = check.shop_admin_status
                sku_data["shop_admin_status"] = check.shop_admin_status
                update_list.append(check)
                # sku_list.append(j)
                sku_list.append(sku_data)
            j.save()
        # i.sku = sku_list
        product_data["sku"] = sku_list
        # show_list.append(i)
        show_list.append(product_data)

    SalesUnitProductSelection.objects.bulk_update(update_list, ['status', 'shop_admin_status'])

    # check sku exists for the product if any don't remove the shop from product

    # print(f"SKU EXIST FOR PRODUCT AFTER DELETION: {sku_data}")

    # check sku exists for the sub category if any don't remove the shop from sub category

    # print(f"SKU EXIST FOR SUB CATEGORY AFTER DELETION: {sku_sub_category_data}")

    # check sku exists for the category if any don't remove the shop from sub category

    if category_id:

        sku_category_data = SKU.objects.filter(product__item_category__uuid=category_id,
                                               sku_status="Visible")

        long_dist_sku_category = sku_category_data.filter(sku_expiry_duration__gt=30)
        if not long_dist_sku_category:
            ProductCategory.objects.filter(uuid=category_id).update(
                long_distance_availability=False)
        else:
            long_dist_sku_category_flag = False
            for z in long_dist_sku_category:
                long_dist_sales_check_cat = SalesUnitProductSelection.objects.filter(sku=z, status = "Visible").first()
                if long_dist_sales_check_cat:
                    ProductCategory.objects.filter(uuid=category_id).update(
                        long_distance_availability=True)
                    long_dist_sku_category_flag = True
                    break
            if not long_dist_sku_category_flag:
                ProductCategory.objects.filter(uuid=category_id).update(
                    long_distance_availability=False)

        if not sku_category_data:
            update_status = ProductCategory.objects.filter(uuid=category_id).first()
            if update_status:
                update_status.sales_unit.remove(shop)
        else:
            category_remove_flag = True
            # print(f"Iterating category skus : {sku_category_data}")
            for l in sku_category_data:
                sku_category_sales = SalesUnitProductSelection.objects.filter(sku=l, sales_unit=shop).first()
                # print(f"category  skus Shop Status for {sku_category_sales}")
                if sku_category_sales:
                    # print("category skus Shop status exists")
                    if sku_category_sales.shop_admin_status == "Visible":
                        # print(f"{sku_category_sales}: Visible")
                        category_remove_flag = False
                        break
            if category_remove_flag:
                # print("No visible status available removed")
                update_status = ProductCategory.objects.filter(uuid=category_id).first()
                if update_status:
                    update_status.sales_unit.remove(shop)
            else:
                update_status = ProductCategory.objects.filter(uuid=category_id).first()
                if update_status:
                    update_status.sales_unit.add(shop)
    else:

        for cat in ProductCategory.objects.all():
            sku_category_data = SKU.objects.filter(product__item_category=cat,
                                                   sku_status="Visible")

            long_dist_sku_category = sku_category_data.filter(sku_expiry_duration__gt=30)
            if not long_dist_sku_category:
                cat.long_distance_availability = False
                cat.save()
            else:
                long_dist_sku_category_flag = False
                for y in long_dist_sku_category:
                    long_dist_sales_check_cat = SalesUnitProductSelection.objects.filter(sku=y, status = "Visible").first()
                    if long_dist_sales_check_cat:
                        cat.long_distance_availability = True
                        cat.save()
                        long_dist_sku_category_flag = True
                        break
                if not long_dist_sku_category_flag:
                    cat.long_distance_availability = False
                    cat.save()

            if not sku_category_data:
                cat.sales_unit.remove(shop)
            else:
                category_remove_flag = True
                # print(f"Iterating category skus : {sku_category_data}")
                for t in sku_category_data:
                    sku_category_sales = SalesUnitProductSelection.objects.filter(sku=t, sales_unit=shop).first()
                    # print(f"category  skus Shop Status for {sku_category_sales}")
                    if sku_category_sales:
                        # print("category skus Shop status exists")
                        if sku_category_sales.shop_admin_status == "Visible":
                            # print(f"{sku_category_sales}: Visible")
                            category_remove_flag = False
                            break
                if category_remove_flag:
                    # print("No visible status available removed")
                    cat.sales_unit.remove(shop)
                else:
                    cat.sales_unit.add(shop)

    if sub_category_id:
        sku_sub_category_data = SKU.objects.filter(
            product__item_sub_category__uuid=sub_category_id,
            sku_status="Visible")

        long_dist_sku_sub_category = sku_sub_category_data.filter(sku_expiry_duration__gt=30)
        if not long_dist_sku_sub_category:
            ProductSubCategory.objects.filter(uuid=sub_category_id).update(
                long_distance_availability=False)
        else:

            long_dist_sub_category_flag = False
            for u in long_dist_sku_sub_category:
                long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=u, status = "Visible").first()
                if long_dist_sales_check_sub:
                    ProductSubCategory.objects.filter(
                        uuid=sub_category_id).update(
                        long_distance_availability=True)
                    long_dist_sub_category_flag = True
                    break
            if not long_dist_sub_category_flag:
                ProductSubCategory.objects.filter(uuid=sub_category_id).update(
                    long_distance_availability=False)

        # remove shop from the sub category list
        if not sku_sub_category_data:
            update_status = ProductSubCategory.objects.filter(uuid=sub_category_id).first()
            if update_status:
                update_status.sales_unit.remove(shop)
        else:
            sub_category_remove_flag = True
            # print(f"Iterating on Sub category skus : {sku_sub_category_data}")
            for v in sku_sub_category_data:
                sku_sub_category_sales = SalesUnitProductSelection.objects.filter(sku=v,
                                                                                  sales_unit=shop).first()
                # print(f"sub category  skus Shop Status for {sku_sub_category_sales}")
                if sku_sub_category_sales:
                    # print("sub category skus Shop status exists")
                    if sku_sub_category_sales.shop_admin_status == "Visible":
                        # print(f"{sku_sub_category_sales}: Visible")
                        sub_category_remove_flag = False
                        break
            if sub_category_remove_flag:
                # print("No visible status available removed")
                update_status = ProductSubCategory.objects.filter(uuid=sub_category_id).first()
                if update_status:
                    update_status.sales_unit.remove(shop)
            else:
                update_status = ProductSubCategory.objects.filter(uuid=sub_category_id).first()
                if update_status:
                    update_status.sales_unit.add(shop)

    else:
        if category_id:
            for sub_cat in ProductSubCategory.objects.filter(category__uuid=category_id):

                sku_sub_category_data = SKU.objects.filter(product__item_sub_category=sub_cat,
                                                           sku_status="Visible")

                long_dist_sku_sub_category = sku_sub_category_data.filter(sku_expiry_duration__gt=30)
                if not long_dist_sku_sub_category:
                    sub_cat.long_distance_availability = False
                    sub_cat.save()
                else:

                    long_dist_sub_category_flag = False
                    for g in long_dist_sku_sub_category:
                        long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=g).first()
                        if long_dist_sales_check_sub:
                            if long_dist_sales_check_sub.status == "Visible":
                                sub_cat.long_distance_availability = True
                                sub_cat.save()
                                long_dist_sub_category_flag = True
                                break
                    if not long_dist_sub_category_flag:
                        sub_cat.long_distance_availability = False
                        sub_cat.save()

                # remove shop from the sub category list
                if not sku_sub_category_data:
                    sub_cat.sales_unit.remove(shop)
                else:
                    sub_category_remove_flag = True
                    # print(f"Iterating on Sub category skus : {sku_sub_category_data}")
                    for h in sku_sub_category_data:
                        sku_sub_category_sales = SalesUnitProductSelection.objects.filter(sku=h,
                                                                                          sales_unit=shop).first()
                        # print(f"sub category  skus Shop Status for {sku_sub_category_sales}")
                        if sku_sub_category_sales:
                            # print("sub category skus Shop status exists")
                            if sku_sub_category_sales.shop_admin_status == "Visible":
                                # print(f"{sku_sub_category_sales}: Visible")
                                sub_category_remove_flag = False
                                break
                    if sub_category_remove_flag:
                        sub_cat.sales_unit.remove(shop)
                    else:
                        sub_cat.sales_unit.add(shop)
        else:
            for sub_cat in ProductSubCategory.objects.all():
                sku_sub_category_data = SKU.objects.filter(product__item_sub_category=sub_cat,
                                                           sku_status="Visible")
                long_dist_sku_sub_category = sku_sub_category_data.filter(sku_expiry_duration__gt=30)

                if not long_dist_sku_sub_category:
                    sub_cat.long_distance_availability = False
                    sub_cat.save()
                else:

                    long_dist_sub_category_flag = False
                    for o in long_dist_sku_sub_category:
                        long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=o, status = "Visible").first()
                        if long_dist_sales_check_sub:
                            sub_cat.long_distance_availability = True
                            sub_cat.save()
                            long_dist_sub_category_flag = True
                            break
                    if not long_dist_sub_category_flag:
                        sub_cat.long_distance_availability = False
                        sub_cat.save()

                # remove shop from the sub category list
                if not sku_sub_category_data:
                    sub_cat.sales_unit.remove(shop)
                else:
                    sub_category_remove_flag = True
                    # print(f"Iterating on Sub category skus : {sku_sub_category_data}")
                    for p in sku_sub_category_data:
                        sku_sub_category_sales = SalesUnitProductSelection.objects.filter(sku=p,
                                                                                          sales_unit=shop).first()
                        # print(f"sub category  skus Shop Status for {sku_sub_category_sales}")
                        if sku_sub_category_sales:
                            # print("sub category skus Shop status exists")
                            if sku_sub_category_sales.shop_admin_status == "Visible":
                                # print(f"{sku_sub_category_sales}: Visible")
                                sub_category_remove_flag = False
                                break
                    if sub_category_remove_flag:
                        # print("No visible status available removed")

                        sub_cat.sales_unit.remove(shop)
                    else:
                        sub_cat.sales_unit.add(shop)

    # product status checking
    # unique category and sub_category update the availability with the same flow
    sku_category_list = []
    sku_sub_category_list = []
    for product in product_list:

        sku_products = SKU.objects.filter(product=product, sku_status="Visible")
        long_dist_sku_product = sku_products.filter(sku_expiry_duration__gt=30)

        if not long_dist_sku_product:
            product.long_distance_availability = False
            product.save()
        else:

            long_dist_product_flag = False
            for e in long_dist_sku_product:
                long_dist_sales_check_sub = SalesUnitProductSelection.objects.filter(sku=e, status = "Visible").first()
                if long_dist_sales_check_sub:
                    product.long_distance_availability = True
                    product.save()
                    long_dist_product_flag = True
                    break
            if not long_dist_product_flag:
                product.long_distance_availability = False
                product.save()

        if not sku_products:
            product.sales_unit.remove(shop)
        else:
            product_remove_flag = True
            for sku_product in sku_products:
                sku_product_sales = SalesUnitProductSelection.objects.filter(sku=sku_product, sales_unit=shop).first()
                # print(f"category  skus Shop Status for {sku_category_sales}")
                if sku_product_sales:
                    # print("category skus Shop status exists")
                    if sku_product_sales.shop_admin_status == "Visible":
                        # print(f"{sku_category_sales}: Visible")
                        product_remove_flag = False
                        break
            if product_remove_flag:
                product.sales_unit.remove(shop)
            else:
                product.sales_unit.add(shop)

        # remove shop from the category list

    try:
        log_user = request.user
        log_type = "product"
        message = f"Product Status Updated for Category or Sub Category"
        description = f"Bulk Category update"
        LoggingOperation.objects.create(user=log_user, log_type=log_type, message=message,
                                        description=description)
    except:
        pass

    paginator = Paginator(show_list, 10)

    products = paginator.get_page(1)
    # print(products)
    # print(show_list)

    if request.headers.get('x-requested-with') == 'XMLHttpRequest':
        tbody = render_to_string('product_page.html', {"product_data": products})
        pagination = render_to_string('pagination.html', {'product_data': products})
        return JsonResponse({'tbody': tbody, "pagination": pagination})
    base_url = reverse('list_shop_product',
                       kwargs={'shop_id': shop_id, 'product_type': 'Master Product', "sales_unit_active": "active"})
    return redirect(base_url)


def update_product_product_admin_status(request):
    if request.method == 'POST':
        sku_id = request.POST.get('sku_id')
        shop_id = request.POST.get('shop_id')
        new_status = request.POST.get('status')
        try:
            sales_unit_product = SalesUnitProductSelection.objects.get(sales_unit=shop_id, sku=sku_id)
        except SalesUnitProductSelection.DoesNotExist:
            return JsonResponse({'success': False, 'error': 'Operation not allowed.'})
        try:
            sales_unit_product.shop_admin_status = new_status
            sales_unit_product.save()
            # if visible add the shop availablility

            if new_status == "Visible":
                sales_unit_product.sku.sales_unit.add(sales_unit_product.sales_unit)
                sales_unit_product.sku.product.sales_unit.add(sales_unit_product.sales_unit)
                sales_unit_product.sku.product.item_category.sales_unit.add(sales_unit_product.sales_unit)
                sales_unit_product.sku.product.item_sub_category.sales_unit.add(sales_unit_product.sales_unit)

                # else remove shop availablility
            else:
                sales_unit_product.sku.sales_unit.remove(sales_unit_product.sales_unit)
                shop_admin_status_update_only_one_shop.apply_async(args=[sales_unit_product.sku.id, sales_unit_product.sales_unit.uuid], countdown=5)


            try:
                log_user = request.user
                log_type = "product"
                message = f"Product Shop Admin Status Updated for Shop {sales_unit_product.sales_unit.unit_name}({sales_unit_product.sales_unit.unit_code})"
                description = f"SKU {sales_unit_product.sku.sku_name}  with code {sales_unit_product.sku.sku_code} updated its status to {new_status} for product {sales_unit_product.sku.product.item_name} ({sales_unit_product.sku.product.item_code} "
                LoggingOperation.objects.create(user=log_user, log_type=log_type, message=message,
                                                description=description)
            except:
                pass
            return JsonResponse({'status': 'success'})
        except:
            return JsonResponse({'error': 'Invalid request.'})


@login_required
@user_passes_test(check_user_role(["Shop Admin"]))
def category_based_shop_admin_update_status(request, shop_id):
    category_id = request.POST.get('category')
    sub_category_id = request.POST.get('subcategory')
    new_status = request.POST.get('status_category_select')
    shop = Shop.objects.filter(uuid=shop_id).first()
    product_list = Products.objects.order_by('-created_date')
    if category_id:
        product_list = product_list.filter(item_category__uuid=category_id)
    if sub_category_id:
        product_list = product_list.filter(item_sub_category=sub_category_id)
    update_list = []
    product_selection = SalesUnitProductSelection.objects.filter(sales_unit__uuid=shop_id)
    update_list = []
    show_list = []
    for i in product_list:
        product_data = {"pk": i.pk, "item_name": i.item_name, "item_code": i.item_code,
                        "category_name": i.item_category.category_name,
                        "sub_category_name": i.item_sub_category.sub_category_name, "sku": []}
        sku_list = []
        for j in i.skus.all():

            sku_data = {"pk": j.pk, "sku_name": j.sku_name, "sku_code": j.sku_code, "sku_quantity": j.sku_quantity,
                        "sku_unit": j.sku_unit, "sku_mrp": j.sku_mrp, "sku_status": j.sku_status}
            if j.sku_status == "Visible":
                check = product_selection.filter(sku=j).first()
                if check.status == "Visible":
                    check.shop_admin_status = new_status
                    # j.status = check.status
                    # j.shop_admin_status = new_status
                    sku_data["status"] = check.status

                    sku_data["shop_admin_status"] = new_status
                else:
                    # j.status = check.status
                    # j.shop_admin_status = check.shop_admin_status

                    sku_data["status"] = check.status

                    sku_data["shop_admin_status"] = check.shop_admin_status
                update_list.append(check)
                # sku_list.append(j)
                sku_list.append(sku_data)

            else:
                check = product_selection.filter(sku=j).first()
                # j.status = check.status
                # j.shop_admin_status = check.shop_admin_status
                sku_data["status"] = check.status

                sku_data["shop_admin_status"] = check.shop_admin_status
                update_list.append(check)
                # sku_list.append(j)
                sku_list.append(sku_data)
        # i.sku = sku_list
        product_data["sku"] = sku_list
        # show_list.append(i)
        show_list.append(product_data)

    SalesUnitProductSelection.objects.bulk_update(update_list, ['shop_admin_status'])
    category_based_shop_admin_status_celery.apply_async(args = [shop.uuid, category_id], countdown= 5)
    sub_category_based_shop_admin_status_celery.apply_async(args=[shop.uuid, sub_category_id, category_id], countdown= 5)
    category_product_list.apply_async(args = [shop.uuid, category_id, sub_category_id], countdown =5)

    paginator = Paginator(show_list, 10)

    products = paginator.get_page(1)

    try:
        log_user = request.user
        log_type = "product"
        message = f"Product Status Updated for Category or Sub Category For Shop"
        description = f"Bulk Category update for Shop"
        LoggingOperation.objects.create(user=log_user, log_type=log_type, message=message,
                                        description=description)
    except:
        pass

    if request.headers.get('x-requested-with') == 'XMLHttpRequest':
        tbody = render_to_string('product_list_page.html', {"product_data": products})

        pagination = render_to_string('pagination.html', {'product_data': products})
        return JsonResponse({'tbody': tbody, "pagination": pagination})

    base_url = reverse('list_product_shop_admin')
    return redirect(base_url)


@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def sub_category_creation(request, category_id):
    link_1 = request.POST.get('image_link_1', '/media/icons/icon.jpg')
    link_2 = request.POST.get('image_link_2', '/media/standard_images/standard_image.jpg')
    link_3 = request.POST.get('image_link_3', '/media/banners/banner.jpg')

    image_field1 = render_to_string('image_field.html',
                                    {'class_name': 'upload', 'image_reset_button': 'image_reset_button1',
                                     'field_name': 'icon'})
    image_field2 = render_to_string('image_field.html',
                                    {'class_name': 'upload2', 'image_reset_button': 'image_reset_button2',
                                     'field_name': 'standard_image'})
    image_field3 = render_to_string('image_field.html',
                                    {'class_name': 'upload3', 'image_reset_button': 'image_reset_button3',
                                     'field_name': 'banner_image'})

    errors = []
    if request.headers.get('x-requested-with') == 'XMLHttpRequest':
        category = get_object_or_404(ProductCategory, uuid=category_id)
        sub_category_form = ProductSubCategoryForm(request.POST)

        sub_category_list = ProductSubCategory.objects.filter(category=category)
        if sub_category_form.is_valid():
            sub_category = sub_category_form.save(commit=False)
            sub_category.category = category
            sub_category.save()
            try:
                log_user = request.user
                log_type = "product"
                message = "Sub category created"
                description = f"Sub category {sub_category.sub_category_name}  with code {sub_category.sub_category_code} created for category {sub_category.category.category_name} with code {sub_category.category.category_code}"
                LoggingOperation.objects.create(user=log_user, log_type=log_type, message=message,
                                                description=description)
            except:
                pass
            sub_categories = render_to_string('sub_category_delete_list.html', {'sub_category': sub_category_list})
            return JsonResponse({"data": sub_categories, "errors": [], "success": True, 'image_field1': image_field1,
                                 'image_field2': image_field2, 'image_field3': image_field3,
                                 "link_1": link_1, "link_2": link_2, "link_3": link_3})
        else:
            error = {}
            for i, data in sub_category_form.errors.items():
                error[i] = data[0]

            errors.append(sub_category_form.errors)

            if sub_category_list:
                sub_categories = render_to_string('sub_category_delete_list.html', {'sub_category': sub_category_list})
                return JsonResponse(
                    {
                        "data": sub_categories,
                        "errors": error,
                        "success": False,
                        'image_field1': image_field1, 'image_field2': image_field2, 'image_field3': image_field3,
                        "link_1": link_1, "link_2": link_2, "link_3": link_3

                    })
            else:
                return JsonResponse({
                    "data": [],
                    "errors": error,
                    "success": False,
                    'image_field1': image_field1, 'image_field2': image_field2, 'image_field3': image_field3,
                    "link_1": link_1, "link_2": link_2, "link_3": link_3

                })
    else:
        return JsonResponse({
            "data": [],
            "errors": {"sub_category_code": "Something went wrong try again"},
            "success": False,
            'image_field1': image_field1, 'image_field2': image_field2, 'image_field3': image_field3,
            "link_1": link_1, "link_2": link_2, "link_3": link_3
        })


def filter_products(request):
    category_id = request.GET.get('category_id', None)

    # Filter products based on the category selected
    if category_id:
        products = Products.objects.filter(item_category__uuid=category_id)
    else:
        products = Products.objects.all()  # Default all products

    # Prepare the data for JSON response
    product_list = []
    for product in products:
        product_list.append({
            'id': product.id,
            'name': product.name,
            'price': product.price,
            'category': product.category.name
        })

    return JsonResponse({'products': product_list})


# @login_required
# @user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
# def product_list_shop_admin(request, product_type = "Master Product"):
#     search_query = request.GET.get('search', "")
#
#     product_list = Products.objects.filter(product_type =product_type).order_by('-created_date')
#     category_list = ProductCategory.objects.all().order_by("-created_at")
#     shop_details = get_object_or_404(Shop,uuid = shop_id)
#     shop_name = shop_details.unit_name
#     shop_location = shop_details.unit_location
#
#     if search_query:
#         product_list = Products.objects.filter(product_type =product_type).filter(
#             Q(item_name__icontains=search_query) |
#             Q(item_code__icontains=search_query) |
#             Q(item_category__category_name__icontains=search_query)|
#             Q(item_sub_category__sub_category_name__icontains = search_query)
#         ).order_by('-created_date')
#
#     for i in product_list:
#         try:
#             sales_product = SalesUnitProductSelection.objects.get(sales_unit = shop_details.uuid, product= i.id)
#             i.status = sales_product.status
#         except:
#             i.status = "disable"
#     paginator = Paginator(product_list, 10)
#     page = request.GET.get('page')
#
#     try:
#         products = paginator.get_page(page)
#     except (PageNotAnInteger, TypeError):
#         products = paginator.get_page(1)
#     except EmptyPage:
#         products = paginator.get_page(paginator.num_pages)
#
#     base_url = reverse('list_shop_product',kwargs={'shop_id':shop_id, 'product_type':'Master Product'})
#     query_params = request.GET.copy()
#     if 'page' in query_params:
#         del query_params['page']
#     pagination_links = [
#         {
#             'page_number': page_number,
#             'url': f"{base_url}?{query_params.urlencode(safe='/')}&page={page_number}",
#         }
#         for page_number in products.paginator.page_range
#     ]
#     context = {
#         "product_data": products,
#         'search_query': search_query,
#         'pagination_links': pagination_links,
#         'product_type' : product_type,
#         "product_active": "active",
#         "shop_id": shop_id,
#         "shop_name": shop_name,
#         "shop_location":shop_location,
#         "category_list": category_list
#
#     }
#
#     return render(request, "product_list_shop.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def image_field_rendering(request):
    link_1 = request.GET.get('image_link_1', '/media/icons/icon.jpg')
    link_2 = request.GET.get('image_link_2', '/media/standard_images/standard_image.jpg')
    link_3 = request.GET.get('image_link_3', '/media/banners/banner.jpg')

    image_field1 = render_to_string('image_field.html',
                                    {'class_name': 'upload', 'image_reset_button': 'image_reset_button1',
                                     'field_name': 'icon'})
    image_field2 = render_to_string('image_field.html',
                                    {'class_name': 'upload2', 'image_reset_button': 'image_reset_button2',
                                     'field_name': 'standard_image', "link": link_2})
    image_field3 = render_to_string('image_field.html',
                                    {'class_name': 'upload3', 'image_reset_button': 'image_reset_button3',
                                     'field_name': 'banner_image', "link": link_3})
    return JsonResponse(
        {'image_field1': image_field1, 'image_field2': image_field2, 'image_field3': image_field3, "link_1": link_1,
         "link_2": link_2, "link_3": link_3})


@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def tag_list(request):
    """
    In the product section on the side bar. We have
    the product tags. We list the products based on the tags
    on app.
    :param request:
    :return:
    """
    tags = Tags.objects.order_by("-created_at")
    paginator = Paginator(tags, 6)
    page = request.GET.get("page")
    try:
        tags = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        tags = paginator.get_page(1)
    except EmptyPage:
        tags = paginator.get_page(paginator.num_pages)
    context = {"tag_data": tags, "category_active": "active"}
    return render(request, "tag_list.html", context)


@require_POST
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def tag_delete(request, tag_id):
    """
    delete a tag listed in product section on side bar.
    :param request:
    :param tag_id:
    :return:
    """
    tags = Tags.objects.filter(pk=tag_id)
    if tags:
        tags.delete()
    return redirect("tag_list")


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def tag_edit(request, tag_id):
    """
    If we click on the edit button on tag listing we are
    redirecting to edit form. We can change the tag details
    with the form.
    :param request:
    :param tag_id:
    :return:
    """
    errors = []
    tags = get_object_or_404(Tags, pk=tag_id)
    if request.method == "POST":
        form = TagForm(request.POST, request.FILES, instance=tags)
        if form.is_valid():
            # image validations are in the form

            # if no images we set the value of the field to None
            if request.POST.get('image1', ''):
                form.instance.icon = None
            form.save()
            return redirect("tag_list")

        else:
            errors.append(form.errors)
    else:
        form = TagForm(instance=tags)
    context = {"tag_form": form, "category_active": "active"}
    return render(request, "tag_edit.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def tag_add(request):
    """
    In the tag list window we have a button add tag. on clicking
    the add tag button. we can add a new tag. fields are
    tag_name, icon, products.
    :param request:
    :return:
    """
    errors = []
    if request.method == "POST":
        tag_form = TagForm(request.POST, request.FILES)
        if tag_form.is_valid():
            tags = tag_form.save()
            return redirect("tag_list")
        else:
            errors.append(tag_form.errors)
            context = {
                "errors": errors,
                "tag_form": tag_form,
                "category_active": "active"
            }
            return render(request, "tag_add.html", context)
    else:
        tag_form = TagForm()

    context = {
        "errors": errors,
        "tag_form": tag_form,
        "category_active": "active"

    }
    return render(request, "tag_add.html", context)


@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def dynamic_filter_list(request):
    """In app just like tags we have easy filters also. One of them is dynamic
    filter. for eg: black forest. if we select the black forest we will return the
    products associated with the filter.
    admin listing of dynamic filter.
    """
    filter_list = DynamicFiltering.objects.order_by("-created_at")
    paginator = Paginator(filter_list, 6)
    page = request.GET.get("page")
    try:
        filter_list = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        filter_list = paginator.get_page(1)
    except EmptyPage:
        filter_list = paginator.get_page(paginator.num_pages)
    context = {"filter_data": filter_list, "category_active": "active"}
    return render(request, "dynamic_filter_list.html", context)


@require_POST
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def dynamic_filter_delete(request, filter_id):
    """
    Dynamic filter also listed on the product section on side bar.
    for each dynamic filter, There is a delete button on the listing.
    on clicking the delete button. a confirmation dialog box will appear.
    click yes and we could delete the filter.
    :param request:
    :param filter_id:
    :return:
    """
    filter_list = DynamicFiltering.objects.filter(pk=filter_id)
    if filter_list:
        filter_list.delete()
    return redirect("filter_list")


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def dynamic_filter_edit(request, filter_id):
    """
    Editing a dynamic filter. for each dynamic filter listed on
    dynamic filter list, we have a edit button. clicking on
    the edit button will open a form. fields are tag name and products.

    :param request:
    :param filter_id:
    :return:
    """
    errors = []
    filter_list = get_object_or_404(DynamicFiltering, pk=filter_id)
    if request.method == "POST":
        form = DynamicFilterForm(request.POST, instance=filter_list)
        if form.is_valid():
            form.save()
            return redirect("filter_list")

        else:
            errors.append(form.errors)
    else:
        form = DynamicFilterForm(instance=filter_list)
    context = {"filter_form": form, "category_active": "active"}
    return render(request, "dynamic_filter_edit.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def dynamic_filter_add(request):
    """
    Adding a dynamic filter.
    On dynamic filter listing page which we can open by clicking
    on the product section on side bar have a add dynamic filter button.
    clicking will open the form enter the products and tag name on the form.
    on submission create a new dynamic filter.

    :param request:
    :return:
    """
    errors = []
    if request.method == "POST":
        filter_form = DynamicFilterForm(request.POST)
        if filter_form.is_valid():
            filter = filter_form.save()
            return redirect("filter_list")
        else:
            errors.append(filter_form.errors)
            context = {
                "errors": errors,
                "filter_form": filter_form,
                "category_active": "active"
            }
            return render(request, "dynamic_filter_add.html", context)
    else:
        filter_form = DynamicFilterForm()

    context = {
        "errors": errors,
        "filter_form": filter_form,
        "category_active": "active"

    }
    return render(request, "dynamic_filter_add.html", context)


@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def special_list(request):
    """
    special list showed on the app. for eg:  Rolls - Chicken Spring Roll,
    special list will list when we click on the special list on product section
    on side bar.
    :param request:
    :return:
    """
    special_product_list = SpecialList.objects.order_by("-created_at")
    paginator = Paginator(special_product_list, 6)
    page = request.GET.get("page")
    try:
        special_product_list = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        special_product_list = paginator.get_page(1)
    except EmptyPage:
        special_product_list = paginator.get_page(paginator.num_pages)
    context = {"special_data": special_product_list, "category_active": "active"}
    return render(request, "special_list.html", context)


@require_POST
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def special_list_delete(request, special_list_id):
    """
    Deleting a special list. for each special list listed on
    special listing, we have a delete button. clicking on
    the delete button will open a confirm dialog box. if you confirm
    it will delete the special list.
    :param request:
    :param special_list_id:
    :return:
    """
    special_product_list = SpecialList.objects.filter(pk=special_list_id)
    if special_product_list:
        special_product_list.delete()
    return redirect("special_list")


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def special_list_edit(request, special_list_id):
    """

    Editing a special list. for each special list listed on
    special listing, we have a edit button. clicking on
    the edit button will open a form. fields are special name and icon,
    standard image, banner image, and products.

    :param request:
    :param special_list_id:
    :return:
    """
    errors = []
    special_product_list = get_object_or_404(SpecialList, pk=special_list_id)
    if request.method == "POST":
        form = SpecialListForm(request.POST, request.FILES, instance=special_product_list)

        # validations and cleansing the images on the SpecialListForm
        if form.is_valid():
            if request.POST.get('image1', ''):
                form.instance.icon = None
            if request.POST.get('image2', ''):
                form.instance.standard_image = None
            if request.POST.get('image3', ''):
                form.instance.banner_image = None
            form.save()
            return redirect("special_list")

        else:
            errors.append(form.errors)
    else:
        form = SpecialListForm(instance=special_product_list)
    context = {"special_form": form, "category_active": "active"}
    return render(request, "special_list_edit.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def special_list_add(request):
    """
    Adding a special list.
    On special listing page which we can open by clicking
    on the product section on side bar have a add special list button.
    clicking will open the form enter the special name and icon,
    standard image, banner image, and products.
    on submission create a new special list.
    :param request:
    :return:
    """
    errors = []
    if request.method == "POST":
        form = SpecialListForm(request.POST, request.FILES)
        if form.is_valid():
            special = form.save()
            return redirect("special_list")
        else:
            # error handling
            errors.append(form.errors)
            context = {
                "errors": errors,
                "special_form": form,
                "category_active": "active"
            }
            return render(request, "special_list_add.html", context)
    else:
        form = SpecialListForm()

    context = {
        "errors": errors,
        "special_form": form,
        "category_active": "active"

    }
    return render(request, "special_list_add.html", context)

#.......................................... end product ....................................................

# ............................................. start orders ...............................................#


class SendVerificationEmailView(APIView):
    """
    View to send delivery boy verification email.
    """

    # permission_classes = [IsAuthenticated]

    def post(self, request, delivery_boy):
        """
        On orders on side bar, we have a menu delivery details. if we click on
        delivery details we have the delivery boys listed. we can add new delivery
        boys. for assigning a order, adding alone not help. for that we have to
        verify the delivery boy. we will send the verification sms to the phone number using this view.

        :param request:
        :param delivery_boy:
        :return:
        """
        user = request.user
        delivery_boys = get_object_or_404(DeliveryBoys, pk=delivery_boy)
        if delivery_boys.status == 'verified':
            context = {"message": "Phone  alreay verified .", 'status': 0}
            return render(request, "phone_verification.html", context)

        # Create a token
        token = jwt.encode({'delivery_boy': str(delivery_boys.pk), 'exp': timezone.now() + timedelta(hours=24)},
                           settings.SECRET_KEY, algorithm='HS256')

        # Create verification link
        # verification_link = request.build_absolute_uri(
        #     reverse('verify-phone', args=[token])
        # )
        domain = getattr(settings, "DOMAIN", "http://localhost/")
        verification_link = f"{domain}{reverse('verify-phone', args=[token])}"
        # sms_flag = True
        # try:
        #     # sending sms with the twilio
        #     sms.apply_async(args=[f"click here to verify: {verification_link}", delivery_boys.phone_number],countdown=5)
        #     context = {"message": "Verification link sent to your phone number.", 'status': 1, "send": 1}
        # except Exception as e:
        #     sms_flag = False


        # # Send the email
        email_flag = True
        try:
            send_mail(
                'Verify your email',
                f'Click the link to verify your email: {verification_link}',
                settings.DEFAULT_FROM_EMAIL,
                [delivery_boys.email],
                fail_silently=False,
            )
        except Exception as e:
            email_flag = False

        # we are sending to email and the phone number.

        # if sms_flag and email_flag :
        #     context = {"message": "Verification link sent to your phone number and email. Please check.", 'status': 1, "send": 1}
        # elif sms_flag:
        #     context = {"message": "Verification link sent to your phone number. Please check.", 'status': 1, "send": 1}
        if email_flag:
            context = {"message": "Verification link sent to your email. Please check.", 'status': 1, "send": 1}
        else:
            context = {"message": "Invalid Phone number or email!", 'status': 0, "send": 1}


        return render(request, "phone_verification.html", context)




class VerifyEmailView(APIView):
    """
    Delivery details menu on Orders section in the side bar will list the delivery boys.
    this view is to verify with the link.
    we send a link to verify the delivery boy on clicking that link, VerifyEmailView will
    verify.
    """
    def get(self, request, token):
        try:
            payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
            delivery_boys = get_object_or_404(DeliveryBoys, pk=payload['delivery_boy'])

            # Update email verification status
            delivery_boys.status = "verified"
            delivery_boys.save()

            return render(request, 'phone_verification.html', {
                'status': 1,
                'message': "Phone number verified successfully.",
                "send": 0
            })
        except jwt.ExpiredSignatureError:
            return render(request, 'phone_verification.html', {
                'status': 0,
                'message': "Verification link has expired.",
                "send": 0
            })
        except jwt.InvalidTokenError:
            return render(request, 'phone_verification.html', {
                'status': 0,
                'message': "Invalid verification link.",
                "send": 0
            })


@user_passes_test(check_user_role(["Shop Admin", "PU Admin"]))
def delivery_boy_list(request):
    """
    Delivery details menu on Orders section in the side bar will list the delivery boys.
    It is only available for shop admin.
    each shop have their own delivery boys.
    :param request:
    :return:
    """

    # get the shop
    shop_id_verify = Shop.objects.prefetch_related('unit_admin_user').filter(
        unit_admin_user__uuid=request.user.uuid)

    # if shop exists get the shop id
    if shop_id_verify:
        shop_id = shop_id_verify[0]
    else:
        shop_id = ""

    # fetch delivery boy associated with the shop
    delivery_boys = DeliveryBoys.objects.filter(shop=shop_id).order_by("-created_date")
    paginator = Paginator(delivery_boys, 6)
    page = request.GET.get("page")

    try:
        delivery_boys = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        delivery_boys = paginator.get_page(1)
    except EmptyPage:
        delivery_boys = paginator.get_page(paginator.num_pages)
    context = {"delivery_boy_data": delivery_boys, "orders_active": "active"}
    return render(request, "delivery_boy_list.html", context)


@require_POST
@login_required
@user_passes_test(check_user_role(["Shop Admin"]))
def delivery_boy_delete(request, delivery_boy_id):
    """
    Delivery details menu on Orders section in the side bar will list the delivery boys.
    Deleting a delivery boy. for each delivery boy listed on
    delivery boy list, we have a delete button. clicking on
    the delete button will open a confirm dialog box. if you confirm
    it will delete the boy.
    """
    delivery_boy = DeliveryBoys.objects.filter(pk=delivery_boy_id)
    if delivery_boy:
        delivery_boy.delete()
    return redirect("delivery_boy_list")


@login_required
@user_passes_test(check_user_role(["Shop Admin"]))
def delivery_boy_edit(request, delivery_boy_id):
    """
    Delivery details menu on Orders section in the side bar will list the delivery boys.
    Editing a delivery boy. for each delivery boy listed, we have a edit button. clicking on
    the edit button will open a form. fields are team member name and phone number,
    email.

    """
    errors = []
    delivery_boy = DeliveryBoys.objects.filter(pk=delivery_boy_id).first()
    if request.method == "POST":
        form = DeliveryBoyForm(request.POST, instance=delivery_boy)
        if form.is_valid():
            delivery_boy = form.save()
            delivery_boy.status = "not verified"
            delivery_boy.save()
            return redirect("delivery_boy_list")

        else:
            errors.append(form.errors)
    else:
        form = DeliveryBoyForm(instance=delivery_boy)
    context = {"delivery_boy_form": form, "orders_active": "active"}
    return render(request, "delivery_boy_edit.html", context)


@login_required
@user_passes_test(check_user_role(["Shop Admin"]))
def delivery_boy_add(request):
    """
    Adding a delivery boy.
    On delivery boy listing page which we can open by clicking
    on the order section on side bar have a add delivery boy button.
    clicking will open the form enter the fields are team member name and phone number,
    email.
    on submission create a new delivery boy.
    """
    errors = []
    if request.method == "POST":
        shop_id_verify = Shop.objects.prefetch_related('unit_admin_user').filter(
            unit_admin_user__uuid=request.user.uuid)
        if shop_id_verify:
            shop_id = shop_id_verify[0]
        else:
            shop_id = ""
        form = DeliveryBoyForm(request.POST)
        if form.is_valid():
            delivery_boy = form.save()
            delivery_boy.shop = shop_id
            delivery_boy.save()
            return redirect("delivery_boy_list")
        else:
            errors.append(form.errors)
            context = {
                "errors": errors,
                "delivery_boy_form": form,
                "orders_active": "active"
            }
            return render(request, "delivery_boy_add.html", context)
    else:
        form = DeliveryBoyForm()

    context = {
        "errors": errors,
        "delivery_boy_form": form,
        "orders_active": "active"

    }
    return render(request, "delivery_boy_add.html", context)


# pip install twilio
# def sms(body, to_phone):
#     from twilio.rest import Client
#
#     account_sid = "ACc6df2dd6139dd07e9d84e78ac73f27f8"
#     auth_token = "d01958dbb0ae7723b4718873add279e7"
#     fromph = "+18106965991"
#     to_phone = str(to_phone)
#     # Here we'll build a new Twilio_client with different credentials
#     twilio_client = Client(account_sid, auth_token)
#
#     message = twilio_client.messages.create(
#         to=to_phone,
#         from_=fromph,
#         body=body)

from django.utils import timezone
from dateutil import parser

@login_required
@user_passes_test(check_user_role(["Shop Admin", "PU Admin"]))
def order_list(request, order_id=None):
    """
    List orders for shop admin / PU admin with product data and custom images (if present).
    """
    try:
        platform = request.GET.get('platform')
        order_type = request.GET.get('order_type', "")
        shop_qs = Shop.objects.filter(unit_admin_user__uuid=request.user.uuid)
        shop_id = shop_qs[0] if shop_qs else ""
        errors = []
        field_name = request.COOKIES.get('field_name')
        sort_order = request.COOKIES.get('sort_order')
        # ----------------- POST: courier form handling -----------------
        if request.method == "POST":
            order_delivery = OrderDelivery.objects.filter(order=order_id).first()
            order = Orders.objects.filter(pk=order_id).first()
            form = CourierDetailsForm(request.POST, instance=order_delivery)
            if form.is_valid():
                delivery_data = form.save(commit=False)
                delivery_data.order = order
                delivery_data.delivery_type = "Courier Delivery"
                delivery_data.save()
                if order.order_status == "Order Packed":
                    order.order_status = "Despatched"
                    order.save()

                    title = "Your Order is On the Way 🚚"

                    description = f"""Hi {order.user_uuid.first_name},

                    Your order {order.order_ID} has been dispatched and is on its way to your location.

                    We’ll notify you once it is delivered.

                    Thank you for choosing Navya Bakers!"""
                    NotificationInit(order.user_uuid.fcm_token, order, title, description, order.user_uuid)
                    EmailSend(title, description, [order.user_uuid.email], False)
                    temp_name = "out_for_delivery_general"
                    if order.user_uuid.opt_in == True:
                        send_whatsapp_message.apply_async(args=[order.user_uuid.phone_number, order.order_ID, order.user_uuid.first_name, temp_name], countdown=2 )

                return JsonResponse({
                    'status': 'success',
                    'order_id': order_id,
                    'OrderID': order.order_ID,
                })

            else:
                errors.append(form.errors)
                return JsonResponse({'status': 'fail', 'errors': form.errors})

        # ----------------- GET: filters & params -----------------
        search_query = request.GET.get('search', "")
        order_date = request.GET.get('product_date', "")
        order_status = request.GET.get('product_status', "")
        check_new_orders = request.GET.get('check_new_orders', False)
        last_order_check = request.session.get('last_order_check', None)
        
        sort_order_data = request.GET.get('sort_order', "")
        if not sort_order_data:
            sort_order_data = sort_order
        field_name_data = request.GET.get('field_name', "")
        if not field_name_data:
            field_name_data = field_name

        # ----------------- base queryset with prefetch for performance -----------------
        # Prefetch OrderProducts and their customizations (where custom_image lives)
        orderproducts_qs = OrderProducts.objects.select_related('sku').prefetch_related(
            Prefetch('customizations', queryset=OrderProductCustomization.objects.all())
        )
        
        # Prefetch AdditionalDetails (reverse OneToOne relation)
        additionaldetails_qs = AdditionalDetails.objects.all()

        if shop_id:
            base_qs = Orders.objects.filter(
                store_uuid=shop_id,
                order_type__in=["Local Orders", "Pick Up"]
            ).exclude(order_status="New Order")
        else:
            base_qs = Orders.objects.filter(
                order_type="Long Distance Orders"
            ).exclude(order_status="New Order")

        order_list = base_qs.select_related('user_uuid', 'drop_address', 'store_uuid').prefetch_related(
            Prefetch('order_data', queryset=orderproducts_qs),
            Prefetch('additionaldetails', queryset=additionaldetails_qs)  # OneToOne reverse relation with explicit queryset
        ).order_by('-created_date')

        # order_type filter (your existing logic)
        if order_type == "new orders":
            order_list = order_list.filter(order_status="Confirmed")

        # platform filter

        if platform:
            if platform == 'web':
                order_list = order_list.filter(
                    Q(platform__iexact="Web") | Q(platform__iexact="Other")
                )
            else:  # mobile section
                order_list = order_list.filter(
                    Q(platform__iexact="IOS") |
                    Q(platform__iexact="Andriod") |
                    Q(platform__iexact="Android") |
                    Q(platform__iexact="Flutter_web_app")
                )

        # AJAX filters (search / date / status / sorting)
        if request.headers.get('x-requested-with') == 'XMLHttpRequest':
            if search_query:
                order_list = order_list.filter(
                    Q(order_ID__icontains=search_query) |
                    Q(order_type__icontains=search_query) |
                    Q(order_status__icontains=search_query) |
                    Q(user_uuid__first_name__icontains=search_query)
                ).order_by('-created_date')

            if order_date:
                try:
                    formatted_date = datetime.strptime(order_date, "%Y-%m-%d")
                    order_list = order_list.filter(created_date__date=formatted_date)
                except ValueError:
                    pass

            if order_status:
                order_list = order_list.filter(product_status=order_status)

            if sort_order_data and field_name_data:
                if sort_order_data == 'asc':
                    if field_name_data == 'user_uuid':
                        order_list = order_list.order_by(field_name_data + '__first_name')
                    else:
                        order_list = order_list.order_by(field_name_data)
                else:
                    if field_name_data == 'user_uuid':
                        order_list = order_list.order_by('-' + field_name_data + '__first_name')
                    else:
                        order_list = order_list.order_by('-' + field_name_data)

        # ----------------- Pagination -----------------
        paginator = Paginator(order_list, 6)
        page = request.GET.get('page')
        try:
            orders = paginator.get_page(page)
        except (PageNotAnInteger, TypeError):
            orders = paginator.get_page(1)
        except EmptyPage:
            orders = paginator.get_page(paginator.num_pages)

        # ----------------- Build items list for each order -----------------
        for order in orders:
            items = []
            for op in order.order_data.all():
                customization = None
                try:
                    customization = op.customizations.all()[0] if op.customizations.exists() else None
                except Exception:
                    customization = None

                item = {
                    "product_name": op.product_name,
                    "sku_name": op.sku.sku_name if getattr(op, 'sku', None) and getattr(op.sku, 'sku_name', None) else "",
                    "sku_quantity": getattr(op.sku, 'sku_quantity', ""),
                    "sku_unit": getattr(op.sku, 'sku_unit', ""),
                    "quantity": op.quantity,
                    "price": op.price,
                    "custom_note": customization.custom_note if customization else "",
                    "custom_image_url": customization.custom_image.url if customization and getattr(customization, 'custom_image', None) else None,
                }
                items.append(item)
            setattr(order, "items", items)

        # ----------------- other context bits -----------------
        form = CourierDetailsForm()
        try:
            ongoing_orders = DeliveryBoys.objects.filter(status='verified', shop=shop_id)
        except Exception:
            ongoing_orders = []

        new_order_data = [{"order_ID": order.order_ID} for order in orders if (order.order_status == "Confirmed")]
        
        context = {
            "courier_form": form,
            "order_data": orders,
            "search_query": search_query,
            "date": order_date,
            "product_status": order_status,
            "ongoing_orders": ongoing_orders,
            "orders_active": "active",
            "platform": platform,
            "order_type": order_type if order_type else "",
            "new_order_data": new_order_data,
        }

        # ----------------- AJAX return with new orders count -----------------
        if request.headers.get('x-requested-with') == 'XMLHttpRequest':
        # Get last check time from session (stored as string)
            last_order_check_str = request.session.get('last_order_check')
            
            # Convert to datetime for filtering
            last_order_check = None
            if last_order_check_str:
                try:
                    last_order_check = parser.parse(last_order_check_str)
                    if timezone.is_naive(last_order_check):
                        last_order_check = timezone.make_aware(last_order_check)
                except Exception as e:
                    print(f"Error parsing date: {e}")
            
            # Calculate new orders count
            new_orders_count = 0
            if last_order_check:
                try:
                    if shop_id:
                        new_orders_count = Orders.objects.filter(
                            store_uuid=shop_id,
                            created_date__gt=last_order_check,
                            order_type__in=["Local Orders", "Pick Up"]
                        ).exclude(order_status="New Order").count()
                    else:
                        new_orders_count = Orders.objects.filter(
                            order_type="Long Distance Orders",
                            created_date__gt=last_order_check
                        ).exclude(order_status="New Order").count()
                except Exception as e:
                    print(f"Error counting new orders: {e}")
            
            # Store current time as string in session
            request.session['last_order_check'] = str(timezone.now())
            
            # Prepare JSON response (all serializable)
            ajax_context = {
                "order_data": orders,
                "ongoing_orders": ongoing_orders,
                "platform": platform,
                "order_type": order_type,
                "new_order_data": new_order_data,
            }
            
            tbody = render_to_string('order_list_page.html', ajax_context)
            pagination = render_to_string('order_list_pagination.html', {"order_data": orders})
            
            return JsonResponse({
                'tbody': tbody,
                'pagination': pagination,
                'new_orders_count': new_orders_count,
                'status': 'success'
            })

        # ----------------- final render -----------------
        if order_type == "new orders":
            return render(request, "new_orders.html", context)
        return render(request, "order_list.html", context)

    except IndexError:
        context = {
            "order_data": [],
            "orders_active": "active"
        }
        return render(request, "order_list.html", context)


#
#
#
#

@login_required
@user_passes_test(check_user_role(["PU Admin"]))
def courier_details_edit(request, order_id):
    csrf = request.POST.get('csrfmiddlewaretoken')
    errors = []
    button_status = "false"
    order_delivery = OrderDelivery.objects.filter(order=order_id).first()

    if order_delivery:
        form = CourierDetailsForm(instance=order_delivery)
        if order_delivery.order.order_status == "Order Packed":
            button_status = "true"

    else:
        order = Orders.objects.filter(uuid=order_id).first()
        order_delivery = OrderDelivery.objects.create(delivery_type="Courier Delivery", order=order)
        form = CourierDetailsForm(instance=order_delivery)
        if order.order_status == "Order Packed":
            button_status = "true"

    courier_form = render_to_string('courier_edit_form.html',
                                    {'courier_form': form, "order_id": order_id, "button_status": button_status})

    return JsonResponse({'success': True, 'form': courier_form})


class ShipperAutocomplete(autocomplete.Select2QuerySetView):
    def get_result_label(self, item):
        # Customize the format of the autocomplete options
        return f"{item.team_member_name}"

    def get_queryset(self):
        ongoing_orders = DeliveryBoys.objects.filter(status='verified', delivery_boy_status='not assigned')
        if self.q:
            ongoing_orders = ongoing_orders.filter(team_member_name__istartswith=self.q)

        return ongoing_orders


# function to send notification



def WhatsappMessageSend(order, pick_up_address, drop_address, delivery_boy):

    try:
        if delivery_boy:
            START_LAT = order.drop_address.latitude
            START_LNG = order.drop_address.longitude
            DEST_LAT = order.store_uuid.latitude
            DEST_LNG = order.store_uuid.longitude
            google_place_link = f"https://www.google.com/maps/dir/{START_LAT},{START_LNG}/{DEST_LAT},{DEST_LNG}"
            data = {"pick_up_address": pick_up_address, "drop_address": drop_address,
                    "google_place_link": google_place_link, "orderID": order.order_ID}
            data = send_whatsapp_message_delivery_partner.apply_async(
    number="+919876543210",
    delivery_partner_name="Ravi",
    order_id="ORD12345",
    pickup_location="Navya Restaurant, Coimbatore",
    delivery_address="12, Gandhipuram, Coimbatore",
    customer_name="Akash",
    customer_phone="9876543210",
    order_amount="450",
    payment_mode="Cash on Delivery",
    delivery_slot="6:00 PM - 7:00 PM"
)

            return data
        else:
            return None

    except Exception as e:
        logging.error(f"Send Whatsapp message to Shipper Failed: {e}")
        return None

def ShipperEmailSend(shipper_uuid, order, pick_up_address, drop_address, delivery_boy):
    try:
        # fetch location user location details
        location_list = []
        orders = Orders.objects.filter(delivery_boy=shipper_uuid, order_status="Delivery Assigned")
        for i in orders:
            try:
                latitude = i.drop_address.latitude
                longitude = i.drop_address.longitude
                location_list.append((latitude, longitude))
            except:
                pass

        map_link = [f"{i[0]},{i[1]}" for i in location_list]
        map_link_join = "|".join(map_link)

        # send email with the map
        subject = f"Order {order.order_ID} Assinged to you"
        latitude = str(order.store_uuid.latitude)
        longitude = str(order.store_uuid.longitude)
        base = 'https://www.google.com/maps/dir/?api=1'
        map_link_to_send = f"{base}&origin={latitude},{longitude}&destination={latitude},{longitude}&waypoints={map_link_join}&travelmode=driving"

        message = f""" The Order no: {order.order_ID} assigned to you by NavyaBakeShop.\nPickup Location : {pick_up_address} \nDelivery Location : {drop_address}\n\nPlease click link to track order : {map_link_to_send}\n\nNavya Bakers
                  """
        # change to celery
        # email_data = email_sending.s(subject=subject,
        #                              message=message,
        #                              from_email=settings.EMAIL_HOST_USER,
        #                              recipient_list=[delivery_boy.email],
        #                              fail_silently=False, )
        email_data = email_sending.apply_async(args=[subject, message, settings.EMAIL_HOST_USER, [delivery_boy.email], False], countdown=5)
        return email_data



    except Exception as e:
        logging.error(f"Failed to Send Email While Assigning Delivery Boy:{e}")
        return None

def save_shipper(request):
    if request.method != "POST":
        logging.warning("❌ Invalid request method - expected POST")
        return JsonResponse({"success": False, "message": "Invalid request method"})

    shipper_uuid = request.POST.get("shipper_uuid")
    order_uuid = request.POST.get("order_uuid")
    page = request.POST.get("page")
    search = request.POST.get("search")
    product_date = request.POST.get("product_date")
    order = Orders.objects.filter(uuid=order_uuid).first()

    logging.info(f"📦 save_shipper called | shipper={shipper_uuid}, order={order_uuid}")

    try:
                # -------------------------------
        # 🔥 DELHIVERY FLOW (FINAL)
        # -------------------------------
        if shipper_uuid == "DELHIVERY":
            logging.info(f"🚚 Delhivery assignment started for order {order.order_ID}")

            try:
               

                # ✅ ONLY LONG DISTANCE
                if order.order_type != "Long Distance Orders":
                    return JsonResponse({
                        "success": False,
                        "message": "Delhivery only allowed for long distance orders"
                    })


                if not order.drop_address:
                    return JsonResponse({
                        "success": False,
                        "message": "Delivery address missing"
                    })

                

                # ✅ PREVENT DUPLICATE
                existing = Delhivery.objects.filter(order=order).first()
                if existing and existing.awb_number:
                    return JsonResponse({
                        "success": False,
                        "message": "Shipment already created"
                    })

                # ✅ PAYMENT TYPE
                if order.payment_mode and order.payment_mode.mode_name == "COD":
                    payment_type = "COD"
                else:
                    payment_type = "Prepaid"


                # 🚀 CALL STAGING API
                response = create_delhivery_shipment(order, payment_type)

                if not response.get("success"):
                    return JsonResponse({
                        "success": False,
                        "message": "Shipment creation failed",
                        "error": response.get("error")
                    })

                # ✅ SAVE
                Delhivery.objects.update_or_create(
                    order=order,
                    defaults={
                        "delivery_type": "DELHIVERY",
                        "awb_number": response.get("awb"),
                        "tracking_url": response.get("tracking_url"),
                        "status": "CREATED"
                    }
                )

                # ✅ UPDATE ORDER STATUS
                order.order_status = "Despatched"
                order.save()

                logging.info(f"✅ Delhivery AWB: {response.get('awb')}")

                return JsonResponse({
                    "success": True,
                    "message": "Assigned to Delhivery",
                    "awb": response.get("awb"),
                    "tracking_url": response.get("tracking_url"),
                    "page": page,
                    "search": search,
                    "product_date": product_date,
                })

            except Exception as e:
                logging.error(f"❌ Delhivery error: {str(e)}")
                return JsonResponse({
                    "success": False,
                    "message": "Delhivery integration failed"
                })
        # -------------------------------
        # Validate shipper
        # -------------------------------
        logging.info(f"🔍 Validating shipper: {shipper_uuid}")
        shipper = DeliveryBoys.objects.filter(pk=shipper_uuid).first()
        if not shipper:
            logging.error(f"❌ Invalid shipper UUID: {shipper_uuid}")
            return JsonResponse({"success": False, "message": "Invalid shipper"})
        logging.info(f"✅ Shipper validated: {shipper.team_member_name}")

        # -------------------------------
        # Validate order
        # -------------------------------
        logging.info(f"🔍 Validating order: {order_uuid}")
        
        if not order:
            logging.error(f"❌ Invalid order UUID: {order_uuid}")
            return JsonResponse({"success": False, "message": "Invalid order"})
        
        logging.info(f"✅ Order found: {order.order_ID}, Status: {order.order_status}")

        if order.order_status in ["Viewed", "New Order"]:
            logging.warning(f"⚠️ Order {order.order_ID} not confirmed yet")
            return JsonResponse({"success": False, "message": "Order not confirmed"})

        if order.order_status == "Delivered":
            logging.warning(f"⚠️ Order {order.order_ID} already delivered")
            return JsonResponse({"success": False, "message": "Order already delivered"})

        if order.order_status == "Failed":
            logging.warning(f"⚠️ Order {order.order_ID} has failed")
            return JsonResponse({"success": False, "message": "Order failed"})

        if order.order_status == "Delivery Assigned":
            logging.warning(f"⚠️ Order {order.order_ID} already assigned")
            return JsonResponse({"success": False, "message": "Order already assigned"})

        # -------------------------------
        # Assign delivery boy
        # -------------------------------
        logging.info(f"🚀 Assigning order {order.order_ID} to {shipper.team_member_name}")
        
        order.order_status = "Delivery Assigned"
        order.order_assigntime = timezone.now()
        order.delivery_boy = shipper_uuid
        order.save()
        deliverylink = f"https://navyasvpstaging.test4u.in/orders/delivery-confirm/{order.uuid}/"
        logging.info(f"✅ Order status updated to Delivery Assigned {deliverylink}")

        OrderDelivery.objects.update_or_create(
            delivery_boy=shipper,
            order=order,
            defaults={"delivery_type": order.order_type},
        )
        logging.info(f"✅ OrderDelivery record created/updated")

        shipper.delivery_boy_status = "assigned"
        shipper.save()
        logging.info(f"✅ Shipper status updated to 'assigned'")

        # -------------------------------
        # Build addresses safely
        # -------------------------------
        logging.info("🏠 Building address information")
        
        drop_address = ""
        if order.drop_address:
            d = order.drop_address
            drop_address = (
                f"{d.name}, {d.house_number_or_name}, {d.street}, "
                f"{d.city}, {d.state_or_province} - {d.pin_code}"
            )
            logging.info(f"✅ Drop address built")

        pick_up_address = ""
        if order.store_uuid:
            s = order.store_uuid
            pick_up_address = (
                f"{s.unit_name} ({s.unit_code}), {s.unit_location}, {s.city}"
            )
            logging.info(f"✅ Pickup address built")

        google_place_link = ""
        if order.drop_address and order.store_uuid:
            google_place_link = (
                f"https://www.google.com/maps/dir/"
                f"{order.drop_address.latitude},{order.drop_address.longitude}/"
                f"{order.store_uuid.latitude},{order.store_uuid.longitude}"
            )
            logging.info(f"✅ Google Maps link generated")

        # -------------------------------
        # Delivery slot & pickup time
        # -------------------------------
        logging.info("⏰ Processing delivery slot and pickup time")
        
        delivery_slot = ""
        pickup_time = ""

        if order.delivery_slot_date and order.delivery_slot_time:
            try:
                delivery_datetime = datetime.strptime(
                    f"{order.delivery_slot_date} {order.delivery_slot_time}",
                    "%Y-%m-%d %H:%M:%S",
                )
                logging.info(f"✅ Parsed delivery datetime (DB format)")
            except ValueError:
                try:
                    delivery_datetime = datetime.strptime(
                        f"{order.delivery_slot_date} {order.delivery_slot_time}",
                        "%Y-%m-%d %I:%M %p",
                    )
                    logging.info(f"✅ Parsed delivery datetime (UI format)")
                except ValueError as e:
                    logging.error(f"❌ Failed to parse datetime: {e}")
                    delivery_datetime = None

            if delivery_datetime:
                pickup_datetime = delivery_datetime - timedelta(hours=1)
                delivery_slot = delivery_datetime.strftime("%d %b %Y | %I:%M %p")
                pickup_time = pickup_datetime.strftime("%I:%M %p (%d-%m-%Y)")
                logging.info(f"✅ Delivery slot: {delivery_slot}")

        payment_mode = (
            order.payment_mode.mode_name if order.payment_mode else "N/A"
        )
        logging.info(f"💳 Payment mode: {payment_mode}")

        # -------------------------------
        # Send WhatsApp & Email ASYNCHRONOUSLY
        # -------------------------------
        logging.info("📱 Attempting to queue WhatsApp message task")
        try:
            whatsapp_task = send_whatsapp_message_delivery_partner.apply_async(
                args=[
                    shipper.phone_number,
                    shipper.team_member_name,
                    order.order_ID,
                    pick_up_address,
                    order.user_uuid.first_name,
                    order.user_uuid.phone_number,
                    drop_address,
                    google_place_link,
                    str(order.grand_total),
                    payment_mode,
                    delivery_slot,
                    pickup_time,
                    deliverylink

                ],
                countdown=2
            )
            logging.info(f"✅ WhatsApp task queued with ID: {whatsapp_task.id}")
        except Exception as whatsapp_err:
            logging.error(
                f"❌ WhatsApp task queue failed: {whatsapp_err}\n{traceback.format_exc()}"
            )

        logging.info("📧 Attempting to queue email task")
        try:
            email_task = send_delivery_assign_email.apply_async(
                args=[
                    shipper.team_member_name,
                    order.order_ID,
                    pick_up_address,
                    drop_address,
                    google_place_link,
                    order.user_uuid.first_name,
                    order.user_uuid.phone_number,
                    str(order.grand_total),
                    payment_mode,
                    delivery_slot,
                    pickup_time,
                    shipper.email,
                    deliverylink
                ],
                countdown=2
            )
            logging.info(f"✅ Email task queued with ID: {email_task.id}")
        except Exception as email_err:
            logging.error(
                f"❌ Email task queue failed: {email_err}\n{traceback.format_exc()}"
            )

        # -------------------------------
        # Notifications (SAFE)
        # -------------------------------
        logging.info("🔔 Sending customer notifications")
        try:
            title = "Order Assigned"
            description = (
                "Your order has been assigned. "
                "Delivery partner will reach you shortly!"
            )

            logging.info("📲 Sending FCM notification")
            NotificationInit(
                order.user_uuid.fcm_token,
                order,
                title,
                description,
                order.user_uuid,
            )
            logging.info("✅ FCM notification sent")

            logging.info("📧 Sending customer email")
            EmailSend(
                title,
                description,
                [order.user_uuid.email],
                False,
            )
            logging.info("✅ Customer email sent")
            otp = generate_delivery_otp()

            DeliveryOTP.objects.update_or_create(
                order=order,
                defaults={"otp": otp, "is_verified": False}
            )
            title = status
            if order.user_uuid.opt_in:
                logging.info("📱 Queuing customer WhatsApp message")
                customer_whatsapp_task = send_whatsapp_message.apply_async(
                    args=[
                        order.user_uuid.phone_number,
                        order.order_ID,
                        order.user_uuid.first_name,
                        "delivery_assining_gen",
                    ],
                    countdown=2,
                )
                delivery_otp = send_whatsapp_message.apply_async(
                    args=[
                        order.user_uuid.phone_number,
                        otp,
                        datetime.now().strftime("%d %b %Y"),
                        "delivery_otp",
                    ],
                    countdown=2,
                )
                logging.info(f"✅ Customer WhatsApp queued with ID: {customer_whatsapp_task.id}")
                logging.info(f"✅ Delivery OTP WhatsApp queued with ID: {delivery_otp.id}")
            else:
                logging.info("ℹ️ Customer opted out of WhatsApp notifications")

        except Exception as notify_err:
            logging.error(
                f"⚠️ Customer notification failed (non-blocking): "
                f"{notify_err}\n{traceback.format_exc()}"
            )

        # -------------------------------
        # Success response
        # -------------------------------
        logging.info(f"🎉 save_shipper COMPLETED SUCCESSFULLY for order {order.order_ID}")
        
        response_data = {
            "success": True,
            "page": page,
            "search": search,
            "product_date": product_date,
        }
        logging.info(f"📤 Returning success response: {response_data}")
        
        return JsonResponse(response_data)

    except Exception as e:
        logging.error(
            f"❌❌❌ save_shipper CRITICAL EXCEPTION CAUGHT ❌❌❌\n"
            f"Exception Type: {type(e).__name__}\n"
            f"Exception Message: {str(e)}\n"
            f"Full Traceback:\n{traceback.format_exc()}"
        )
        
        error_response = {
            "success": False,
            "message": "Something went wrong",
            "page": page,
            "search": search,
            "product_date": product_date,
        }
        logging.error(f"📤 Returning error response: {error_response}")
        
        return JsonResponse(error_response)


import random

def generate_delivery_otp():
    return str(random.randint(100000, 999999))



def save_order_status(request):
    if request.method == 'POST':
        page = request.POST.get('page')
        search = request.POST.get('search')
        product_date = request.POST.get('product_date')
        try:
            order_uuid = request.POST.get('order_uuid')
            status = request.POST.get('selected_value').strip()
            order = Orders.objects.filter(uuid=order_uuid).first()
            order.order_status = status
            order.save()
            if status == "Order Packed":
                try:
                    combined_datetime = datetime.combine(order.delivery_slot_date, order.delivery_slot_time)
                except:
                    combined_datetime = datetime.now()
                time_dif_with_del_slot = combined_datetime.astimezone() - datetime.now().astimezone()
                duration_in_s_data = time_dif_with_del_slot.total_seconds()
                hours_del = divmod(duration_in_s_data, 3600)[0]

                if hours_del < 0:
                    order.color_status = "Red"
                    order.color_status_updation_time = datetime.now()
                    order.save()

                elif hours_del < 1 and hours_del > 0:
                    order.color_status = "Orange"
                    order.color_status_updation_time = datetime.now()
                    order.save()
                else:
                    order.color_status = "White"
                    order.color_status_updation_time = datetime.now()
                    order.save()
            if status == "Failed" or status == "Cancelled":
                order.color_status = "Dark Red"
                order.color_status_updation_time = datetime.now()
                order.save()
                try:
                    shipper_uuid = order.delivery_boy
                    shipper = DeliveryBoys.objects.filter(pk=shipper_uuid).first()
                    if shipper.delivery_boy_status == "assigned":
                        shipper.delivery_boy_status = "not assigned"
                        shipper.save()
                except:
                    pass

            
            # send notification
            try:
                initialize_fcm_app()
            except:
                pass
            if status == "Order Packed":
                temp_name = "order_ready_for_pickup"
                description = f"Your order {order.order_ID} is ready for pickup. Please visit our shop to collect it at your convenience.Thank you for choosing us!"
                if order.order_type != "Pick Up":
                    NotificationInit(order.user_uuid.fcm_token, order, status, description, order.user_uuid)
                    return JsonResponse({'status': 'success', 'page': page, 'search': search, 'product_date': product_date})

            elif status == "Delivery Assigned":
                temp_name = "delivery_assigned_general"
                description = f"""Hi {order.user_uuid.first_name},

            Your order {order.order_ID} has been assigned to a delivery partner.

            It will be delivered to your doorstep shortly.

            Thank you for choosing Navya Bakers!"""

                
            elif status == "Delivered" or status == "Collected":
                temp_name = "delivered_general"

                if order.order_type == "Pick Up":
                    status = "Order Collected"
                    description = f"""Hi {order.user_uuid.first_name},

                Your order {order.order_ID} has been successfully collected.

                We hope you enjoy your purchase. Thank you for choosing Navya Bakers!"""
                else:
                    status = "Order Delivered"
                    description = f"""Hi {order.user_uuid.first_name},

                Your order {order.order_ID} has been successfully delivered.

                We hope you enjoyed your experience. Your feedback means a lot to us and helps us improve!

                Thank you for choosing Navya Bakers!""" 
            elif status == "Failed":
                temp_name = "deliveryfailedcase_general"
                description = f"""Hi {order.user_uuid.first_name},

            We’re sorry to inform you that your order {order.order_ID} could not be completed.

            If you need any assistance or would like to place the order again, please feel free to contact our support team.

            Thank you for your understanding."""

            elif status == "Cancelled":
                temp_name = "custom_delivery_failedcase"
                description = f"""Hi {order.user_uuid.first_name},

            Your order {order.order_ID} has been cancelled successfully.

            If this was not intended or you need any help, please contact our support team.

            We hope to serve you again soon!"""
            # else:
            #     description = "Your Order Status will be updated soon!"
            title = status
            NotificationInit(order.user_uuid.fcm_token, order, title, description, order.user_uuid)
            EmailSend(title, description, [order.user_uuid.email], False)
            if order.user_uuid.opt_in == True:
                send_whatsapp_message.apply_async(args=[order.user_uuid.phone_number, order.order_ID,order.user_uuid.first_name,temp_name],countdown=2 )
            return JsonResponse({'status': 'success', 'page': page, 'search': search, 'product_date': product_date})
        except Exception as e:
            return JsonResponse(
                {'status': 'fail', 'msg': str(e), 'page': page, 'search': search, 'product_date': product_date})


@csrf_exempt
def save_customorder_status(request):
    if request.method == 'POST':
        try:
            order_uuid = request.POST.get('order_uuid')
            status = request.POST.get('selected_value')

            order = Orders.objects.filter(uuid=order_uuid).first()

            if not order:
                return JsonResponse({'status': 'fail', 'msg': 'Order not found'})

            # Ensure the status cannot be changed once set to "Contacted"
            if order.order_status == "Contacted":
                return JsonResponse(
                    {'status': 'fail', 'msg': 'Order status cannot be changed after being set to Contacted'})

            order.order_status = status
            order.save()

            return JsonResponse({'status': 'success'})
        except Exception as e:
            return JsonResponse({'status': 'fail', 'msg': str(e)})



@csrf_exempt
def save_customorder_data(request):
    if request.method != 'POST':
        return JsonResponse({'status': 'error', 'message': 'Invalid request method'})

    # --- Fetch order ---
    order_uuid = request.POST.get('order_ID')
    order = Orders.objects.filter(order_ID=order_uuid).first()

    if not order:
        return JsonResponse({'status': 'fail', 'msg': 'Order not found'})

    # --- Basic fields ---
    quantity = request.POST.get("quantity")
    quantity_unit = request.POST.get("quantity_unit")
    delivery_type = request.POST.get("delivery_type")

    del_type = None

    # Reset order fields
    order.delivery_slot_date = None
    order.delivery_slot_time = None

    # --- Delivery Type Logic ---
    if delivery_type == "pickup":
        del_type = "Pick Up"

        pickup_date = request.POST.get("pickup_date")
        if pickup_date:
            order.delivery_slot_date = parse_date(pickup_date)

    elif delivery_type == "home_delivery":
        del_type = "Home Delivery"

        delivery_date = request.POST.get("delivery_date")
        slot_id = request.POST.get("time_slot")  # should be slot ID

        if delivery_date:
            order.delivery_slot_date = parse_date(delivery_date)

        if slot_id:
            try:
                slot = DeliverySlot.objects.get(id=slot_id)
                order.delivery_slot_time = slot.start_time
            except DeliverySlot.DoesNotExist:
                order.delivery_slot_time = None

    # Save Order delivery info
    order.save()

    # --- Save / Update Custom Order Tracking ---
    custom_track, created = CustomOrderTracking.objects.get_or_create(order_id=order)

    custom_track.quantity = quantity
    custom_track.quantity_unit = quantity_unit
    custom_track.delivery_type = del_type

    custom_track.save()

    # --- Update other order fields using form ---
    form = CustomOrderForm(request.POST, instance=order)

    if form.is_valid():
        form.save()
        return JsonResponse({'status': 'success', 'message': 'Order updated successfully'})
    else:
        return JsonResponse({'status': 'error', 'errors': form.errors})

def order_status_update_to_viewed(request, orderID):
    if request.method == 'POST':
        page = request.POST.get('page')
        search = request.POST.get('search')
        product_date = request.POST.get('product_date')
        try:
            order = Orders.objects.filter(order_ID=orderID).first()
            if order.order_status == 'Confirmed':
                order.order_status = 'Viewed'
                order.save()
                # send notification

                title = "Order Viewed"
                description = "Your Order Viewed by the Merchant and will be packed soon!"
                temp_name ="order_viewed" 
                NotificationInit(order.user_uuid.fcm_token, order, title, description, order.user_uuid)
                # EmailSend(title, description, [order.user_uuid.email], False)
                # if order.user_uuid.opt_in == True:
                #     send_whatsapp_message.apply_async(args=[order.user_uuid.phone_number,orderID,order.user_uuid.first_name,temp_name],countdown=2 )
                # try:
                #     initialize_fcm_app()
                #     send_notification.s(order.user_uuid.fcm_token, order, title, description, order.user_uuid)
                # except Exception as e:
                #     logging.error(f"Notification Send Failed on Order Viewed: {e}")
                #     # print(e)
                #
                #     pass
                return JsonResponse({'status': 'success', 'page': page, 'search': search, 'product_date': product_date})
            return JsonResponse({'status': 'fail', 'page': page, 'search': search, 'product_date': product_date})


        except Exception as e:
            return JsonResponse(
                {'status': 'fail', 'msg': str(e), 'page': page, 'search': search, 'product_date': product_date})

#........................................ promotioins start ...........................................
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def coupon_list(request):
    """
    Coupons  menu on Promotions section in the side bar will list the coupons.
    It is only available for super admin and nbc admin.
    nbc admin can create, edit and delete coupons.
    this defined coupons will list on apps.
    if they are eligible they can use the coupons.
    :param request:
    :return:
    """
    coupon = Coupons.objects.order_by("-created_at")
    paginator = Paginator(coupon, 6)
    page = request.GET.get("page")
    try:
        coupon_list_data = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        coupon_list_data = paginator.get_page(1)
    except EmptyPage:
        coupon_list_data = paginator.get_page(paginator.num_pages)
    context = {"coupon_data": coupon_list_data, "promotion_active": "active"}
    return render(request, "coupon_list.html", context)

@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def coupon_delete(request, coupon_id):

    """
     Coupons  menu on Promotions section in the side bar will list the coupons.
    Deleting a coupon. for each coupon listed on
    coupon list, we have a delete button. clicking on
    the delete button will open a confirm dialog box. if you confirm
    it will delete the coupon.
    :param request:
    :param coupon_id:
    :return:
    """
    coupon = Coupons.objects.filter(pk=coupon_id)

    # there are ads exists for coupons so we couldn't delete the coupon
    # since we add a warning message
    if coupon:
        ads = Ads.objects.filter(Coupon=coupon_id)

        if ads:
            messages.add_message(request, messages.WARNING,
                                 "Your coupon used in ads! couldn't delete coupon")
            # messages.error(request, "Your coupon used in ads! couldn't delete coupon")
        else:
            coupon.delete()
    return redirect("list_coupons")

@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def coupon_edit(request, coupon_id):
    """
    Coupons  menu on Promotions section in the side bar will list the coupons.
    Editing a coupon. for each coupon listed, we have a edit button. clicking on
    the edit button will open a form. fields are coupon_name and coupon_code,
    coupon_description, icon, coupon_on, applicable_category, applicable_subcategory,
    applicable_product, applicable_sku, minimum_total_bill_amount, coupon_type,
    discount_amount, no_of_users, validity_start_date, validity_end_date, terms_and_conditions.
    validations listed on the forms. applicable_category, applicable_subcategory,
    applicable_product, applicable_sku are dynamic fields based on coupon_on field.
    coupon maybe amount based or percentage based. the field used to indicate is
    coupon_type.

    Imp: image refresh and showing logic is handled via javascript.It is field wise and based on id.

    :param request:
    :param coupon_id:
    :return:
    """
    errors = []

    coupon = Coupons.objects.filter(pk=coupon_id).first()
    if request.method == "POST":
        # since request not editable make a copy and update the value
        post_data = request.POST.copy()

        # null value raises validation error so remove the null value.
        # null value appears since the drop down on select have a field select all.
        # if we select the value using the select all option we need to remove the "".
        if '' in post_data.getlist('ApplicableCategory', None):
            category_list = post_data.getlist('ApplicableCategory', None)
            if category_list:
                category_list.remove('')
            post_data.setlist('ApplicableCategory', category_list)

        if '' in post_data.getlist('ApplicableSubCategory', None):
            sub_category_list = post_data.getlist('ApplicableSubCategory', None)
            if sub_category_list:
                sub_category_list.remove('')
            post_data.setlist('ApplicableSubCategory', sub_category_list)
        if '' in post_data.getlist('ApplicableProduct', None):
            product_list = post_data.getlist('ApplicableProduct', None)
            if product_list:
                product_list.remove('')
            post_data.setlist('ApplicableProduct', product_list)

        if '' in post_data.getlist('ApplicableSku', None):
            sku_list = post_data.getlist('ApplicableSku', None)
            if sku_list:
                sku_list.remove('')
            post_data.setlist('ApplicableSku', sku_list)

        coupon_on = post_data.get("CouponOn", None)

        # since coupon_on based dynamic field and coupon_type dynamic fields exists
        # other related fields should populate null except the selected required values

        try:
            if coupon_on == "Category":
                del (post_data['ApplicableSubCategory'])
                del (post_data['ApplicableProduct'])
                del (post_data['ApplicableSku'])

            elif coupon_on == "SubCategory":
                del (post_data['ApplicableProduct'])
                del (post_data['ApplicableSku'])

            elif coupon_on == "Product":
                del (post_data['ApplicableSku'])
        except:
            pass

        form = CouponForm(post_data, request.FILES, instance=coupon)

        if form.is_valid():
            if request.POST.get('image', ''):
                form.instance.Icon = None
            if request.POST.get("CouponType") == "amount":
                form.instance.DiscountPercentage = None
                form.instance.MaxDiscountAmountForPercentage = None
            else:
                form.instance.DiscountAmount = None
            try:
                form.save()
            # one type of validation error couldn't managed itself in the form.
            # please refer coupon form
            except  ValidationError as e:
                error_data = e.message_dict
                form.add_error(list(error_data.keys())[0], e.messages[0])
                return JsonResponse({"success": False, 'errors': form.errors})

            return JsonResponse({'success': True})

        else:
            errors.append(form.errors)
            return JsonResponse({'success': False, 'errors': form.errors})
    else:
        form = CouponForm(instance=coupon)
    context = {
        "errors": errors,
        "coupon_form": form,
        "coupon_id": coupon.pk,
        "promotion_active": "active",
        "coupon_data": coupon.CouponOn,
        "coupon_type": coupon.CouponType
    }
    return render(request, "coupon_edit.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def coupon_add(request):
    """
    Coupons  menu on Promotions section in the side bar will list the coupons.
    adding a coupon.We have a add coupon button. clicking on
    the add button will open a form. fields are coupon_name and coupon_code,
    coupon_description, icon, coupon_on, applicable_category, applicable_subcategory,
    applicable_product, applicable_sku, minimum_total_bill_amount, coupon_type,
    discount_amount, no_of_users, validity_start_date, validity_end_date, terms_and_conditions.
    validations listed on the forms. applicable_category, applicable_subcategory,
    applicable_product, applicable_sku are dynamic fields based on coupon_on field.
    coupon maybe amount based or percentage based. the field used to indicate is
    coupon_type.

    Imp: image refresh and showing logic is handled via javascript.It is field wise and based on id.
    :param request:
    :return:
    """
    errors = []
    if request.method == "POST":
        post_data = request.POST.copy()

        # null value raises validation error so remove the null value.
        # null value appears since the drop down on select have a field select all.
        # if we select the value using the select all option we need to remove the "".

        if '' in post_data.getlist('ApplicableCategory', None):
            category_list = post_data.getlist('ApplicableCategory', None)
            if category_list:
                category_list.remove('')
            post_data.setlist('ApplicableCategory', category_list)

        if '' in post_data.getlist('ApplicableSubCategory', None):
            sub_category_list = post_data.getlist('ApplicableSubCategory', None)
            if sub_category_list:
                sub_category_list.remove('')
            post_data.setlist('ApplicableSubCategory', sub_category_list)
        if '' in post_data.getlist('ApplicableProduct', None):
            product_list = post_data.getlist('ApplicableProduct', None)
            if product_list:
                product_list.remove('')
            post_data.setlist('ApplicableProduct', product_list)

        if '' in post_data.getlist('ApplicableSku', None):
            sku_list = post_data.getlist('ApplicableSku', None)
            if sku_list:
                sku_list.remove('')
            post_data.setlist('ApplicableSku', sku_list)

        coupon_on = post_data.get("CouponOn", None)


        # since coupon_on based dynamic field and coupon_type dynamic fields exists
        # other related fields should populate null except the selected required values

        try:
            if coupon_on == "Category":
                del (post_data['ApplicableSubCategory'])
                del (post_data['ApplicableProduct'])
                del (post_data['ApplicableSku'])

            elif coupon_on == "SubCategory":
                del (post_data['ApplicableProduct'])
                del (post_data['ApplicableSku'])

            elif coupon_on == "Product":
                del (post_data['ApplicableSku'])
        except:
            pass

        coupon_form = CouponForm(post_data, request.FILES)

        if coupon_form.is_valid():
            if request.POST.get('image', ''):
                coupon_form.instance.Icon = None
            if request.POST.get("CouponType") == "amount":
                coupon_form.instance.DiscountPercentage = None
                coupon_form.instance.MaxDiscountAmountForPercentage = None
            else:
                coupon_form.instance.DiscountAmount = None

            try:
                coupon_form.save()
            except  ValidationError as e:

                # one type of validation error couldn't managed itself in the form.
                # please refer coupon form

                error_data = e.message_dict
                coupon_form.add_error(list(error_data.keys())[0], e.messages[0])
                return JsonResponse({"success": False, 'errors': coupon_form.errors})
            return JsonResponse({'success': True})
        else:
            errors.append(coupon_form.errors)
            return JsonResponse({'success': False, 'errors': coupon_form.errors})

    else:
        coupon_form = CouponForm()
        errors = []
    context = {
        "errors": errors,
        "coupon_form": coupon_form,
        "promotion_active": "active",
        "coupon_data": "Category",
        "coupon_type": "amount"
    }
    return render(request, "coupon_add.html", context)


@user_passes_test(check_user_role(["Super Admin", "NBC Admin", "PU Admin"]))
def delivery_slot_list(request):
    """
    In NBC admin dashboard, On the orders section we have order slot menu. clicking on the
    menu will open the slot list. we can add, edit and view the slots from here.
    :param request:
    :return:
    """

    delivery_slots = DeliverySlot.objects.order_by('-created_at')  # .filter(shop = shop_id)
    paginator = Paginator(delivery_slots, 6)
    page = request.GET.get("page")
    try:
        delivery_slot = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        delivery_slot = paginator.get_page(1)
    except EmptyPage:
        delivery_slot = paginator.get_page(paginator.num_pages)
    context = {"delivery_slot_data": delivery_slot, "orders_active": "active"}
    return render(request, "delivery_slot_list.html", context)


@require_POST
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def delivery_slot_delete(request, delivery_slot_id):
    """
    In NBC admin dashboard, On the orders section we have order slot menu.
    on the slots listed, for each slot we have a delete button. Clicking on
    the delete button will open a dialog box. clicking on the confirm will
    delete the delivery slot.

    :param request:
    :param delivery_slot_id:
    :return:
    """
    delivery_slot = DeliverySlot.objects.filter(pk=delivery_slot_id)
    if delivery_slot:
        delivery_slot.delete()
    return redirect("delivery_slot_list")


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def delivery_slot_edit(request, delivery_slot_id):
    """
    In NBC admin dashboard, On the orders section we have order slot menu.
    on the slots listed, for each slot we have a edit button. Clicking on
    the edit button will open a edit form. fields of the form are  start_time,
    end_time, is_available, status. based on the availability status, it is
    showing on the app(select Delivery slots).

    :param request:
    :param delivery_slot_id:
    :return:
    """
    errors = []

    delivery_slot = DeliverySlot.objects.filter(pk=delivery_slot_id).first()
    if request.method == "POST":
        form = DeliverySlotForm(request.POST, instance=delivery_slot)  # request = request
        if form.is_valid():
            del_slot = form.save()

            return redirect("delivery_slot_list")

        else:
            errors.append(form.errors)
    else:
        form = DeliverySlotForm(instance=delivery_slot)  # , request = request
    context = {"delivery_slot_form": form, "orders_active": "active"}
    return render(request, "delivery_slot_edit.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def delivery_slot_add(request):
    """
        In NBC admin dashboard, On the orders section we have order slot menu.
    Clicking on the Add Delivery Slot button will open a add delivery slot form.
    fields of the form are  start_time, end_time, is_available, status.

    :param request:
    :return:
    """
    errors = []

    if request.method == "POST":
        form = DeliverySlotForm(request.POST)  # , request = request
        if form.is_valid():
            del_slot = form.save()
            return redirect("delivery_slot_list")
        else:
            errors.append(form.errors)
            context = {
                "errors": errors,
                "delivery_slot_form": form,
                "orders_active": "active"
            }
            return render(request, "delivery_slot_add.html", context)
    else:
        form = DeliverySlotForm()  # request = request

    context = {
        "errors": errors,
        "delivery_slot_form": form,
        "orders_active": "active"

    }
    return render(request, "delivery_slot_add.html", context)

@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def discount_list(request):
    """
    Discount  menu on Promotions section in the side bar will list the discounts.
    It is only available for super admin and nbc admin.
    nbc admin can create, edit and delete discounts.
    this defined discount will shown on product listing with a strike.
    """
    discount = Discount.objects.order_by("-created_at")
    paginator = Paginator(discount, 6)
    page = request.GET.get("page")
    try:
        discount_list_data = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        discount_list_data = paginator.get_page(1)
    except EmptyPage:
        discount_list_data = paginator.get_page(paginator.num_pages)
    context = {"discount_data": discount_list_data, "promotion_active": "active"}
    return render(request, "discount_list.html", context)

@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def discount_delete(request, discount_id):
    try:
        discount = Discount.objects.get(pk=discount_id)
    except Discount.DoesNotExist:
        messages.error(request, "Discount not found.")
        return redirect("list_discounts")

    ads = Ads.objects.filter(Discount=discount)

    if ads.exists():
        messages.warning(
            request,
            "This discount is used in ads and cannot be deleted."
        )
    else:
        discount.delete()
        messages.success(request, "Discount deleted successfully.")

    return redirect("list_discounts")

@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def discount_edit(request, discount_id):
    """
    Discount  menu on Promotions section in the side bar will list the discounts.
    Editing a discount. for each discount listed, we have a edit button. clicking on
    the edit button will open a form. fields are discount_name and discount_code,
    discount_description, discount_on, applicable_category, applicable_subcategory,
    applicable_product, applicable_sku,
    discount_percentage.
    validations listed on the forms. applicable_category, applicable_subcategory,
    applicable_product, applicable_sku are dynamic fields based on discount_on field.
    discount is percentage based.
     Imp: image refresh and showing logic is handled via javascript.It is field wise and based on id.
    :param request:
    :param discount_id:
    :return:
    """
    errors = []

    discount = Discount.objects.filter(pk=discount_id).first()
    if request.method == "POST":
        post_data = request.POST.copy()
        # null value raises validation error so remove the null value.
        # null value appears since the drop down on select have a field select all.
        # if we select the value using the select all option we need to remove the "".

        if '' in post_data.getlist('ApplicableCategory', None):
            category_list = post_data.getlist('ApplicableCategory', None)
            if category_list:
                category_list.remove('')
            post_data.setlist('ApplicableCategory', category_list)

        if '' in post_data.getlist('ApplicableSubCategory', None):
            sub_category_list = post_data.getlist('ApplicableSubCategory', None)
            if sub_category_list:
                sub_category_list.remove('')
            post_data.setlist('ApplicableSubCategory', sub_category_list)
        if '' in post_data.getlist('ApplicableProduct', None):
            product_list = post_data.getlist('ApplicableProduct', None)
            if product_list:
                product_list.remove('')
            post_data.setlist('ApplicableProduct', product_list)

        if '' in post_data.getlist('ApplicableSku', None):
            sku_list = post_data.getlist('ApplicableSku', None)
            if sku_list:
                sku_list.remove('')
            post_data.setlist('ApplicableSku', sku_list)

        discount_on = post_data.get("DiscountOn", None)
        # since discount_on based dynamic field and coupon_type dynamic fields exists
        # other related fields should populate null except the selected required values

        try:
            if discount_on == "Category":
                del (post_data['ApplicableSubCategory'])
                del (post_data['ApplicableProduct'])
                del (post_data['ApplicableSku'])

            elif discount_on == "SubCategory":
                del (post_data['ApplicableProduct'])
                del (post_data['ApplicableSku'])

            elif discount_on == "Product":
                del (post_data['ApplicableSku'])
        except:
            pass

        form = DiscountForm(post_data, request.FILES, instance=discount)
        if form.is_valid():

            if request.POST.get('image2', ''):
                form.instance.StandardImage = None
            if request.POST.get('image3', ''):
                form.instance.BannerImage = None
            form.save()
            return JsonResponse({'success': True})

        else:
            errors.append(form.errors)
            return JsonResponse({'success': False, 'errors': form.errors})
    else:
        form = DiscountForm(instance=discount)
    
    # Get image URLs for display
    standard_image_url = ""
    banner_image_url = ""
    
    if discount.StandardImage:
        standard_image_url = discount.StandardImage.url
    else:
        standard_image_url = "/media/standard_images/standard_image.jpg"
    
    if discount.BannerImage:
        banner_image_url = discount.BannerImage.url
    else:
        banner_image_url = "/media/banners/banner.jpg"
    
    context = {
        "errors": errors,
        "discount_form": form,
        "discount_id": discount.pk,
        "promotion_active": "active",
        "discount_data": discount.DiscountOn,
        "standard_image_url": standard_image_url,
        "banner_image_url": banner_image_url,
    }
    return render(request, "discount_edit.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def discount_add(request):
    """
    Discount  menu on Promotions section in the side bar will list the discounts.
    adding a discount.We have a add coupon discount. clicking on
    the add button will open a form. fields are discount_name and discount_code,
    discount_description, discount_on, applicable_category, applicable_subcategory,
    applicable_product, applicable_sku,
    discount_percentage.
    validations listed on the forms. applicable_category, applicable_subcategory,
    applicable_product, applicable_sku are dynamic fields based on discount_on field.
    discount is percentage based.
     Imp: image refresh and showing logic is handled via javascript.It is field wise and based on id.
    """
    errors = []
    if request.method == "POST":
        post_data = request.POST.copy()
        
        # DEBUG: Print form data
        print("=== DEBUG: Form POST Data ===")
        print(f"POST data keys: {list(post_data.keys())}")
        
        # DEBUG: Print FILES data
        print(f"FILES data: {request.FILES}")
        if request.FILES:
            print(f"StandardImage in FILES: {'StandardImage' in request.FILES}")
            print(f"BannerImage in FILES: {'BannerImage' in request.FILES}")
            if 'StandardImage' in request.FILES:
                print(f"StandardImage name: {request.FILES['StandardImage'].name}")
                print(f"StandardImage size: {request.FILES['StandardImage'].size}")
            if 'BannerImage' in request.FILES:
                print(f"BannerImage name: {request.FILES['BannerImage'].name}")
                print(f"BannerImage size: {request.FILES['BannerImage'].size}")
        
        # null value raises validation error so remove the null value.
        # null value appears since the drop down on select have a field select all.
        # if we select the value using the select all option we need to remove the "".
        if '' in post_data.getlist('ApplicableCategory', None):
            category_list = post_data.getlist('ApplicableCategory', None)
            if category_list:
                category_list.remove('')
            post_data.setlist('ApplicableCategory', category_list)

        if '' in post_data.getlist('ApplicableSubCategory', None):
            sub_category_list = post_data.getlist('ApplicableSubCategory', None)
            if sub_category_list:
                sub_category_list.remove('')
            post_data.setlist('ApplicableSubCategory', sub_category_list)
        if '' in post_data.getlist('ApplicableProduct', None):
            product_list = post_data.getlist('ApplicableProduct', None)
            if product_list:
                product_list.remove('')
            post_data.setlist('ApplicableProduct', product_list)

        if '' in post_data.getlist('ApplicableSku', None):
            sku_list = post_data.getlist('ApplicableSku', None)
            if sku_list:
                sku_list.remove('')
            post_data.setlist('ApplicableSku', sku_list)

        discount_on = post_data.get("DiscountOn", None)

        # since discount_on based dynamic field and coupon_type dynamic fields exists
        # other related fields should populate null except the selected required values

        try:
            if discount_on == "Category":
                del (post_data['ApplicableSubCategory'])
                del (post_data['ApplicableProduct'])
                del (post_data['ApplicableSku'])

            elif discount_on == "SubCategory":
                del (post_data['ApplicableProduct'])
                del (post_data['ApplicableSku'])

            elif discount_on == "Product":
                del (post_data['ApplicableSku'])
        except:
            pass

        discount_form = DiscountForm(post_data, request.FILES)
        
        # DEBUG: Check if form is valid
        print("\n=== DEBUG: Form Validation ===")
        print(f"Form is valid: {discount_form.is_valid()}")
        if not discount_form.is_valid():
            print(f"Form errors: {discount_form.errors}")
            print(f"Form cleaned_data keys: {list(discount_form.cleaned_data.keys()) if discount_form.is_bound else 'Form not bound'}")
        
        if discount_form.is_valid():
            # DEBUG: Print cleaned data before saving
            print("\n=== DEBUG: Form Cleaned Data ===")
            cleaned_data = discount_form.cleaned_data
            for key, value in cleaned_data.items():
                print(f"{key}: {value}")
            
            # DEBUG: Check image fields
            if 'StandardImage' in cleaned_data:
                print(f"StandardImage value: {cleaned_data['StandardImage']}")
                print(f"StandardImage type: {type(cleaned_data['StandardImage'])}")
            if 'BannerImage' in cleaned_data:
                print(f"BannerImage value: {cleaned_data['BannerImage']}")
                print(f"BannerImage type: {type(cleaned_data['BannerImage'])}")
            
            # image submission
            if request.POST.get('image2', ''):
                print("DEBUG: image2 reset detected - setting StandardImage to None")
                discount_form.instance.StandardImage = None
            if request.POST.get('image3', ''):
                print("DEBUG: image3 reset detected - setting BannerImage to None")
                discount_form.instance.BannerImage = None
            
            # Save the form
            discount_instance = discount_form.save()
            print(f"\n=== DEBUG: Instance Saved ===")
            print(f"Instance ID: {discount_instance.id}")
            print(f"StandardImage saved: {discount_instance.StandardImage}")
            print(f"BannerImage saved: {discount_instance.BannerImage}")
            
            # Check if images are actually saved
            if discount_instance.StandardImage:
                print(f"StandardImage path: {discount_instance.StandardImage.path}")
                print(f"StandardImage URL: {discount_instance.StandardImage.url}")
            if discount_instance.BannerImage:
                print(f"BannerImage path: {discount_instance.BannerImage.path}")
                print(f"BannerImage URL: {discount_instance.BannerImage.url}")
            
            return JsonResponse({'success': True})
        else:
            errors.append(discount_form.errors)
            return JsonResponse({'success': False, 'errors': discount_form.errors})

    else:
        discount_form = DiscountForm()  # Fixed: Initialize form for GET request
        print("DEBUG: GET request - initializing empty form")
        errors = []
    
    context = {
        "errors": errors,
        "discount_form": discount_form,
        "promotion_active": "active",
        "discount_data": "Category"
    }
    return render(request, "discount_add.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def image_field_style(request):
    link_1 = request.GET.get('image_link_1', '/media/icons/icon.jpg')
    link_2 = request.GET.get('image_link_2', '/media/standard_images/standard_image.jpg')
    link_3 = request.GET.get('image_link_3', '/media/banners/banner.jpg')

    image_field1 = render_to_string('image_field.html',
                                    {'class_name': 'upload', 'image_reset_button': 'image_reset_button1',
                                     'field_name': 'Icon'})
    image_field2 = render_to_string('image_field.html',
                                    {'class_name': 'upload2', 'image_reset_button': 'image_reset_button2',
                                     'field_name': 'StandardImage', "link": link_2})
    image_field3 = render_to_string('image_field.html',
                                    {'class_name': 'upload3', 'image_reset_button': 'image_reset_button3',
                                     'field_name': 'BannerImage', "link": link_3})
    return JsonResponse(
        {'image_field1': image_field1, 'image_field2': image_field2, 'image_field3': image_field3, "link_1": link_1,
         "link_2": link_2, "link_3": link_3})



@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def ad_list(request):
    """
    Ads menu on Promotions section in the side bar will list the ads.
    It is only available for super admin and nbc admin.
    nbc admin can create, edit and delete ads.
    These ads are used in carousel in the app and also the bottom banner.
    We are defining the placement of ads while creating.

    :param request:
    :return:
    """
    ad = Ads.objects.order_by("-created_at")
    paginator = Paginator(ad, 6)
    page = request.GET.get("page")
    try:
        ad_list_data = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        ad_list_data = paginator.get_page(1)
    except EmptyPage:
        ad_list_data = paginator.get_page(paginator.num_pages)
    context = {"ad_data": ad_list_data, "promotion_active": "active"}
    return render(request, "ad_list.html", context)

@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def ad_delete(request, ad_id):
    """
    Ads  menu on Promotions section in the side bar will list the ads.
    Deleting a ad. for each ads listed on
    ads list, we have a delete button. clicking on
    the delete button will open a confirm dialog box. if you confirm
    it will delete the ad.
    :param request:
    :param ad_id:
    :return:
    """
    ad = Ads.objects.filter(pk=ad_id)
    if ad:
        ad.delete()
    return redirect("list_ad")

@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def ad_edit(request, ad_id):
    """
    Ads menu on Promotions section in the side bar will list the ads.
    Editing a ad. for each ad listed, we have a edit button. clicking on
    the edit button will open a form. fields are placement_choices and AdTitle,
    AdDescription, AdPlacement, AdType, StandardImage,
    BannerImage, Discount,
    Coupon, Product.
    validations listed on the forms. Discount, Coupon, Product are dynamic fields based on AdType field.
    On submitting we can edit a ad already existing.
    Imp: image refresh and showing logic is handled via javascript.It is field wise and based on id.
    :param request:
    :param ad_id:
    :return:
    """
    errors = []

    ad = Ads.objects.filter(pk=ad_id).first()
    if request.method == "POST":

        form = AdForm(request.POST, request.FILES, instance=ad)

        coupon = request.POST.get("Coupon", "")
        discount = request.POST.get("Discount", "")

        product = request.POST.get("Product", "")

        if form.is_valid():
            ad = form.save(commit=False)

            # dyanamic entering of  adtype based on the form value
            if ad.AdType == "Coupon":
                try:
                    coupon_instance = Coupons.objects.filter(pk=coupon).first()
                    ad.Coupon = coupon_instance
                except:
                    pass

            elif ad.AdType == "Discount":
                try:
                    discount_instance = Discount.objects.filter(pk=discount).first()
                    ad.Discount = discount_instance
                except:
                    pass

            elif ad.AdType == "Product":
                try:
                    product_instance = Products.objects.filter(pk=product).first()
                    ad.Product = product_instance
                except:
                    pass

            # if no image mark it as none
            if request.POST.get('image2', ''):
                form.instance.StandardImage = None
            if request.POST.get('image3', ''):
                form.instance.BannerImage = None
            ad.save()
            return redirect('list_ad')

        else:
            errors.append(form.errors)
    else:
        form = AdForm(instance=ad)
    context = {
        "errors": errors,
        "ad_form": form,
        "promotion_active": "active",
        "ad_data": ad.AdType
    }
    return render(request, "ad_edit.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def ad_add(request):
    """
    Ad  menu on Promotions section in the side bar will list the ads.
    adding a ad.We have a add ad button. clicking on
    the add button will open a form. fields are placement_choices and AdTitle,
    AdDescription, AdPlacement, AdType, StandardImage,
    BannerImage, Discount,
    Coupon, Product.
    validations listed on the forms. Discount, Coupon, Product are dynamic fields based on AdType field.
    Imp: image refresh and showing logic is handled via javascript.It is field wise and based on id.

    """
    errors = []
    if request.method == "POST":

        form = AdForm(request.POST, request.FILES)

        coupon = request.POST.get("Coupon", "")
        discount = request.POST.get("Discount", "")

        product = request.POST.get("Product", "")

        if form.is_valid():
            ad = form.save(commit=False)

            # dyanamic entering of  adtype based on the form

            if ad.AdType == "Coupon":
                try:
                    coupon_instance = Coupons.objects.filter(pk=coupon).first()
                    ad.Coupon = coupon_instance
                except:
                    pass


            elif ad.AdType == "Discount":
                try:
                    discount_instance = Discount.objects.filter(pk=discount).first()
                    ad.Discount = discount_instance
                except:
                    pass

            elif ad.AdType == "Product":
                try:
                    product_instance = Products.objects.filter(pk=product).first()
                    ad.Product = product_instance
                except:
                    pass
            # if no image mark it as none
            if request.POST.get('image2', ''):
                form.instance.StandardImage = None
            if request.POST.get('image3', ''):
                form.instance.BannerImage = None
            form.save()

            return redirect('list_ad')
        else:
            errors.append(form.errors)

            context = {
                "errors": errors,
                "ad_form": form,
                "promotion_active": "active",
                "ad_data": form.instance.AdType
            }
    else:
        form = AdForm()
        errors = []
        context = {
            "errors": errors,
            "ad_form": form,
            "promotion_active": "active",
            "ad_data": "Discount"
        }
    return render(request, "ad_add.html", context)


# ------------------------------------------- promotions end ...........................................

# ................................................ products start ............................................
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def custom_product_add(request):
    """
    Custom Products  menu on Products section in the side bar will list the custom products.
    adding a Custom Product.We have a Add Custom Product button. clicking on
    the add button will open a form. fields are item_name and item_code,
    item_description, veg_or_non_veg_status, min_size, max_size,
    size_unit, availability.
    Imp: Images are stored separately in CustomProductVideo and CustomProductImage.
    multiple image upload supported
    for validations please refer CustomProductForm
    """
    errors = []

    if request.method == "POST":
        custom_product_form = CustomProductForm(request.POST, request.FILES)
        if custom_product_form.is_valid():
            custom_product = custom_product_form.save()

            # Handle image uploads (optional)
            images = request.FILES.getlist('images')
            if images:  # Only process if images are uploaded
                for image in images:
                    CustomProductImage.objects.create(custom_product=custom_product, image=image)

            # Handle video uploads (optional)
            videos = request.FILES.getlist('videos')
            if videos:  # Only process if videos are uploaded
                for video in videos:
                    CustomProductVideo.objects.create(custom_product=custom_product, video=video)

            try:
                # logging operation
                log_user = request.user
                log_type = "product"
                message = "Custom Product Added"
                description = f"Custom Product {custom_product.item_name}  with code {custom_product.item_code} added "
                log(log_user, log_type, message, description)
            except:
                pass

            # After saving, redirect to the listing page
            return redirect('custom_product_listing')

        else:
            errors.append(custom_product_form.errors)
    else:
        custom_product_form = CustomProductForm()

    context = {
        "errors": errors,
        "custom_product_form": custom_product_form,
        "category_active": "active"
    }

    return render(request, "custom_product_add.html", context)

# Custom Product Listing View
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def custom_product_listing(request):
    """
    Custom Products  menu on Products section in the side bar will list the custom products.
    It is only available for super admin and nbc admin.
    nbc admin can create, edit and delete custom products.
    custom product means, customer can define a product by directly contact
    with navya bakers.
    There is a section in the app to create a custom order. The admin can define the customization
    available products in the custom products section.
    :param request:
    :return:
    """
    # Fetch all the custom products (with pagination if needed)
    product_data = CustomProduct.objects.order_by('-created_date')
    paginator = Paginator(product_data, 6)
    page = request.GET.get("page")
    try:
        product_list_data = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        product_list_data = paginator.get_page(1)
    except EmptyPage:
        product_list_data = paginator.get_page(paginator.num_pages)
    context = {"product_data": product_list_data, "category_active": "active"}
    return render(request, 'custom_product_list.html', context)



# Delete Custom Product View
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def delete_custom_product(request, product_id):
    """
    Custom Products  menu on Products section in the side bar will list the custom products.
    Deleting a custom product. for each custom product listed on
    custom product list, we have a delete button. clicking on
    the delete button will open a confirm dialog box. if you confirm
    it will delete the custom product.
    :param request:
    :param product_id:
    :return:
    """
    product = get_object_or_404(CustomProduct, pk=product_id)
    product_data = product

    if request.method == 'POST':
        # Delete the product
        product.delete()
        try:
            # logging operation
            log_user = request.user
            log_type = "product"
            message = "Custom Product deleted"
            description = f"Custom product {product_data.item_name}  with code {product_data.item_code} deleted "
            log(log_user, log_type, message, description)
        except Exception as e:
            logging.error(f"Error on Custom Product Delete(logging): {e}")
            # print(e)
            pass
        return redirect('custom_product_listing')  # Redirect to the listing page after deletion

    return render(request, 'delete_confirmation.html', {'product': product})




@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def custom_product_edit(request, product_id):
    """
    Custom Products  menu on Products section in the side bar will list the custom products.
    Editing a Custom Product. for each custom product listed, we have a edit button. clicking on
    the edit button will open a form. fields are item_name and item_code,
    item_description, veg_or_non_veg_status, min_size, max_size,
    size_unit, availability.
        Imp: Images are stored separately in CustomProductVideo and CustomProductImage.
    multiple image upload supported
    for validations please refer CustomProductForm
    :param request:
    :param product_id:
    :return:
    """
    errors = []
    custom_product = get_object_or_404(CustomProduct, pk=product_id)
    custom_product_form = CustomProductForm(request.POST or None, request.FILES or None, instance=custom_product)

    if request.method == "POST":
        if custom_product_form.is_valid():
            custom_product = custom_product_form.save()

            # Handle image uploads
            images = request.FILES.getlist('images')
            for image in images:
                CustomProductImage.objects.create(custom_product=custom_product, image=image)

            # Handle video uploads
            videos = request.FILES.getlist('videos')
            for video in videos:
                CustomProductVideo.objects.create(custom_product=custom_product, video=video)

            try:
                log_user = request.user
                log_type = "product"
                message = "Custom Product Updated"
                description = f"Product {custom_product.item_name}  with code {custom_product.item_code} updated "
                log(log_user, log_type, message, description)
            except:
                pass

            return redirect('custom_product_listing')  # Redirect to the listing page after saving

        else:
            errors.append(custom_product_form.errors)

    context = {
        "errors": errors,
        "custom_product_form": custom_product_form,
        "custom_product": custom_product
    }

    return render(request, "custom_product_edit.html", context)


@login_required
def delete_custom_image(request, product_id, image_id):
    """
        Custom Products  menu on Products section in the side bar will list the custom products.
        custom product have its associated images and videos which is not listed. when we try to
        add or edit a custom product it will be on form.
        These images and videos are stored in separate table and managed separately.
        access via id.
        Table: CustomProductImage

        We can remove the image with this api.
    :param request:
    :param product_id:
    :param image_id:
    :return:
    """
    image = get_object_or_404(CustomProductImage, id=image_id)
    custom_product_id = image.custom_product.id  # type: ignore
    image.delete()
    try:
        log_user = request.user
        log_type = "product"
        message = "Custom Product Image deleted"
        description = f"Custom product {image.custom_product.item_name}  with code {image.custom_product.item_code} deleted its image"
        LoggingOperation.objects.create(user=log_user, log_type=log_type, message=message, description=description)
    except:
        pass
    return redirect('custom_product_edit', product_id=custom_product_id)


@login_required
def delete_custom_video(request, product_id, video_id):
    """
    Custom Products  menu on Products section in the side bar will list the custom products.
    custom product have its associated images and videos which is not listed. when we try to
    add or edit a custom product it will be on form.
    These images and videos are stored in separate table and managed separately.
    access via id.
    Table: CustomProductVideo
    We can remove the video with this api.

    :param request:
    :param product_id:
    :param video_id:
    :return:
    """
    video = get_object_or_404(CustomProductVideo, id=video_id)
    custom_product_id = video.custom_product.id  # type: ignore

    # Deleting video
    video.delete()

    try:
        # logging operation
        log_user = request.user
        log_type = "product"
        message = "Custom Product Video deleted"
        description = f"Custom product {video.custom_product.item_name}  with code {video.custom_product.item_code} deleted its video"
        log(log_user, log_type, message, description)
    except Exception as e:
        logging.error(f"Custom Product Video Delete(logging): {e}")
        # print(e)
        pass
    return redirect('custom_product_edit', product_id=custom_product_id)


@login_required
def production_unit(request):
    """
    Production unit means where products are manufacturing. Currently only one
    production unit available.
    production units  menu on Sales Unit section in the side bar will list the production unit.
    It is only available for super admin and nbc admin.
    nbc admin can create, edit and delete production unit. Only one production unit could be
    created now. creating more than one production unit is restricted. If no production unit created,
    a add button is available to create one else we hide that button.
    edit and delete button available for the production unit.

    :param request:
    :return:
    """
    production_unit = ProductionUnit.objects.order_by("-created_at")
    paginator = Paginator(production_unit, 10)
    page = request.GET.get("page")
    try:
        production_unit = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        production_unit = paginator.get_page(1)
    except EmptyPage:
        production_unit = paginator.get_page(paginator.num_pages)
    context = {"production_unit_data": production_unit, "sales_unit_active": "active"}
    return render(request, "production_unit_list.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def production_unit_add(request):
    """
    production units  menu on Sales Unit section in the side bar will list the production unit.
    adding a Production unit.We have a Add Production Unit button. clicking on
    the add button will open a form. fields are pu_name, pu_code, pu_location, street, city
    district, state_or_province, pin_code, latitude, longitude, gst, contact_no, email, status

    *NOt implemented yet (Only one production unit, not checking the status)
    We can change the status of pu by open, close or currently not accepting any order. it is by
    changing the status field.


    :param request:
    :return:
    """
    errors = []
    if request.method == "POST":
        production_unit_form = ProductionUnitForm(request.POST)
        if production_unit_form.is_valid():
            production_unit_form.save()
            return redirect("production_unit_list")
        else:
            errors.append(production_unit_form.errors)
            context = {
                "errors": errors,
                "production_unit_form": production_unit_form,
                "show_modal": "modal_hide",
                "sales_unit_active": "active"
            }
            return render(request, "production_unit_add.html", context)
    else:
        production_unit_form = ProductionUnitForm()
        errors = []
    context = {
        "errors": errors,
        "production_unit_form": production_unit_form,
        "sales_unit_active": "active"
    }
    return render(request, "production_unit_add.html", context)


@require_POST
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def production_unit_delete(request, pu_id):
    """
    production units  menu on Sales Unit section in the side bar will list the production unit.
    Deleting a production unit. for each production unit listed on
    production unit list, we have a delete button. clicking on
    the delete button will open a confirm dialog box. if you confirm
    it will delete the production unit.
    :param request:
    :param pu_id:
    :return:
    """
    pu = ProductionUnit.objects.filter(pk=pu_id)
    if pu:
        pu.delete()
    return redirect("production_unit_list")


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def production_unit_edit(request, pu_id=None):
    """
    production units  menu on Sales Unit section in the side bar will list the production unit.
    Editing a production unit. for each production unit listed, we have a edit button. clicking on
    the edit button will open a form. fields are pu_name, pu_code, pu_location, street, city
    district, state_or_province, pin_code, latitude, longitude, gst, contact_no, email, status
    :param request:
    :param pu_id:
    :return:
    """
    pu = get_object_or_404(ProductionUnit, pk=pu_id)
    errors = []
    if request.method == "POST":
        pu_form = ProductionUnitForm(request.POST, instance=pu)
        if pu_form.is_valid():
            pu_form.save()
            return redirect("production_unit_list")
        else:
            errors.append(pu_form.errors)
    else:
        pu_form = ProductionUnitForm(instance=pu)
    context = {"pu_form": pu_form, "sales_unit_active": "active"}
    return render(request, "production_unit_edit.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def faq(request):
    """
    Faq's are the possible questions raised by the user.
    Faq  menu on Settings in the side bar will list the Faq.
    It is only available for super admin and nbc admin.
    nbc admin can create, edit and delete faq.
    add button is available to create.
    edit and delete button available for each faq listed.
    This created faq will render on the faq section on app.

    :param request:
    :return:
    """
    faqs = FAQ.objects.all()
    return render(request, "faq.html", {'faqs': faqs})


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def add_faq(request):
    """
    Faq  menu on Settings in the side bar will list the Faq.
    adding a Faq.We have a Add Faq button. clicking on
    the add button will open a form. fields are category, question,
    answer
    :param request:
    :return:
    """
    if request.method == 'POST':
        form = FAQForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('faq')
        else:
            messages.error(request, 'There was an error with your form submission.')
    else:
        form = FAQForm()

    return render(request, 'add_faq.html', {'form': form})


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def edit_faq(request, faq_id):
    """
    Faq  menu on Settings in the side bar will list the Faq.
    editing a Faq.We have a edit Faq button for each faq listed. clicking on
    the edit button will open a form. fields are category, question,
    answer
    :param request:
    :param faq_id:
    :return:
    """
    faq = get_object_or_404(FAQ, id=faq_id)
    if request.method == 'POST':
        form = FAQForm(request.POST, instance=faq)
        if form.is_valid():
            form.save()
            return redirect('faq')
        else:
            messages.error(request, 'There was an error with your form submission.')
    else:
        form = FAQForm(instance=faq)

    return render(request, 'edit_faq.html', {'form': form, 'faq': faq})


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def delete_faq(request, faq_id):
    """
    Faq  menu on Settings in the side bar will list the Faq.
    deleting a Faq.We have a delete Faq button for each faq listed. clicking on
    the delete button will open a form. fields are category, question,
    answer
    :param request:
    :param faq_id:
    :return:
    """
    faq = get_object_or_404(FAQ, id=faq_id)
    faq.delete()
    return redirect('faq')

    # return render(request, 'delete_faq.html', {'faq': faq})


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def about_us(request):
    """
    About us describe about the Navya backers,
    Where there shops, contact details, app url,
    privacy polices, terms and conditions and what is their motto etc......

    each of this details are handled separately.
    we can edit each of this details.

    :param request:
    :return:
    """
    about_us = AboutUs.objects.first()
    contact_us = ContactUs.objects.first()
    app_url = AppUrl.objects.first()
    privacy_policy = PrivacyPolicy.objects.first()
    terms_and_conditions = TermsAndConditions.objects.first()
    refund_policy = ReturnPolicy.objects.first()
    if request.method == 'POST':
        if 'edit_about_us' in request.POST:
            about_us_form = AboutUsForm(request.POST, request.FILES,
                                        instance=about_us)  # Include request.FILES for image upload
            if about_us_form.is_valid():
                about_us_form.save()
        elif 'edit_contact_us' in request.POST:
            contact_us_form = ContactUsForm(request.POST, instance=contact_us)
            if contact_us_form.is_valid():
                contact_us_form.save()
        elif 'edit_app_url' in request.POST:
            app_url_form = AppUrlForm(request.POST, instance=app_url)
            if app_url_form.is_valid():
                app_url_form.save()
        elif 'edit_privacy_policy' in request.POST:
            privacy_policy_form = PrivacyPolicyForm(request.POST, instance=privacy_policy)
            if privacy_policy_form.is_valid():
                privacy_policy_form.save()
        elif 'edit_terms_conditions' in request.POST:
            terms_conditions_form = TermsAndConditionsForm(request.POST, instance=terms_and_conditions)
            if terms_conditions_form.is_valid():
                terms_conditions_form.save()
        elif 'edit_refund_policy' in request.POST:
            refund_policy_form = RefundPolicyForm(request.POST, instance=refund_policy)
            if refund_policy_form.is_valid():
                refund_policy_form.save()

        return redirect('about_us')  # After saving, redirect to the page again

    # Create forms with current data
    about_us_form = AboutUsForm(instance=about_us)
    contact_us_form = ContactUsForm(instance=contact_us)
    app_url_form = AppUrlForm(instance=app_url)
    privacy_policy_form = PrivacyPolicyForm(instance=privacy_policy)
    terms_conditions_form = TermsAndConditionsForm(instance=terms_and_conditions)
    refund_policy_form = RefundPolicyForm(instance=refund_policy)

    return render(request, 'about_us.html', {
        'about_us': about_us,
        'contact_us': contact_us,
        'app_url': app_url,
        'privacy_policy': privacy_policy,
        'terms_and_conditions': terms_and_conditions,
        'about_us_form': about_us_form,
        'contact_us_form': contact_us_form,
        'app_url_form': app_url_form,
        'privacy_policy_form': privacy_policy_form,
        'terms_conditions_form': terms_conditions_form,
        'refund_policy': refund_policy,
        'refund_policy_form': refund_policy_form,
    })


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def privacy_policy_view(request):
    """
    In about us section we have the privacy policy view, in addition to that
    we have a separate menu on the settings, Privacy Policy. on clicking
    it will list current privacy policy, we have edit button on the list.
    by clicking edit we can edit the current privacy policy.

    :param request:
    :return:
    """
    policy = PrivacyPolicy.objects.first()
    form = PrivacyPolicyForm(request.POST or None, instance=policy)

    if request.method == 'POST' and form.is_valid():
        form.save()
        return redirect('privacy_policy')

    return render(request, 'privacy_policy.html', {
        'form': form,
        'policy': policy,
    })

@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def terms_conditions_view(request):
    """
        In about us section we have the Terms and condition view, in addition to that
    we have a separate menu on the settings, Terms and condition. on clicking
    it will list current terms and condition, we have edit button on the list.
    by clicking edit we can edit the current terms and condition.

    :param request:
    :return:
    """
    terms = TermsAndConditions.objects.first()
    form = TermsAndConditionsForm(request.POST or None, instance=terms)

    if request.method == 'POST' and form.is_valid():
        form.save()
        return redirect('terms_and_conditions')

    return render(request, 'terms_conditions.html', {
        'form': form,
        'terms': terms,
    })


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def delivery_settings(request):
    """
    * Not implemented yet........
    :param request:
    :return:
    """
    return render(request, "delivery_settings.html")


@login_required
@user_passes_test(check_user_role(["Shop Admin", "PU Admin"]))
def custom_orders(request, order_id=None):
    try:
        platform = request.GET.get('platform')
        shop_id_verify = Shop.objects.prefetch_related('unit_admin_user').filter(
            unit_admin_user__uuid=request.user.uuid)
        shop_id = shop_id_verify[0]
        errors = []
        field_name = request.COOKIES.get('field_name')
        sort_order = request.COOKIES.get('sort_order')
        search_query = request.GET.get('search', "")
        order_date = request.GET.get('product_date', "")
        order_status = request.GET.get('product_status', "")
        sort_order_data = request.GET.get('sort_order', "")
        if not sort_order_data:
            sort_order_data = sort_order
        field_name_data = request.GET.get('field_name', "")
        if not field_name_data:
            field_name_data = field_name
        
        # Modified query to properly prefetch related data
        order_list = (
            Orders.objects
            .filter(store_uuid=shop_id, order_type="Custom Orders")
            .exclude(order_status="New Order")
            .select_related("custom_product", "user_uuid", "drop_address","additionaldetails")         
            .prefetch_related("custom_order")  # This prefetches CustomOrderTracking objects
            .order_by('-created_date')
        )
        
        if platform:
            if platform == 'web':
                order_list = order_list.filter(
                    Q(platform__iexact="Web") | Q(platform__iexact="Other")
                )
            else:  # mobile section
                order_list = order_list.filter(
                    Q(platform__iexact="IOS") |
                    Q(platform__iexact="Andriod") |
                    Q(platform__iexact="Android") |
                    Q(platform__iexact="Flutter_web_app")
                )

        if request.headers.get('x-requested-with') == 'XMLHttpRequest':
            if search_query:
                order_list = order_list.filter(
                    Q(order_ID__icontains=search_query) |
                    Q(order_type__icontains=search_query) |
                    Q(order_status__icontains=search_query) |
                    Q(user_uuid__first_name__icontains=search_query)
                ).order_by('-created_date')
            if order_date:
                formatted_date = datetime.strptime(order_date, "%Y-%m-%d")
                order_list = order_list.filter(created_date__date=formatted_date)
            if order_status:
                order_list = order_list.filter(order_status=order_status)

            if sort_order_data and field_name_data:
                if sort_order_data == 'asc':
                    if field_name_data == 'user_uuid':
                        order_list = order_list.order_by(field_name_data + '__first_name')
                    else:
                        order_list = order_list.order_by(field_name_data)
                else:
                    if field_name_data == 'user_uuid':
                        order_list = order_list.order_by('-' + field_name_data + '__first_name')
                    else:
                        order_list = order_list.order_by('-' + field_name_data)

        paginator = Paginator(order_list, 6)

        page = request.GET.get('page')

        try:
            orders = paginator.get_page(page)
        except (PageNotAnInteger, TypeError):
            orders = paginator.get_page(1)
        except EmptyPage:
            orders = paginator.get_page(paginator.num_pages)

        base_url = reverse('custom_orders')
        query_params = request.GET.copy()
        if 'page' in query_params:
            del query_params['page']
        pagination_links = [
            {
                'page_number': page_number,
                'url': f"{base_url}?{query_params.urlencode(safe='/')}&page={page_number}",
            }
            for page_number in orders.paginator.page_range
        ]

        form = CustomOrderForm()
        ongoing_orders = DeliveryBoys.objects.filter(status='verified', shop=shop_id)
        context = {
            "order_form": form,
            "order_data": orders,
            'search_query': search_query,
            'date': order_date,
            'product_status': order_status,
            'pagination_links': pagination_links,
            "orders_active": "active",
            "ongoing_orders": ongoing_orders,
            "platform": platform
        }
        
        if request.headers.get('x-requested-with') == 'XMLHttpRequest':
            tbody = render_to_string('custom_order_list_page.html',
                                     {"order_data": orders, 'pagination_links': pagination_links,
                                      "ongoing_orders": ongoing_orders, "platform": platform})
            pagination = render_to_string('order_list_pagination.html', {"order_data": orders})
            return JsonResponse({'tbody': tbody, "pagination": pagination})

        response = render(request, "custom_orders.html", context)
        return response
        
    except IndexError:
        context = {
            "order_data": [],
            "orders_active": "active"
        }
        return render(request, "custom_orders.html", context)


from datetime import datetime
from django.utils import timezone

def get_available_delivery_slots(request):
    selected_date = request.GET.get("date")
    if not selected_date:
        return JsonResponse({"status": "fail", "message": "Date not provided"}, status=400)

    try:
        selected_date_obj = datetime.strptime(selected_date, "%Y-%m-%d").date()
    except ValueError:
        return JsonResponse({"status": "fail", "message": "Invalid date format"}, status=400)

    current_time = timezone.localtime().time()
    today = timezone.localdate()

    slots = DeliverySlot.objects.filter(is_available=True, status="active")

    # ✅ Filter out past slots ONLY if selected date is today
    if selected_date_obj == today:
        # Filter slots where end_time is greater than current time
        slots = slots.filter(end_time__gt=current_time)
        # Also ensure we don't show slots that have already ended
        slots = slots.exclude(end_time__lte=current_time)

    slot_data = [
        {
            "id": slot.id,
            "label": f"{slot.start_time.strftime('%I:%M %p')} - {slot.end_time.strftime('%I:%M %p')}"
        }
        for slot in slots
    ]

    return JsonResponse({"status": "success", "slots": slot_data})

def get_delivery_fee(request):
    """Return delivery fee for a given order (based on user's last known distance)."""
    if request.method != "GET":
        return JsonResponse({'error': 'Invalid method'}, status=400)

    order_uuid = request.GET.get('order_id')
    if not order_uuid:
        return JsonResponse({'error': 'Missing order_id'}, status=400)

    # Fetch order by UUID (primary key)
    order = Orders.objects.filter(uuid=order_uuid).select_related('user_uuid').first()
    if not order:
        return JsonResponse({'error': 'Order not found'}, status=404)

    user = order.user_uuid
    # Get the most recent location record for this user
    user_location = UserLocation.objects.filter(user=user).order_by('-created_at').first()
    distance_km = Decimal('0')
    if user_location and user_location.distance is not None:
        distance_km = Decimal(str(user_location.distance))
    # else distance_km remains 0

    fee = calculate_dynamic_delivery_fee(distance_km)  # returns Decimal
    return JsonResponse({'fee': str(fee)})


def calculate_dynamic_delivery_fee(distance_km=0):
    """Calculate delivery fee based on distance and global settings."""
    settings = DeliverySettings.objects.first()
    if not settings:
        return Decimal('0.0')
    
    # Ensure distance is Decimal
    if not isinstance(distance_km, Decimal):
        distance_km = Decimal(str(distance_km))
    
    base_fee = settings.base_fee
    base_km = settings.base_km
    per_km_fee = settings.per_km_fee

    if distance_km > base_km:
        extra_km = distance_km - base_km
        delivery_fee = base_fee + (per_km_fee * extra_km)
    else:
        delivery_fee = base_fee
    
    return round(delivery_fee, 2)


@login_required
@user_passes_test(check_user_role(["Shop Admin"]))
def custom_order_edit(request):
    if request.method == "POST":
        orderID = request.POST.get("order_ID")
        custom_orders = Orders.objects.filter(order_ID=orderID).first()
        del_type = ""
        if not custom_orders:
            return JsonResponse({'status': 'fail', 'message': 'Order not found'}, status=404)
 
        # Save main order details
        form = CustomOrderForm(request.POST, instance=custom_orders)
        if not form.is_valid():
            return JsonResponse({'status': 'fail', 'errors': form.errors})
        
        # Get subtotal for tax calculation
        sub_total = request.POST.get("sub_total")
        if sub_total:
            try:
                sub_total_decimal = Decimal(str(sub_total))
                # Calculate taxes as 5% of subtotal
                calculated_taxes = (sub_total_decimal * Decimal('0.05')).quantize(Decimal('0.01'))
                # Override form's taxes_and_charges with calculated value
                custom_orders.taxes_and_charges = calculated_taxes
            except (ValueError, TypeError):
                custom_orders.taxes_and_charges = Decimal('0.00')
        
        form.save()
 
        # Additional custom order fields
        quantity = request.POST.get("quantity")
        quantity_unit = request.POST.get("quantity_unit")
        delivery_type = request.POST.get("delivery_type")
        
        # Initialize variables
        pickup_date = None
        delivery_date = None
        time_slot_id = None
 
        # Handle delivery/pickup logic - only get relevant fields based on delivery type
        if delivery_type == "pickup":
            # Only get pickup date for pickup orders
            pickup_date = request.POST.get("pickup_date")
            delivery_date = None
            time_slot_id = None
            custom_orders.order_type = "Custom Orders"
            del_type = "Pick Up"
            
            custom_orders.delivery_slot_date = parse_date(pickup_date) if pickup_date else None
            custom_orders.delivery_slot_time = None
            
            # Delivery charges should be 0 for pickup
            custom_orders.delivery_charges = Decimal('0.00')
            
        elif delivery_type == "home_delivery":
            # Only get delivery date and time slot for home delivery
            delivery_date = request.POST.get("delivery_date")
            time_slot_id = request.POST.get("time_slot")  # expect slot ID instead of name
            pickup_date = None
            custom_orders.order_type = "Custom Orders"
            del_type = "Home Delivery"
            custom_orders.delivery_slot_date = parse_date(delivery_date) if delivery_date else None
            
            if time_slot_id:
                try:
                    slot = DeliverySlot.objects.get(id=time_slot_id)
                    custom_orders.delivery_slot_time = slot.start_time
                except DeliverySlot.DoesNotExist:
                    custom_orders.delivery_slot_time = None
            else:
                custom_orders.delivery_slot_time = None
            
            # Calculate delivery charges based on user location
            try:
                user = custom_orders.user_uuid
                user_location = UserLocation.objects.filter(user=user).order_by('-created_at').first()
                distance_km = Decimal('0')
                if user_location and user_location.distance is not None:
                    distance_km = Decimal(str(user_location.distance))
                
                delivery_fee = calculate_dynamic_delivery_fee(distance_km)
                custom_orders.delivery_charges = delivery_fee
            except Exception as e:
                print(f"Error calculating delivery fee: {e}")
                custom_orders.delivery_charges = Decimal('0.00')
        else:
            # No delivery type selected - clear all delivery/pickup fields
            pickup_date = None
            delivery_date = None
            time_slot_id = None
            custom_orders.delivery_slot_date = None
            custom_orders.delivery_slot_time = None
            custom_orders.delivery_charges = Decimal('0.00')
 
        # Save or update custom tracking
        custom_track, created = CustomOrderTracking.objects.get_or_create(order_id=custom_orders)
        custom_track.quantity = quantity
        custom_track.quantity_unit = quantity_unit
        custom_track.delivery_type = del_type
        custom_track.save()
 
        # Update order status
        custom_orders.order_status = "Bill Created"
        custom_orders.save()
        
        bill_edit, created = BillEdited.objects.get_or_create(order=custom_orders)
        if not created:
            # Already exists → this is an edit
            bill_edit.is_edited = True
            bill_edit.save()
 
        # Notifications
        title = "Your Bill is Ready 🧾"
        description = f"""Dear {custom_orders.user_uuid.first_name},
 
        Your bill for custom order {orderID} has been successfully generated.
 
        Please review the details and proceed with the payment at your convenience.
 
        If you have any questions, feel free to contact our support team.
 
        Best regards,  
        Navya Bakers Team
        """
        # NotificationInit(custom_orders.user_uuid.fcm_token, custom_orders, title, description, custom_orders.user_uuid)
        # EmailSend(title, description, [custom_orders.user_uuid.email], False)
        # temp_name= "custom_bill_created"
        # logging.info(f"Opt-in status for user {custom_orders.user_uuid.uuid}: {custom_orders.user_uuid.opt_in}")
 
        # if custom_orders.user_uuid.opt_in == True:
        #     send_whatsapp_message.apply_async(args=[custom_orders.user_uuid.phone_number,orderID,custom_orders.user_uuid.first_name,temp_name],countdown=2 ) # optional
 
        return JsonResponse({
            'status': 'success',
            'order_id': custom_orders.uuid,
            'OrderID': custom_orders.order_ID
        })
        
    elif request.method == "GET":
        order_id = request.GET.get('order_id')
        custom_orders = Orders.objects.filter(uuid=order_id).first()
        
        if custom_orders:
            form = CustomOrderForm(instance=custom_orders)
 
            # ----------------- Get tracking -----------------
            try:
                custom_tracking = CustomOrderTracking.objects.get(order_id=custom_orders)
            except CustomOrderTracking.DoesNotExist:
                custom_tracking = None
 
            # ----------------- Delivery type -----------------
            existing_delivery_type = ""
            if custom_tracking and custom_tracking.delivery_type == "Pick Up":
                existing_delivery_type = "pickup"
            else:
                existing_delivery_type = "home_delivery"
 
            # ----------------- Dates -----------------
            pickup_date = None
            delivery_date = None
 
            if existing_delivery_type == "pickup":
                pickup_date = custom_orders.delivery_slot_date
            elif existing_delivery_type == "home_delivery":
                delivery_date = custom_orders.delivery_slot_date
 
            # ----------------- Calculate taxes (5% of subtotal) -----------------
            calculated_taxes = Decimal('0.00')
            if custom_orders.sub_total:
                try:
                    sub_total_decimal = Decimal(str(custom_orders.sub_total))
                    calculated_taxes = (sub_total_decimal * Decimal('0.05')).quantize(Decimal('0.01'))
                except (ValueError, TypeError):
                    calculated_taxes = Decimal('0.00')
 
            # ----------------- Calculate delivery fee -----------------
            calculated_delivery_fee = Decimal('0.00')
            if existing_delivery_type == "home_delivery":
                try:
                    user = custom_orders.user_uuid
                    user_location = UserLocation.objects.filter(user=user).order_by('-created_at').first()
                    distance_km = Decimal('0')
                    if user_location and user_location.distance is not None:
                        distance_km = Decimal(str(user_location.distance))
                    
                    calculated_delivery_fee = calculate_dynamic_delivery_fee(distance_km)
                except Exception as e:
                    print(f"Error calculating delivery fee: {e}")
                    calculated_delivery_fee = Decimal('0.00')
            else:
                # Pickup orders have 0 delivery fee
                calculated_delivery_fee = Decimal('0.00')
 
            # ----------------- Slot filtering (MAIN FIX) -----------------
            today = timezone.localdate()
            current_time = timezone.localtime().time()
            
            # Base queryset for all active slots
            all_slots = DeliverySlot.objects.filter(
                is_available=True,
                status="active"
            ).order_by("start_time")
 
            selected_date = delivery_date or pickup_date
            selected_date_obj = selected_date if selected_date else today
 
            # Filter slots based on selected date
            if selected_date_obj == today:
                # For today: only show slots that haven't ended yet
                available_slots = all_slots.filter(end_time__gt=current_time)
            else:
                # For future dates: show all active slots
                available_slots = all_slots
 
            # ----------------- Selected slot -----------------
            selected_slot_id = None
 
            if existing_delivery_type == "home_delivery" and custom_orders.delivery_slot_time:
                try:
                    # Try to find the exact slot that matches the saved time
                    slot = DeliverySlot.objects.filter(
                        start_time=custom_orders.delivery_slot_time,
                        is_available=True,
                        status="active"
                    ).first()
                    
                    # Check if this slot is still valid (for today's date)
                    if slot and selected_date_obj == today:
                        if slot.end_time <= current_time:
                            # Slot has expired, don't select it
                            slot = None
                    
                    # If the saved slot is invalid or expired, select the first available slot
                    if not slot and available_slots.exists():
                        slot = available_slots.first()
 
                    if slot:
                        selected_slot_id = slot.id
 
                except Exception as e:
                    print("Slot selection error:", e)
                    # Fallback to first available slot
                    if available_slots.exists():
                        selected_slot_id = available_slots.first().id
 
        else:
            form = CustomOrderForm()
            custom_tracking = None
            existing_delivery_type = ""
            pickup_date = None
            delivery_date = None
            selected_slot_id = None
            selected_date_obj = timezone.localdate()
            calculated_taxes = Decimal('0.00')
            calculated_delivery_fee = Decimal('0.00')
            
            # Get available slots for today (only future slots)
            current_time = timezone.localtime().time()
            available_slots = DeliverySlot.objects.filter(
                is_available=True,
                status="active",
                end_time__gt=current_time
            ).order_by("start_time")
 
        # ----------------- Render -----------------
        form_data = render_to_string(
            'order_status_update_form.html',
            {
                'order_form': form,
                'order_id': order_id,
                'order_status': custom_orders.order_status if custom_orders else '',
                'delivery_slots': available_slots,  # ✅ filtered slots
                'custom_tracking': custom_tracking,
                'existing_delivery_type': existing_delivery_type,
                'pickup_date': pickup_date,
                'delivery_date': delivery_date,
                'selected_slot_id': selected_slot_id,
                'calculated_taxes': calculated_taxes,  # ✅ 5% of subtotal
                'calculated_delivery_fee': calculated_delivery_fee,  # ✅ based on location
            }
        )
 
        return JsonResponse({'success': True, 'form': form_data})
 
    return JsonResponse({'status': 'fail', 'message': 'Invalid request method'})


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def system_settings(request):
    return render(request, "system_settings.html")


# @csrf_exempt
# async def custom_order_payment(request):
#     if request.method == "POST":
#         try:
#             data = await request.json()
#             body = request.body.decode('utf-8')
#             received_signature = request.headers.get("X-Razorpay-Signature")

#             # Compute HMAC SHA256 signature
#             generated_signature = hmac.new(
#                 RAZORPAY_WEBHOOK_SECRET.encode(),
#                 body.encode(),
#                 hashlib.sha256
#             ).hexdigest()

#             # Compare signatures
#             if hmac.compare_digest(generated_signature, received_signature):
#                 event_data = json.loads(body)  # Parse JSON data
#                 orderID = data["notes"].get("orderID", "")
#                 if orderID:
#                     custom_orders = Orders.objects.filter(order_ID=orderID).first()
#                     custom_orders.order_status = "Confirmed"
#                     custom_orders.save()
#                     Payment.objects.create(
#                         order=custom_orders,
#                         razorpay_order_id=data["order_id"],
#                         payment_status="paid",  # payment status pending
#                     )

#                 return JsonResponse({"status": "success"}, status=200)
#             else:
#                 return JsonResponse(
#                     {"status": 0, "message": "Payment verification failed"}, status=400
#                 )
#         except Exception as e:
#             return JsonResponse({
#                 "status": 0, "message": "Something went wrong!", "error": str(e)
#             }, status=400)

#     else:
#         return JsonResponse({'status': 'fail', 'errors': "Method not allowed"}, status=403)


def choice_list(request):
    choice = request.GET.get('choice')

    if choice == 'Discount':
        discount_choice = [{"id": i.id, "name": i.DiscountName} for i in Discount.objects.all()]
        try:
            if discount_choice:
                return JsonResponse({"tables": list(discount_choice), "type": choice},
                                    safe=False)
            else:
                return JsonResponse({"tables": [], "type": choice}, safe=False)

        except StopIteration:
            return JsonResponse({"tables": [], "type": choice}, safe=False)

    if choice == "Coupon":
        coupon_choice = [{"id": i.id, "name": i.CouponName} for i in Coupons.objects.all()]
        try:
            if coupon_choice:
                return JsonResponse({"tables": list(coupon_choice), "type": choice},
                                    safe=False)
            else:
                return JsonResponse({"tables": [], "type": choice}, safe=False)

        except StopIteration:
            return JsonResponse({"tables": [], "type": choice}, safe=False)

    if choice == 'Product':

        product_choice = [{"id": i.id, "name": i.item_name} for i in Products.objects.all()]
        try:
            if product_choice:
                return JsonResponse({"tables": list(product_choice), "type": choice},
                                    safe=False)
            else:
                return JsonResponse({"tables": [], "type": choice}, safe=False)

        except StopIteration:
            return JsonResponse({"tables": [], "type": choice}, safe=False)


def my_polling_task(request):
    # Add your polling logic here, such as fetching data from an API
    today = date.today()
    orders = Orders.objects.filter(order_status__in=["Confirmed", "Viewed", "Order Packed", "Failed"])
    # print(f"Orders : {orders}")
    status_excluded = ["Delivery Assigned", "Despatched", "Contacted", "Bill Created", "Delivered", "Failed", "Cancelled"]
    for i in orders:
        try:
            if (i.delivery_slot_time and i.delivery_slot_time):
                combined_datetime = datetime.combine(i.delivery_slot_date, i.delivery_slot_time)
                time_dif_with_del_slot = combined_datetime.astimezone() - datetime.now().astimezone()
                duration_in_s_data = time_dif_with_del_slot.total_seconds()
                hours_del = duration_in_s_data / 3600
                # print(f"Hours  : {hours_del}")

            else:
                hours_del = 0
            if i.order_status == 'Confirmed' or i.order_status == 'Viewed':

                if hours_del < 2:

                    if i.order_status != 'Order Packed' and i.order_status not in status_excluded:
                        i.color_status = "Yellow"
                        i.color_status_updation_time = datetime.now()
                        i.save()

                if hours_del <= 0:
                    if i.order_status not in status_excluded:
                        i.color_status = "Red"
                        i.color_status_updation_time = datetime.now()
                        i.save()

                if hours_del < 1 and hours_del > 0:
                    if i.order_status not in status_excluded:
                        i.color_status = "Orange"
                        i.color_status_updation_time = datetime.now()
                        i.save()

                if hours_del > 2:
                    i.color_status = "White"
                    i.color_status_updation_time = datetime.now()
                    i.save()


            if i.order_status == 'Order Packed':

                if hours_del <= 0:
                    if i.order_status not in status_excluded:
                        i.color_status = "Red"
                        i.color_status_updation_time = datetime.now()
                        i.save()

                if hours_del < 1 and hours_del > 0:
                    if i.order_status not in status_excluded:
                        i.color_status = "Orange"
                        i.color_status_updation_time = datetime.now()
                        i.save()

                if hours_del > 2:
                    i.color_status = "White"
                    i.color_status_updation_time = datetime.now()
                    i.save()


            if i.order_status == 'Failed' or i.order_status == 'Cancelled':
                i.color_status = "Dark Red"
                i.color_status_updation_time = datetime.now()
                i.save()
        except:
            if i.order_status == "Failed" or i.order_status == "Cancelled":
                i.color_status = "Dark Red"
                i.color_status_updation_time = datetime.now()
                i.save()
            pass

    return JsonResponse({"Status": True})


def list_communication(request):
    msg = Communication.objects.select_related("user", "message").prefetch_related("message__ads").order_by(
        "-created_at")
    paginator = Paginator(msg, 6)
    page = request.GET.get("page")
    msg_list_data = paginator.get_page(page)

    context = {"msg_data": msg_list_data, "promotion_active": "active"}
    return render(request, "communication_list.html", context)

import re

def is_valid_indian_mobile(number):
    """
    Validate Indian mobile numbers:
    - Must start with 6, 7, 8, or 9
    - Must be 10 digits
    - Reject spam patterns: 0000000000, 1111111111, 1234567890
    """
    if not number:
        return False

    # Remove +91 or spaces
    cleaned = number.replace(" ", "").replace("+91", "")

    # Must be exactly 10 digits
    if not re.fullmatch(r"[6-9]\d{9}", cleaned):
        return False

    # Reject spam / fake numbers
    spam_numbers = {
        "0000000000",
        "1111111111",
        "2222222222",
        "3333333333",
        "4444444444",
        "5555555555",
        "6666666666",
        "7777777777",
        "8888888888",
        "9999999999",
        "1234567890",
        "9876543210",
    }

    if cleaned in spam_numbers:
        return False

    return cleaned  # return cleaned number if valid




def send_message(request):
    if request.method == 'POST':

        form = MessageForm(request.POST)
        subject = request.POST.get("subject") or "Notification from Navya Bakers"

        if form.is_valid():
            msg_form = form.save()

            # Fetch all customer users
            customers = Users.objects.filter(user_type="Customer")

            # ==================================================
            # BROADCAST EMAIL
            # ==================================================
            if msg_form.message_type == "Email":
                valid_emails = {}

                for customer in customers:
                    email = customer.email

                    if not email:
                        continue  # skip empty email

                    # Validate the email
                    try:
                        validate_email(email)
                    except ValidationError:
                        continue  # skip invalid email

                    # avoid duplicates
                    valid_emails[email] = customer.uuid

                # Send emails
                for email, user_id in valid_emails.items():
                    try:
                        customer = Users.objects.get(uuid=user_id)

                        body = f"Dear {customer.first_name},\n\n{msg_form.message}"

                        email_sending.apply_async(
                        kwargs={
                            'subject': subject,
                            'message': body,
                            'from_email': settings.EMAIL_HOST_USER,
                            'recipient_list': [email],
                            'fail_silently': False
                        },
                        countdown=2
                    )


                        # Save communication log
                        Communication.objects.create(user=customer, message=msg_form)

                    except Exception as e:
                        logging.error(f"Email Broadcast Error: {email} | {str(e)}")
                        continue

            # ==================================================
            # BROADCAST SMS
            # ==================================================
            # elif msg_form.message_type == "Sms":
            #     valid_numbers = set()  # avoid duplicates

            #     for customer in customers:
            #         cleaned = is_valid_indian_mobile(customer.phone_number)

            #         if not cleaned:
            #             continue  # skip invalid / spam numbers

            #         valid_numbers.add((customer.uuid, cleaned))  # store unique valid numbers

            #     # Send SMS to valid numbers only
            #     for user_id, phone_number in valid_numbers:
            #         try:
            #             customer = Users.objects.get(uuid=user_id)

            #             sms_body = f"Dear {customer.first_name},\n{msg_form.message}"

            #             # sms.apply_async( args=[sms_body, customer.phone_number], countdown=2 )

            #             Communication.objects.create(user=customer, message=msg_form)

            #         except Exception as e:
            #             logging.error(f"SMS Broadcast Error: {phone_number} | {str(e)}")

            # SUCCESS RESPONSE (AJAX)
            if request.headers.get("X-Requested-With") == "XMLHttpRequest":
                return JsonResponse({
                    "success": True,
                    "redirect_url": reverse('communication_list')
                })

            return redirect('communication_list')

        # Invalid form -> AJAX
        else:
            if request.headers.get("X-Requested-With") == "XMLHttpRequest":
                return JsonResponse({"success": False, "errors": form.errors.get_json_data()}, status=400)

            return render(request, "send_message.html", {"form": form})

    # GET request
    form = MessageForm()
    return render(request, "send_message.html", {"form": form})

# def send_email_communication(msg, to_email):
#     subject = msg.ads.AdTitle
#     message = f"{msg.message} {msg.ads.AdDescription}"
#     to_email = [to_email]  # Wrap in a list

#     send_mail(
#         subject=subject,
#         message=message,
#         from_email=settings.EMAIL_HOST_USER,
#         recipient_list=to_email,
#         fail_silently=False,
#     )
#     print("Email Sent Successfully!")

def send_email_attachment(subject, body, to_email, filepath):
    mail = EmailMessage(subject=subject, body=body, from_email=settings.EMAIL_HOST_USER, to=to_email)
    mail.attach_file(filepath)
    mail.send()







@api_view(['POST'])
def coupon_subcategory_list(request):
    try:

        category_ids = request.data.get('category', None)
        sub_category_ids = request.data.get('sub_category', None)
        product_ids = request.data.get('product', None)
        sku_ids = request.data.get('sku', None)
        trigger = request.data.get('trigger', None)
        if '' in category_ids:
            category_ids.remove('')
        if '' in sub_category_ids:
            sub_category_ids.remove('')
        if '' in product_ids:
            product_ids.remove('')
        if '' in sku_ids:
            sku_ids.remove('')

        if trigger == "category":
            categories = ProductCategory.objects.values('uuid', 'category_name')
            for i in categories:
                if str(i["uuid"]) in category_ids:
                    i["checked"] = "True"
                else:
                    i["checked"] = "False"
            subcategories = ProductSubCategory.objects.filter(category_id__in=category_ids).values('uuid',
                                                                                                   'sub_category_name')

            for j in subcategories:
                if str(j["uuid"]) in sub_category_ids:
                    j["checked"] = "True"
                else:
                    j["checked"] = "False"
            products = Products.objects.filter(item_category__in=category_ids).values('id', 'item_name')

            for k in products:
                if str(k["id"]) in product_ids:
                    k["checked"] = "True"
                else:
                    k["checked"] = "False"

            sku = SKU.objects.filter(product__item_category__in=category_ids).values('id', 'sku_name')

            for l in sku:
                if str(l["id"]) in sku_ids:
                    l["checked"] = "True"
                else:
                    l["checked"] = "False"

            return JsonResponse(
                {"categories": list(categories), "sub_categories": list(subcategories), "products": list(products),
                 "sku": list(sku), "trigger": trigger}, safe=False)

        elif trigger == "sub_category":
            categories = ProductCategory.objects.values('uuid', 'category_name')
            for i in categories:
                if str(i["uuid"]) in category_ids:
                    i["checked"] = "True"
                else:
                    i["checked"] = "False"
            subcategories = ProductSubCategory.objects.filter(category_id__in=category_ids).values('uuid',
                                                                                                   'sub_category_name')

            for j in subcategories:
                if str(j["uuid"]) in sub_category_ids:
                    j["checked"] = "True"
                else:
                    j["checked"] = "False"
            products = Products.objects.filter(item_sub_category__in=sub_category_ids).values('id', 'item_name')

            for k in products:
                if str(k["id"]) in product_ids:
                    k["checked"] = "True"
                else:
                    k["checked"] = "False"

            sku = SKU.objects.filter(product__item_sub_category__in=sub_category_ids).values('id', 'sku_name')

            for l in sku:
                if str(l["id"]) in sku_ids:
                    l["checked"] = "True"
                else:
                    l["checked"] = "False"

            return JsonResponse(
                {"categories": list(categories), "sub_categories": list(subcategories), "products": list(products),
                 "sku": list(sku), "trigger": trigger}, safe=False)

        elif trigger == "product":
            categories = ProductCategory.objects.values('uuid', 'category_name')
            for i in categories:
                if str(i["uuid"]) in category_ids:
                    i["checked"] = "True"
                else:
                    i["checked"] = "False"
            subcategories = ProductSubCategory.objects.filter(category_id__in=category_ids).values('uuid',
                                                                                                   'sub_category_name')

            for j in subcategories:
                if str(j["uuid"]) in sub_category_ids:
                    j["checked"] = "True"
                else:
                    j["checked"] = "False"
            products = Products.objects.filter(item_sub_category__in=sub_category_ids).values('id', 'item_name')
            for k in products:
                if str(k["id"]) in product_ids:
                    k["checked"] = "True"
                else:
                    k["checked"] = "False"

            sku = SKU.objects.filter(product_id__in=product_ids).values('id', 'sku_name')

            for l in sku:
                if str(l["id"]) in sku_ids:
                    l["checked"] = "True"
                else:
                    l["checked"] = "False"

            return JsonResponse(
                {"categories": list(categories), "sub_categories": list(subcategories), "products": list(products),
                 "sku": list(sku), "trigger": trigger}, safe=False)


    except Exception as e:
        return JsonResponse({"status": False, "error": str(e)}, safe=False)


@api_view(['POST'])
def order_status_update_to_delivered(request, orderID, status):
    if request.method == 'POST':
        try:
            order = Orders.objects.filter(order_ID=orderID).first()
            payment = Payment.objects.filter(order=order)
            if order.order_status == 'Delivery Assigned':
                if status == "Delivered":
                    order.order_status = 'Delivered'
                    payment.payment_status = "Paid"
                    payment.save()



                elif status == "Failed":
                    order.order_status = "Failed"
                    payment.payment_status = "Failed"
                    payment.save()
                else:
                    return JsonResponse({"status": 0, "msg": "Status change not allowed"})
                order.save()
            elif order.order_status == "Failed":
                return JsonResponse({"status": 0, "msg":"Status already updated to Failed"})

            elif order.order_status == "Delivered":
                return JsonResponse({"status": "success", "msg": "Status already updated to Delivered"})
            else:
                return JsonResponse({"status": 0, "msg": "Status change not allowed"})
                # send notification


            title = "Your Order Has Been Delivered 🎉"

            description = f"""Great news! Your order {orderID} has been successfully delivered.

            We hope you enjoy your purchase. Thank you for choosing Navya Bakers!

            We look forward to serving you again."""
            NotificationInit(order.user_uuid.fcm_token, order, title, description, order.user_uuid)
            EmailSend(title, description, [order.user_uuid.email], False)
            temp_name= "delivered_general"
            if order.user_uuid.opt_in == True:
                send_whatsapp_message.apply_async(args=[order.user_uuid.phone_number,orderID,order.user_uuid.first_name,temp_name],countdown=2 )
            return JsonResponse({'status': 1, "msg": f"Status of your order changes to {status}"})
        except Exception as e:
            return JsonResponse({'status': 0, 'msg': "something went wrong", 'error': str(e)})
    else:
        return JsonResponse({'status': 1, 'msg': "method not allowed"})


# ...................................... start product .......................................................

@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def you_may_also_like_list(request):
    """
    Liked List menu on Product section in the side bar will list the You may also like list.
    It is only available for NBC admin.
    from Liked list we can add a product and we can map product related to that product.
    if one person orders a jam, liked products will contain bread it will be defined in
    the liked list

    :param request:
    :return:

    """
    liked_data = YouMayAlsoLike.objects.order_by("-created_at")
    paginator = Paginator(liked_data, 6)
    page = request.GET.get("page")
    try:
        data = paginator.get_page(page)
    except (PageNotAnInteger, TypeError):
        data = paginator.get_page(1)
    except EmptyPage:
        data = paginator.get_page(paginator.num_pages)
    context = {"like_list": data, "category_active": "active"}
    return render(request, "people_also_like_list.html", context)


@require_POST
@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def you_may_also_like_delete(request, id):
    """
    Liked List menu on Product section in the side bar will list the You may also like list.
    Deleting a Liked Data. for each liked data listed on
    liked list, we have a delete button. clicking on
    the delete button will open a confirm dialog box. if you confirm
    it will delete the liked data.
    """
    you_may_also_like_data = YouMayAlsoLike.objects.filter(pk=id)
    if you_may_also_like_data:
        you_may_also_like_data.delete()
    return redirect("you-may-also-like")


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def you_may_also_like_edit(request, id):
    """
    Liked List menu on Product section in the side bar will list the You may also like list.
    Editing a Liked data. for each liked data listed, we have a edit button. clicking on
    the edit button will open a form. fields are product and liked products.

    """
    errors = []
    you_may_also_like_data = get_object_or_404(YouMayAlsoLike, pk=id)
    request_post = request.POST.copy()
    # if select all button selected we will get a empty string
    # if empty string exists we have to remove it else will raise error
    if '' in request_post.getlist('liked_products', []):
        liked_product_list  = request_post.getlist('liked_products', [])
        if liked_product_list:
            liked_product_list.remove('')
        request_post.setlist('liked_products', liked_product_list)
    if request.method == "POST":
        form = YouMayAlsoLikeForm(request_post, instance=you_may_also_like_data)
        if form.is_valid():
            form.save()
            return redirect("you-may-also-like")

        else:
            errors.append(form.errors)
    else:
        form = YouMayAlsoLikeForm(instance=you_may_also_like_data)
    context = {"like_form": form, "category_active": "active"}
    return render(request, "people_also_like_edit.html", context)


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def you_may_also_like_add(request):
    """
    Adding a liked product.
    Liked List menu on Product section in the side bar will list the You may also like list.
    clicking on add Liked list will open the form. fields are product and liked products.
    on submission create a liked product data.
    """
    errors = []
    # if couldn't update the request so we make a copy and update
    request_post = request.POST.copy()
    # if select all button selected we will get a empty string
    # if empty string exists we have to remove it else will raise error
    if '' in request_post.getlist('liked_products', []):
        liked_product_list  = request_post.getlist('liked_products', [])
        if liked_product_list:
            liked_product_list.remove('')
        request_post.setlist('liked_products', liked_product_list)
    if request.method == "POST":
        form = YouMayAlsoLikeForm(request_post)
        if form.is_valid():
            form.save()
            return redirect("you-may-also-like")
        else:
            errors.append(form.errors)
            context = {
                "errors": errors,
                "like_form": form,
                "category_active": "active"
            }
            return render(request, "people_also_like_add.html", context)
    else:
         form = YouMayAlsoLikeForm()

    context = {
        "errors": errors,
        "like_form": form,
        "category_active": "active"

    }
    return render(request, "people_also_like_add.html", context)



def delivery_settings_view(request):

    """
    delivery settings.
    delivery settings menu on Settings section in the side bar will list Delivery setting.
    delivery fee configuration, current delivery fee discount, add delivery discount option are
    listed on the delivery settings.
    :param request:
    :return:
    """
    settings = DeliverySettings.objects.first()
    if not settings:
        settings = DeliverySettings.objects.create()  # create default if missing

    discounts = DeliveryDiscount.objects.all().order_by('min_order_value')

    if request.method == 'POST':

        # delivery fee configuration
        if 'save_settings' in request.POST:
            settings_form = DeliverySettingsForm(request.POST, instance=settings)
            if settings_form.is_valid():
                settings_form.save()
                return redirect('delivery_settings')

        # add delivery fee discount
        if 'add_discount' in request.POST:
            discount_form = DeliveryDiscountForm(request.POST)
            if discount_form.is_valid():
                discount_form.save()
                return redirect('delivery_settings')

        # delete delivery fee discount
        if 'delete_discount' in request.POST:
            discount_id = request.POST.get('discount_id')
            DeliveryDiscount.objects.filter(id=discount_id).delete()
            return redirect('delivery_settings')

    else:
        settings_form = DeliverySettingsForm(instance=settings)
        discount_form = DeliveryDiscountForm()

    context = {
        'settings_form': settings_form,
        'discount_form': discount_form,
        'delivery_discounts': discounts,
    }

    return render(request, 'delivery_settings.html', context)



def coupon_settings_view(request):
    """
    custom coupon settings.
    Custom Coupon settings menu on Promotions section in the side bar will list Custom coupon list.
    if a customer buys a product between a price range and there is a
    coupon for that price range. A coupon code will send through the email, whatsapp or sms.
    settings view for entering the price range is here

    :param request:
    :return:
    """
    coupon_settings = CustomCouponSettings.objects.first()
    if not coupon_settings:
        coupon_settings = CustomCouponSettings.objects.create()  # create default if missing

    # there is only instance for the same
    # we can update the data
    if request.method == 'POST':
        form = CustomCouponSettingsForm(request.POST, instance = coupon_settings)
        if form.is_valid():
            form.save()
            return redirect("coupon-settings")
    else:
        form = CustomCouponSettingsForm(instance=coupon_settings)

    context = {
        'coupon_form': form,
    }

    return render(request, 'custom_coupon_setting.html', context)


from django.contrib import messages


def payment_mode_list(request):
    modes = PaymentModes.objects.all().order_by("-updated_at")
    return render(request, "payment_modes_list.html", {"modes": modes})

def add_payment_mode(request):
    if request.method == "POST":
        mode_name = request.POST.get("mode_name")
        status = request.POST.get("status") == "true"

        if not mode_name:
            messages.error(request, "Mode name is required")
            return redirect("payment_mode_list")

        PaymentModes.objects.create(
            mode_name=mode_name,
            status=status
        )
        messages.success(request, "Payment Mode added successfully")
        return redirect("payment_mode_list")

    return redirect("payment_mode_list")



def toggle_payment_status(request, mode_id):
    mode = get_object_or_404(PaymentModes, id=mode_id)
    mode.status = not mode.status
    mode.updated_at = timezone.now()
    mode.save()

    messages.success(request, "Status updated")
    return redirect("payment_mode_list")



@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def pending_reviews(request):
    """
    List all unapproved reviews (admin only)
    """
    reviews = ProductReview.objects.filter(
        is_approved=False
    ).select_related("user", "product")

    return render(
        request,
        "pending_reviews.html",
        {"reviews": reviews},
    )



@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def approve_review(request, review_id):
    """
    Approve a review
    """
    review = get_object_or_404(ProductReview, id=review_id)
    review.is_approved = True
    review.save(update_fields=["is_approved"])

    return redirect("pending_reviews")


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def reject_review(request, review_id):
    """
    Reject (delete) a review
    """
    review = get_object_or_404(ProductReview, id=review_id)
    review.delete()

    return redirect("pending_reviews")


@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def shop_slob_list(request):
    slob_list = ShopSlobId.objects.select_related('shop').all()
    form = ShopSlobIdForm()

    if request.method == "POST":
        form = ShopSlobIdForm(request.POST)

        if form.is_valid():
            form.save()
            logging.info(f"Assigned Slob ID: {form.instance.id} for Shop: {form.instance.shop.shop_name} at {datetime.now()}")
            messages.success(request, "Slob ID assigned successfully")
            return redirect('shop_slob_list')

    return render(request, "shop_slob.html", {
        "slob_list": slob_list,
        "form": form
    })



@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def edit_shop_slob(request, pk):
    slob = get_object_or_404(ShopSlobId, pk=pk)

    if request.method == "POST":
        form = ShopSlobIdForm(request.POST, instance=slob)

        if form.is_valid():
            form.save()
            messages.success(request, "Slob ID updated successfully")
            return redirect('shop_slob_list')
    else:
        form = ShopSlobIdForm(instance=slob)
    logging.info(f"Edited Slob ID: {slob.id} for Shop: {slob.shop.shop_name} at {datetime.now()}")

    return render(request, "edit_shop_slob.html", {"form": form})




@login_required
@user_passes_test(check_user_role(["Super Admin", "NBC Admin"]))
def delete_shop_slob(request, pk):
    slob = get_object_or_404(ShopSlobId, pk=pk)
    

    slob.delete()
    logging.info(f"Deleting Slob ID: {slob.id} for Shop: {slob.shop.shop_name} at {datetime.now()}")
    messages.success(request, "Slob ID deleted successfully")

    return redirect('shop_slob_list')


# 🔹 COMMON PAGINATION FUNCTION (clean code)
def paginate_queryset(request, queryset, per_page=10):
    paginator = Paginator(queryset, per_page)
    page_number = request.GET.get("page")
    return paginator.get_page(page_number)


# ✅ 1. LONG DISTANCE (Confirmed only)
def long_distance_orders_view(request):
    orders_list = Orders.objects.filter(
        order_type="Long Distance Orders",
    ).exclude(order_status="New Order").select_related("user_uuid", "store_uuid", "pu_uuid").order_by("-created_date")

    orders = paginate_queryset(request, orders_list)

    return render(request, "long_distance_orders.html", {"orders": orders})


# ✅ 2. CUSTOM ORDERS
def custom_orders_view(request):
    orders_list = Orders.objects.filter(
        order_type="Custom Orders",
    ).exclude(order_status="New Order").select_related("user_uuid", "store_uuid", "pu_uuid").order_by("-created_date")

    orders = paginate_queryset(request, orders_list)

    return render(request, "custom_orderss.html", {"orders": orders})


# ✅ 3. LOCAL + PICKUP (Confirmed only)
def local_pickup_orders_view(request):
    orders_list = Orders.objects.filter(
        order_type__in=["Local Orders", "Pick Up"],
    ).exclude(order_status="New Order").select_related("user_uuid", "store_uuid", "pu_uuid").order_by("-created_date")

    orders = paginate_queryset(request, orders_list)

    return render(request, "local_pickup_orders.html", {"orders": orders})