import io
from itertools import count
from django.shortcuts import render, redirect
from users.models import User, Client, Company, ClientLog, Menu, UserPermissions, Cities, Countries, State, AssocClientCompany, Airlines, LoginOTP, ClientPassport, ClientDocument, ClientTravelInsurance, ClientFrequentFlyer, ClientVisa, ClientLog, EmailSetup, ClientDataView, CompanyDataView
from users.forms import CompanyForm, UserForm, CustomLoginForm, UserCreateForm, UserUpdateForm, CompanyForm, CompanyFormSet, OTPVerificationForm, ForgotPasswordForm, ResetPasswordForm, FrequentFlyerForm, ClientPassportForm, AssocClientCompanyForm, OtherDocumentForm, ClientVisaForm, ClientTravelInsuranceForm
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseBadRequest
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib.auth import authenticate, login
from django.contrib import messages
from django.contrib.auth.views import LoginView, LogoutView
from django.urls import reverse_lazy
from django.utils.http import url_has_allowed_host_and_scheme
from django.http import JsonResponse, HttpResponse, StreamingHttpResponse
from django.template.loader import render_to_string
from django.utils.timezone import make_aware
from django.core.paginator import Paginator
from django.db.models import Q, Value, F
from users.utils import generate_otp, send_login_otp, send_forgot_password_otp, get_assigned_menu, normalize_client_payload, _find_non_serializables, convert_datetime, serialize_company_with_related
from users.signals import serialize_instance, build_changes, normalize_value
from django.utils.crypto import get_random_string
from django.views import View
from django.core.mail import send_mail
from django.conf import settings
from django.contrib.auth import get_user_model, update_session_auth_hash
from django.contrib.auth.hashers import check_password, make_password
from django.db.models.functions import Concat
from django.db.models import F, Value
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.utils.decorators import method_decorator
from django.utils.dateparse import parse_date
from django.views.decorators.http import require_POST
from django.utils.dateformat import DateFormat
from django.utils.html import escape
from django.forms import formset_factory, inlineformset_factory
from users.permissions import get_module_perms, require_menu_perm
from users.permissions import build_session_permissions, get_module_perms, require_menu_perm
from django.db import transaction
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.authtoken.models import Token
from rest_framework_simplejwt.tokens import RefreshToken, TokenError
from rest_framework.authentication import TokenAuthentication
from rest_framework.views import APIView
from rest_framework import status # generics, permissions
from rest_framework.response import Response
from django.core.serializers.json import DjangoJSONEncoder
import json
from rest_framework.parsers import JSONParser
from rest_framework.parsers import MultiPartParser, FormParser
from rest_framework.views import APIView
from rest_framework.decorators import api_view, permission_classes, parser_classes
from rest_framework.pagination import PageNumberPagination
import csv

from users.serializers import (CompanySerializer, CompanydataSerializer, CountrySerializer, StateSerializer, CitySerializer, ClientDataSerializer, CompanyDataSerializer,
                FullClientSerializer, AirlinesSerializer, AddFamilyMemberSerializer, EmailSetupSerializer, CompanyDataSerializer,
                ClientSerializer, ClientTravelInsuranceSerializer, ClientPassportSerializer, ClientVisaSerializer, OtherDocumentSerializer, AssocClientCompanySerializer, 
                FrequentflyerSerializer, ClientDeleteSerializer, ClientExportSerializer)
from django.contrib.auth.models import AnonymousUser
import openpyxl
import random
import json, csv
import pandas as pd
import secrets, string
from django.db import connection
from django.utils import timezone
from django.utils.timezone import localtime
from datetime import datetime, timedelta, date, time
from users.forms import ClientForm
from users.helper import get_user_assigned_menus
from django.urls import reverse
from django.db.models import Prefetch
from django.core.management import call_command
import jwt
# from djang\so.views.decorators.clickjacking import xframe_options_exempt
# Create your views here.

User = get_user_model()


# @require_menu_perm('COMPANY', 'view')
# @api_view(['GET'])
# @permission_classes([IsAuthenticated])
# def company_list(request):

#     companies = Company.objects.all()
#     data = []

#     for company in companies:
#         data.append({
#             'id': company.id,
#             'company_name': company.company_name,
#             'gst_name': company.gst_name or '',
#             'gst_no': company.gst_no or '',
#             'company_address': company.company_address,
#             'account_concerned_person': company.account_concerned_person or '',
#             'contact_no': f"+{company.account_concerned_person_country_code or ''} {company.account_concerned_person_contact_no or ''}",
#             'status': 'Active' if company.company_status == 'active' else 'Inactive',
#             'company_status': company.company_status,
#             'is_active': company.is_active,
#         })

#     return Response({
#         'success': True,
#         'data': data,
#     }, status=status.HTTP_200_OK)


def get_menu_permissions(user, menu_id):
    try:
        return UserPermissions.objects.select_related('ref_user', 'ref_menu').get(ref_user=user, ref_menu_id=menu_id)
    except UserPermissions.DoesNotExist:
        return None

'''
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def company_list(request):
    menu_id = 3
    perms = get_menu_permissions(request.user, menu_id)

    # Prefetch/select_related if Company has FKs (to optimize queries)
    companies = (
        Company.objects
        .select_related("company_country", "company_state", "company_city")  # adjust FK names
        .all()
    )

    serializer = CompanyDataSerializer(companies, many=True)  # your serializer
    data = serializer.data

    active_count = companies.filter(company_status="active").count()
    inactive_count = companies.filter(company_status="inactive").count()

    return Response({
        "success": True,
        "data": data,   # data is now formatted by CompanyDataSerializer
        "active": active_count,
        "inactive": inactive_count,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)
'''


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def company_list(request):

    menu_id = 3
    perms = get_menu_permissions(request.user, menu_id)

    companies = Company.objects.all()
    serializer = CompanydataSerializer(companies, many=True)
    data = serializer.data

    active_count = companies.filter(company_status="active").count()
    inactive_count = companies.filter(company_status="inactive").count()

    return Response({
        "success": True,
        "data": data,   # each company has its company_status
        "active": active_count,
        "inactive": inactive_count,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)



@api_view(['GET'])
@permission_classes([IsAuthenticated])
def search_company(request):
    search_text = request.GET.get('search_text', '').strip()

    if len(search_text) < 2:
        return JsonResponse({'data': [], 'recordsTotal': 0, 'recordsFiltered': 0})

    companies = Company.objects.filter(
        Q(company_name__icontains=search_text) |
        Q(gst_name__icontains=search_text) |
        Q(gst_no__icontains=search_text) |
        Q(company_address__icontains=search_text) |
        Q(company_city__name__icontains=search_text) |
        Q(company_state__name__icontains=search_text) |
        Q(company_country__name__icontains=search_text) |
        Q(company_account_concerned_person__icontains=search_text) 
        # Q(account_concerned_person__icontains=search_text) |
        # Q(travel_concerned_person__icontains=search_text)
    ).select_related("company_city", "company_state", "company_country")

    data = []
    for company in companies:
        data.append({
            'id': company.id,
            'company_name': company.company_name,
            'gst_name': company.gst_name or '',
            'gst_no': company.gst_no or '',
            'company_address': company.company_address,
            'city': company.company_city.name if company.company_city else '',
            'state': company.company_state.name if company.company_state else '',
            'country': company.company_country.name if company.company_country else '',
            'status': 'Active' if company.company_status == 'active' else 'Inactive',
        })

    return JsonResponse({
        'success': True,
        'data': data,
        'recordsTotal': companies.count(),
        'recordsFiltered': companies.count()
    }, status=status.HTTP_200_OK)


# @login_required
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def company_view(request, company_id):
    # company = get_object_or_404(Company, id=company_id)
    company = get_object_or_404(Company, id=company_id)  # fetch from DB
    serializer = CompanyDataSerializer(company, context={'request': request})
    company_data = {
        "gst_document_file": company.gst_document_file.url if company.gst_document_file else None,
    }

    return Response({
        'success': True,
        'company': serializer.data,
        "company_data": company_data,
    }, status=status.HTTP_200_OK)


'''
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def company_create(request):
    serializer = CompanySerializer(data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
'''

'''
# @login_required
# @require_menu_perm('COMPANY', 'add')
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def company_create(request):
    """
    API to create one or more companies using a formset.
    Accepts POST data and returns JSON response.
    """
    formset = CompanyFormSet(request.POST or None, request.FILES or None, queryset=Company.objects.none())

    if formset.is_valid():
        instances = formset.save(commit=False)
        for instance in instances:
            instance.company_status = 'active'   # Default value
            instance.is_active = True            # Default value
            instance.save()
        return Response({
            'success': True,
            'message': 'Company created successfully'
        }, status=status.HTTP_201_CREATED)

    # If invalid, collect all form errors
    errors = {}
    for i, form in enumerate(formset.forms):
        if form.errors:
            errors[f'form-{i}'] = form.errors

    return Response({
        'success': False,
        'errors': errors
    }, status=400)
'''

# @api_view(['POST'])
# @permission_classes([IsAuthenticated])
# @parser_classes([MultiPartParser, FormParser])
# def company_create(request):
#     data = request.data

#     # Collect companies from form-data (companies[0][field], companies[1][field]…)
#     companies = []
#     index = 0
#     while True:
#         prefix = f"companies[{index}]"
#         company_data = {}
#         has_data = False

#         for key, value in data.items():
#             if key.startswith(prefix):
#                 field = key.replace(f"{prefix}[", "").replace("]", "")
#                 company_data[field] = value
#                 has_data = True

#         if not has_data:
#             break

#         companies.append(company_data)
#         index += 1

#     if not companies:
#         return Response({
#             "success": False,
#             "message": "No company data found in request"
#         }, status=status.HTTP_400_BAD_REQUEST)
    
#     # Inject created_by and created_at for each company
#     # user = request.user
#     # now = timezone.now()
#     # for comp in companies:
#     #     comp["created_by"] = user.id
#     #     comp["created_at"] = now

#     serializer = CompanySerializer(data=companies, many=True, context={'request': request})
    
#     if serializer.is_valid():
#         instances = serializer.save(
#             created_by=request.user,
#             company_status="active",
#             is_active=True
#         )
#         return Response({
#             "success": True,
#             "message": "Company or companies created successfully",
#             "data": CompanySerializer(instances, many=True, context={'request': request}).data
#         }, status=status.HTTP_201_CREATED)

#     return Response({
#         "success": False,
#         "errors": serializer.errors
#     }, status=status.HTTP_400_BAD_REQUEST)
from django.forms.models import model_to_dict

def build_changes_company(old_data, new_data):
    changes = {}
    if not isinstance(new_data, dict):
        new_data = model_to_dict(new_data, fields=[field.name for field in new_data._meta.fields])
    if old_data and not isinstance(old_data, dict):
        old_data = model_to_dict(old_data, fields=[field.name for field in old_data._meta.fields])
    
    all_keys = set(old_data.keys() if old_data else []).union(new_data.keys())
    for key in all_keys:
        old_value = old_data.get(key) if old_data else None
        new_value = new_data.get(key) if new_data else None
        # Convert FieldFile to string
        if isinstance(new_value, models.fields.files.FieldFile):
            new_value = new_value.url if new_value else None
        if isinstance(old_value, models.fields.files.FieldFile):
            old_value = old_value.url if old_value else None
        if old_value != new_value:
            changes[key] = {"old": old_value, "new": new_value}
    return changes

# @api_view(['POST'])
# @permission_classes([IsAuthenticated])
# @parser_classes([MultiPartParser, FormParser])
# def company_create(request):
#     data = request.data
#     companies = []
#     index = 0

#     # Collect companies from form-data
#     while True:
#         prefix = f"companies[{index}]"
#         company_data = {}
#         has_data = False

#         for key, value in data.items():
#             if key.startswith(prefix):
#                 field = key.replace(f"{prefix}[", "").replace("]", "")
#                 company_data[field] = value
#                 has_data = True

#         if not has_data:
#             break

#         # Inject created_by and created_at
#         company_data["created_by"] = request.user.id
#         company_data["created_at"] = timezone.now()
#         companies.append(company_data)
#         index += 1

#     if not companies:
#         return Response({
#             "success": False,
#             "message": "No company data found in request"
#         }, status=status.HTTP_400_BAD_REQUEST)

#     serializer = CompanySerializer(data=companies, many=True, context={'request': request})
    
#     if serializer.is_valid():
#         instances = serializer.save(
#             company_status="active",
#             is_active=True
#         )
        
#         # Create ClientLog entries for each company
#         logs = []
#         for instance in instances:
#             instance_dict = model_to_dict(
#                 instance,
#                 fields=[field.name for field in instance._meta.fields]
#             )
#             logs.append(ClientLog(
#                 ref_company_id=instance,
#                 ref_table_name=instance._meta.db_table,
#                 ref_id=instance.pk,
#                 action_type="COMPANY CREATED",
#                 changed_data=json.loads(json.dumps(
#                     build_changes_company(None, instance_dict),
#                     cls=DjangoJSONEncoder
#                 )),
#                 performed_by=request.user,
#                 performed_at=timezone.now(),
#             ))
#         ClientLog.objects.bulk_create(logs)

#         return Response({
#             "success": True,
#             "message": "Company or companies created successfully",
#             "data": CompanySerializer(instances, many=True, context={'request': request}).data
#         }, status=status.HTTP_201_CREATED)

#     return Response({
#         "success": False,
#         "errors": serializer.errors
#     }, status=status.HTTP_400_BAD_REQUEST)
    

@api_view(['POST'])
@permission_classes([IsAuthenticated])
@parser_classes([MultiPartParser, FormParser])
def company_create(request):
    data = request.data
    companies = []
    index = 0

    # Collect companies from form-data
    while True:
        prefix = f"companies[{index}]"
        company_data = {}
        has_data = False

        for key, value in data.items():
            if key.startswith(prefix):
                field = key.replace(f"{prefix}[", "").replace("]", "")
                company_data[field] = value
                has_data = True

        if not has_data:
            break

        # Inject created_by and created_at
        company_data["created_by"] = request.user.id
        company_data["created_at"] = timezone.now()
        companies.append(company_data)
        index += 1

    if not companies:
        return Response({
            "success": False,
            "message": "No company data found in request"
        }, status=status.HTTP_400_BAD_REQUEST)

    serializer = CompanySerializer(data=companies, many=True, context={'request': request})
    
    if serializer.is_valid():
        instances = serializer.save(
            company_status="active",
            is_active=True
        )
        
        # Create ClientLog entries for each company
        logs = []
        for instance in instances:
            logs.append(ClientLog(
                ref_company_id=instance,
                ref_table_name=instance._meta.db_table,
                ref_id=instance.pk,
                action_type="COMPANY CREATED",
                changed_data=json.loads(json.dumps(
                    build_changes(None, serialize_company_with_related(instance)),  # Pass instance, not instance_dict
                    cls=DjangoJSONEncoder
                )),
                performed_by=request.user,
                performed_at=timezone.now(),
            ))
        ClientLog.objects.bulk_create(logs)

        return Response({
            "success": True,
            "message": "Company or companies created successfully",
            "data": CompanySerializer(instances, many=True, context={'request': request}).data
        }, status=status.HTTP_201_CREATED)

    return Response({
        "success": False,
        "errors": serializer.errors
    }, status=status.HTTP_400_BAD_REQUEST)

'''
@api_view(['POST'])
@permission_classes([IsAuthenticated])
@parser_classes([MultiPartParser, FormParser])
def company_create(request):
    data = request.data
    print(data)

    # Collect companies from form-data (companies[0][field], companies[1][field]…)
    companies = []
    index = 0
    while True:
        prefix = f"companies[{index}]"
        company_data = {}
        has_data = False

        for key, value in data.items():
            if key.startswith(prefix):
                field = key.replace(f"{prefix}[", "").replace("]", "")
                company_data[field] = value
                has_data = True

        if not has_data:
            break

        companies.append(company_data)
        index += 1

    if not companies:
        return Response({
            "success": False,
            "message": "No company data found in request"
        }, status=status.HTTP_400_BAD_REQUEST)
    
    serializer = CompanySerializer(data=companies, many=True)
    # print(serializer.data)
    if serializer.is_valid():
        instances = serializer.save(
            company_status="active",
            is_active=True
        )
        return Response({
            "success": True,
            "message": "Company or companies created successfully",
            "data": CompanySerializer(instances, many=True).data
        }, status=status.HTTP_201_CREATED)

    return Response({
        "success": False,
        "errors": serializer.errors
    }, status=status.HTTP_400_BAD_REQUEST)
'''

'''
# @login_required
@require_menu_perm('COMPANY', 'edit')
@api_view(['PUT'])
@permission_classes([AllowAny])
def company_edit(request, company_id):
    company = get_object_or_404(Company, id=company_id)

    if request.method == 'PUT':
        form = CompanyForm(request.POST, request.FILES, instance=company)
        if form.is_valid():
            form.save()
            return redirect('company_list')  # Change if your success URL differs
    else:
        form = CompanyForm(instance=company)

    # Country list for dropdowns
    countries = Countries.objects.all()
    states = State.objects.filter(country_id=company.company_country.id) if company.company_country else State.objects.none()
    cities = Cities.objects.filter(state_id=company.company_state.id) if company.company_state else Cities.objects.none()

    return Response({
        'form': form,
        'company': company,
        'countries': countries,
        'states': states,
        'cities': cities,
        'menuaction': 'COMPANY',                   # highlights the Company tab
        # 'assigned_menu': ['COMPANY', 'DASHBOARD', 'CLIENT', 'USER', 'CLIENT_LOG', 'report'], # shows the Company tab
        'is_default': 1,
    })
'''


@api_view(['GET', 'PUT'])
@permission_classes([IsAuthenticated])
@parser_classes([MultiPartParser, FormParser])
@transaction.atomic
def company_edit(request, company_id):
    company = get_object_or_404(Company, id=company_id)

    if request.method == "PUT":
        # Accept both JSON and form-data safely
        data = request.data

        # Keep old status if not explicitly provided
        if "company_status" not in data:
            data = data.copy()
            data["company_status"] = company.company_status
        
        # if data.company_status == 'active':
        #     data.is_active = True
        # elif data.company_status == 'inactive':
        #     data.is_active = False

        serializer = CompanySerializer(instance=company, data=data, partial=True, context={'request': request})

        if serializer.is_valid():
            updated_company = serializer.save(updated_by=request.user)
            return Response({
                "success": True,
                "message": "Company updated successfully",
                "company_id": updated_company.id,
                "company_status": updated_company.company_status,
                "company": CompanySerializer(updated_company).data,  # fresh data
            }, status=status.HTTP_200_OK)

        return Response({
            "success": False,
            "errors": serializer.errors
        }, status=status.HTTP_400_BAD_REQUEST)

    # GET single company
    serializer = CompanySerializer(company)
    return Response({
        "success": True,
        "company_id": company.id,
        "company_status": company.company_status,
        "company": serializer.data,
    }, status=status.HTTP_200_OK)


@api_view(['POST'])
@permission_classes([IsAuthenticated])
@parser_classes([MultiPartParser])
def company_preview_import(request):
    file = request.FILES.get('file')
    if not file:
        return Response({'error': 'No file uploaded.'}, status=status.HTTP_400_BAD_REQUEST)

    try:
        df = pd.read_csv(file) if file.name.endswith('.csv') else pd.read_excel(file)
        df = df.where(pd.notnull(df), None)
    except Exception as e:
        return Response({'error': f'Error reading file: {e}'}, status=status.HTTP_400_BAD_REQUEST)

    # Column mapping: user-friendly -> backend field
    COLUMN_MAPPING = {
        'Company Name': 'company_name',
        'Address': 'company_address',
        'City': 'company_city',
        'State': 'company_state',
        'Country': 'company_country',
        'Pincode': 'company_pincode',
    }

    # Apply mapping
    df.rename(columns=COLUMN_MAPPING, inplace=True)

    # Required fields
    required = ['company_name', 'company_address', 'company_city', 'company_state', 'company_country', 'company_pincode']
    missing = [col for col in required if col not in df.columns]
    if missing:
        return Response({'error': f'Missing columns: {missing}'}, status=status.HTTP_400_BAD_REQUEST)

    # Already existing companies
    existing_names = set(Company.objects.values_list('company_name', flat=True))

    seen_names = set()
    preview_data = []
    has_errors = False

    for idx, row in df.iterrows():
        company_name = str(row['company_name']).strip() if row.get('company_name') else ""
        error = ""

        if not company_name:
            error = "Company name is required"
        elif company_name in seen_names:
            error = "Duplicate company in file"
        elif company_name in existing_names:
            error = "Company already exists"

        seen_names.add(company_name)

        preview_data.append({
            "row_number": idx + 2,  # +2 because header + 1-based index
            "company_name": company_name,
            "company_address": row.get('company_address'),
            "company_city": row.get('company_city'),
            "company_state": row.get('company_state'),
            "company_country": row.get('company_country'),
            "company_pincode": row.get('company_pincode'),
            "error": error
        })

        if error:
            has_errors = True

    return Response({"rows": preview_data, "has_errors": has_errors})


@api_view(['POST'])
@permission_classes([IsAuthenticated])
@parser_classes([MultiPartParser])
def company_import(request):
    file = request.FILES.get('file')
    if not file:
        return Response({'error': 'No file uploaded.'}, status=status.HTTP_400_BAD_REQUEST)

    try:
        df = pd.read_csv(file) if file.name.endswith('.csv') else pd.read_excel(file)
        df = df.where(pd.notnull(df), None)
    except Exception as e:
        return Response({'error': f'Error reading file: {e}'}, status=status.HTTP_400_BAD_REQUEST)

    # Column mapping
    column_mapping = {
        "Company Name": "company_name",
        "Address": "company_address",
        "City": "company_city",
        "State": "company_state",
        "Country": "company_country",
        "Pincode": "company_pincode",
    }

    # Rename uploaded columns based on mapping
    df.rename(columns=column_mapping, inplace=True)

    # Required fields after renaming
    required = ['company_name', 'company_address', 'company_city', 'company_state', 'company_country', 'company_pincode']
    missing = [col for col in required if col not in df.columns]
    if missing:
        return Response({'error': f'Missing columns: {missing}'}, status=status.HTTP_400_BAD_REQUEST)

    success, failed = 0, []
    for idx, row in df.iterrows():
        try:
            with transaction.atomic():
                company_name = str(row['company_name']).strip()
                if not company_name:
                    raise ValueError("Company name is required")
                if Company.objects.filter(company_name=company_name).exists():
                    raise ValueError("Company already exists")

                # Correct field names (use name instead of city_name/state_name/country_name)
                try:
                    city_instance = Cities.objects.get(name=row.get('company_city'))
                except Cities.DoesNotExist:
                    raise ValueError(f"City '{row.get('company_city')}' not found")

                try:
                    state_instance = State.objects.get(name=row.get('company_state'))
                except State.DoesNotExist:
                    raise ValueError(f"State '{row.get('company_state')}' not found")

                try:
                    country_instance = Countries.objects.get(name=row.get('company_country'))
                except Countries.DoesNotExist:
                    raise ValueError(f"Country '{row.get('company_country')}' not found")

                Company.objects.create(
                    company_name=company_name,
                    company_address=row.get('company_address'),
                    company_city=city_instance,
                    company_state=state_instance,
                    company_country=country_instance,
                    company_pincode=row.get('company_pincode'),
                )
                success += 1
        except Exception as e:
            failed.append({
                "row_number": idx + 2,  # +2 for header + 1-based index
                "company_name": row.get('company_name'),
                "error": str(e)
            })

    return Response({
        "status": True,
        "successfully_imported": success,
        "failed_count": len(failed),
        "failures": failed
    }, status=status.HTTP_200_OK)


@api_view(['DELETE'])
@permission_classes([IsAuthenticated])
def delete_company(request, id):
    try:
        company = Company.objects.get(id=id)

        # Set the user performing the delete
        company.updated_by = request.user
        company.save(update_fields=['updated_by'])

        # Now delete the company (signal will pick up updated_by)
        company.delete()

        return Response({'status': True, 'message': 'Company deleted successfully'}, status=200)
    except Company.DoesNotExist:
        return Response({'status': False, 'message': 'Company not found'}, status=404)
    except Exception as e:
        return Response({'status': False, 'message': str(e)}, status=500)


# from django.core.mail import send_mail
# from django.http import HttpResponse

# def test_email(request):
#     send_mail(
#         'Test Subject',
#         'This is a test email from Django.',
#         'your-email@gmail.com',
#         ['recipient@example.com'],
#         fail_silently=False,
#     )
#     return HttpResponse("Email sent successfully!")


@api_view(['POST'])
@permission_classes([AllowAny])
def custom_login(request):
    email = request.data.get("email")
    password = request.data.get("password")

    if not email or not password:
        return Response({
            "success": False,
            "message": "Email and password are required"
        }, status=status.HTTP_400_BAD_REQUEST)

    try:
        user = User.objects.get(email=email)
        if not user.check_password(password):
            return Response(
                {"error": "Incorrect password"},
                status=status.HTTP_401_UNAUTHORIZED
            )
    except User.DoesNotExist:
        return Response(
            {"error": "Invalid email or password"},
            status=status.HTTP_401_UNAUTHORIZED
        )

    is_first_login = user.last_login is None

    # --- Admin bypass list (fixed 3 users) ---
    ADMIN_BYPASS_EMAILS = [
        "kush.dave@ammrs.co.in",
        "atul@myvaluetrip.com",
        "shachi@myvaluetrip.com",
        "kunael.aneja@ammrs.co.in",
        "vimlesh.kumhar@ammrs.co.in",
        "kush.fsdfd@yopmail.com",
    ]

    if user.email in ADMIN_BYPASS_EMAILS or user.is_superuser:
        # Skip OTP for these admins
        refresh = RefreshToken.for_user(user)

        user_permissions = (
            UserPermissions.objects
            .filter(ref_user=user)
            .select_related("ref_menu")
        )
        permissions_data = [
            {
                "menu_id": perm.ref_menu.id if perm.ref_menu else None,
                "menu_name": perm.ref_menu.menu_name if perm.ref_menu else None,
                "can_view": perm.can_view,
                "can_add": perm.can_add,
                "can_edit": perm.can_edit,
                "can_delete": perm.can_delete,
                "can_export": perm.can_export,
            }
            for perm in user_permissions
        ]

        return Response({
            "success": True,
            "message": "Login successful (OTP not required)",
            "otp_sent": False,
            "first_login": is_first_login,
            "access": str(refresh.access_token),
            "refresh": str(refresh),
            "user": {
                "id": user.id,
                "email": user.email,
                "name": user.username,
                "designation": user.designation,
                "contact": user.contact_no,
                "status": user.status,
                "is_user_access": user.is_superuser,
                "is_superuser": user.is_superuser,
                "permissions": permissions_data,
            }
        }, status=status.HTTP_200_OK)

    # --- Normal users → OTP Flow ---
    otp = generate_otp()
    LoginOTP.objects.update_or_create(
        user=user,
        defaults={
            "otp": otp,
            "is_verified": False,
            "created_at": timezone.now()
        }
    )
    send_login_otp(user.email, otp)

    # Save user id in session (for OTP verification)
    request.session["otp_user_id"] = user.id

    # Generate JWT tokens
    refresh = RefreshToken.for_user(user)

    user_permissions = (
        UserPermissions.objects
        .filter(ref_user=user)
        .select_related("ref_menu")
    )
    permissions_data = [
        {
            "menu_id": perm.ref_menu.id if perm.ref_menu else None,
            "menu_name": perm.ref_menu.menu_name if perm.ref_menu else None,
            "can_view": perm.can_view,
            "can_add": perm.can_add,
            "can_edit": perm.can_edit,
            "can_delete": perm.can_delete,
            "can_export": perm.can_export,
        }
        for perm in user_permissions
    ]

    return Response({
        "success": True,
        "message": "OTP sent to your email",
        "otp_sent": True,
        "first_login": is_first_login,
        "access": str(refresh.access_token),
        "refresh": str(refresh),
        "user": {
            "id": user.id,
            "email": user.email,
            "name": user.username,
            "designation": user.designation,
            "contact": user.contact_no,
            "status": user.status,
            "is_user_access": user.is_superuser,
            "permissions": permissions_data,
        }
    }, status=status.HTTP_200_OK)



@api_view(['POST'])
@permission_classes([AllowAny])
def custom_login_old(request):
    email = request.data.get("email")
    password = request.data.get("password")

    if not email or not password:
        return Response({
            "success": False,
            "message": "Email and password are required"
        }, status=status.HTTP_400_BAD_REQUEST)

    try:
        user = User.objects.get(email=email)
        if not user.check_password(password):
            return Response(
                {"error": "Incorrect password"},
                status=status.HTTP_401_UNAUTHORIZED
            )
    except User.DoesNotExist:
        return Response(
            {"error": "Invalid email or password"},
            status=status.HTTP_401_UNAUTHORIZED
        )
    
    is_first_login = user.last_login is None

    if user is not None:
        # Generate OTP & store
        otp = generate_otp()
        LoginOTP.objects.update_or_create(
            user=user,
            defaults={
                "otp": otp,
                "is_verified": False,
                "created_at": timezone.now()
            }
        )
        send_login_otp(user.email, otp)

        # Save user id in session (for OTP verification)
        request.session["otp_user_id"] = user.id

        # Generate JWT tokens
        refresh = RefreshToken.for_user(user)

        user_permissions = (
            UserPermissions.objects
            .filter(ref_user=user)
            .select_related("ref_menu")
        )
        permissions_data = [
            {
                "menu_id": perm.ref_menu.id if perm.ref_menu else None,
                "menu_name": perm.ref_menu.menu_name if perm.ref_menu else None,
                "can_view": perm.can_view,
                "can_add": perm.can_add,
                "can_edit": perm.can_edit,
                "can_delete": perm.can_delete,
                "can_export": perm.can_export,
            }
            for perm in user_permissions
        ]

        return Response({
            "success": True,
            "message": "OTP sent to your email",
            "otp_sent": True,
            "first_login": is_first_login,
            "access": str(refresh.access_token),
            "refresh": str(refresh),
            "user": {
                "id": user.id,
                "email": user.email,
                # "user_type": getattr(user, "user_type", None),
                "name": user.username,
                "designation": user.designation,
                "contact": user.contact_no,
                "status": user.status,
                "is_user_access": user.is_superuser,
                "permissions": permissions_data,
            }
        }, status=status.HTTP_200_OK)

    return Response({
        "success": False,
        "message": "Invalid email or password",
        "otp_sent": False
    }, status=status.HTTP_400_BAD_REQUEST)



@api_view(['POST'])
@permission_classes([AllowAny])
def verify_otp(request):
    user_id = request.data.get("id")   # take from body
    otp_input = request.data.get("otp")

    if not user_id or not otp_input:
        return Response({"error": "User ID and OTP are required"}, status=status.HTTP_400_BAD_REQUEST)

    try:
        otp_obj = LoginOTP.objects.get(user_id=user_id)
    except LoginOTP.DoesNotExist:
        return Response({"error": "OTP not found"}, status=400)

    if otp_obj.otp == otp_input and not otp_obj.is_expired():
        otp_obj.is_verified = True
        otp_obj.save()

        user = otp_obj.user
        login(request, user)  # optional (not really needed for API login)

        return Response({
            "success": True,
            "message": "OTP verified successfully",
            "user_id": user.id,
            "email": user.email,
        }, status=status.HTTP_202_ACCEPTED)
    else:
        return Response({"error": "Invalid or expired OTP"}, status=400)



# @csrf_exempt
# def forgot_password(request):
#     if request.method == "POST":
#         email = request.POST.get("email")
#         try:
#             user = User.objects.get(email=email)
#             otp = generate_otp()
#             LoginOTP.objects.create(user=user, otp=otp)
#             send_login_otp(user.email, otp)
#             request.session['forgot_user_id'] = user.id
#             messages.success(request, "OTP sent to your registered email.")
#             return redirect("verify_otp_forgot_pass")
#         except User.DoesNotExist:
#             messages.error(request, "No account found with this email.")
#     return render(request, "forgot_password.html")


'''
def forgot_password(request):
    if request.method == 'POST':
        email = request.POST.get('email')
        try:
            user = User.objects.get(email=email)
            otp = random.randint(100000, 999999)
            request.session['reset_email'] = email
            request.session['reset_otp'] = str(otp)

            # Send OTP email (replace with your email sending logic)
            print(f"OTP for {email}: {otp}")

            messages.success(request, 'OTP sent to your email')
            return redirect('verify_otp')  # redirect to OTP page
        except User.DoesNotExist:
            messages.error(request, 'Email not found')
    return render(request, 'forgot_password.html')
'''


@api_view(['POST'])
@permission_classes([AllowAny])
def forgot_password(request):
    email = request.data.get("email")

    if not email:
        return Response({
            "success": False,
            "message": "Email is required"
        }, status=status.HTTP_400_BAD_REQUEST)

    try:
        user = User.objects.get(email=email)
    except User.DoesNotExist:
        return Response({
            "success": False,
            "message": "No user found with this email"
        }, status=status.HTTP_404_NOT_FOUND)

    # Generate OTP
    otp = generate_otp()

    # Update existing OTP or create a new one
    otp_obj, created = LoginOTP.objects.update_or_create(
        user=user,
        defaults={
            "otp": otp,
            "purpose": "forgot_password",
            "created_at": timezone.now(),
            "is_verified": False
        }
    )

    # Send OTP email
    send_forgot_password_otp(user.email, otp)

    # Save user id in session for next step (OTP verification)
    request.session["otp_user_id"] = user.id

    return Response({
        "success": True,
        "message": "OTP sent to your email",
        "otp_sent": True,
        "user": {
            "id": user.id,
            "email": user.email,
            "is_active": user.is_active,
        }
    }, status=status.HTTP_200_OK)



# @csrf_exempt
@api_view(['POST'])
@permission_classes([AllowAny])
def verify_otp_forgot_pass(request):
    otp = request.data.get("otp")
    # allow from session OR request body
    user_id = request.session.get("id") or request.data.get("id")

    if not otp or not user_id:
        return Response({
            "success": False,
            "message": "OTP and user_id are required"
        }, status=status.HTTP_400_BAD_REQUEST)

    try:
        otp_obj = LoginOTP.objects.filter(
            user_id=user_id, purpose="forgot_password", is_verified=False
        ).latest("created_at")
    except LoginOTP.DoesNotExist:
        return Response({
            "success": False,
            "message": "OTP not found or expired"
        }, status=status.HTTP_404_NOT_FOUND)

    if otp_obj.otp == otp and not otp_obj.is_expired():
        otp_obj.is_verified = True
        otp_obj.save()

        # Save for reset password step
        request.session["reset_user_id"] = user_id
        request.session.pop("otp_user_id", None)

        return Response({
            "success": True,
            "message": "OTP verified. You can now reset your password",
            "redirect": True
        }, status=status.HTTP_200_OK)

    return Response({
        "success": False,
        "message": "Invalid or expired OTP"
    }, status=status.HTTP_400_BAD_REQUEST)



@api_view(['POST'])
@permission_classes([AllowAny])  # no token required
def reset_password(request):
    new_password = request.data.get("new_password")
    user_id = request.data.get("id")

    if not new_password or not user_id:
        return Response({
            "success": False,
            "message": "New password and user id are required"
        }, status=status.HTTP_400_BAD_REQUEST)

    try:
        user = User.objects.get(id=user_id)
        user.set_password(new_password)
        user.save()

        return Response({
            "success": True,
            "message": "Password reset successful"
        }, status=status.HTTP_200_OK)
    except User.DoesNotExist:
        return Response({
            "success": False,
            "message": "User not found"
        }, status=status.HTTP_404_NOT_FOUND)


'''
WORKING
# Remove @csrf_exempt from assigned_menu
@login_required
@csrf_exempt
def assigned_menus(request):
    if request.method == "POST":
        try:
            data = json.loads(request.body.decode("utf-8"))
            user_id = data.get("user_id")

            if not user_id:
                return JsonResponse({"error": "user_id is required"}, status=400)

            # Get the user
            try:
                user = User.objects.get(id=user_id)
            except User.DoesNotExist:
                return JsonResponse({"error": "User not found"}, status=404)

            # Get all menus assigned to that user
            permissions = UserPermissions.objects.filter(ref_user=user, can_view=True).select_related("ref_menu")

            menus = []
            for perm in permissions:
                menus.append({
                    "menu_id": perm.ref_menu.id,
                    "menu_name": perm.ref_menu.menu_name,
                    "menu_url": perm.ref_menu.menu_url,
                    "can_view": perm.can_view,
                    "can_add": perm.can_add,
                    "can_edit": perm.can_edit,
                    "can_delete": perm.can_delete,
                    "can_export": perm.can_export,
                })

            return JsonResponse({"user": user.username, "menus": menus}, safe=False)

        except json.JSONDecodeError:
            return JsonResponse({"error": "Invalid JSON"}, status=400)

    return JsonResponse({"error": "Invalid method"}, status=405)
'''

@api_view(['POST'])
@permission_classes([AllowAny])
def assigned_menus_api(request):
    user_id = request.data.get("user_id")
    if not user_id:
        return Response({"error": "user_id is required"}, status=400)

    user = get_object_or_404(User, id=user_id)

    permissions = (
        UserPermissions.objects
        .filter(ref_user=user)
        .select_related("ref_menu", "ref_menu__ref_menu")
        .only(
            'can_view', 'can_add', 'can_edit', 'can_delete', 'can_export',
            'ref_menu__id', 'ref_menu__menu_name', 'ref_menu__menu_url', 'ref_menu__menu_order_no', 'ref_menu__ref_menu_id'
        )
    )

    menus = {}

    # Always add dashboard by default
    try:
        dashboard = Menu.objects.get(menu_action="DASHBOARD")
        menus[dashboard.id] = {
            "id": dashboard.id,
            "name": dashboard.menu_name,
            "url": dashboard.menu_url,
            "order": dashboard.menu_order_no,
            "submenus": [],
            # "permissions": {
            #     "can_view": perm.can_view,
            #     "can_add": perm.can_add,
            #     "can_edit": perm.can_edit,
            #     "can_delete": perm.can_delete,
            #     "can_export": perm.can_export,
            # }
        }
    except Menu.DoesNotExist:
        pass

    for perm in permissions:
        menu = perm.ref_menu
        # Skip if no permissions granted
        if not (perm.can_view or perm.can_add or perm.can_edit or perm.can_delete or perm.can_export):
            continue

        if menu.ref_menu_id is None:  # Parent menu
            if menu.id not in menus:
                menus[menu.id] = {
                    "id": menu.id,
                    "name": menu.menu_name,
                    "url": menu.menu_url,
                    "order": menu.menu_order_no,
                    "submenus": [],
                    "permissions": {
                        "can_view": perm.can_view,
                        "can_add": perm.can_add,
                        "can_edit": perm.can_edit,
                        "can_delete": perm.can_delete,
                        "can_export": perm.can_export,
                    }
                }
        else:  # Submenu
            parent = menu.ref_menu
            if parent.id not in menus:
                menus[parent.id] = {
                    "id": parent.id,
                    "name": parent.menu_name,
                    "url": parent.menu_url,
                    "order": parent.menu_order_no,
                    "submenus": []
                }
            submenu_dict = {
                "id": menu.id,
                "name": menu.menu_name,
                "url": menu.menu_url,
                "permissions": {
                    "can_view": perm.can_view,
                    "can_add": perm.can_add,
                    "can_edit": perm.can_edit,
                    "can_delete": perm.can_delete,
                    "can_export": perm.can_export,
                }
            }
            menus[parent.id]["submenus"].append(submenu_dict)

    # Sort parent menus by menu_order_no
    sorted_menus = sorted(menus.values(), key=lambda m: m["order"])

    # Sort submenus by submenu id (or order if exists)
    for m in sorted_menus:
        m["submenus"] = sorted(m["submenus"], key=lambda sm: sm["id"])

    return Response({"menus": sorted_menus})


'''
@api_view(['POST'])
@permission_classes([AllowAny])
def assigned_menus_api(request):
    user_id = request.data.get("user_id")
    if not user_id:
        return Response({"error": "user_id is required"}, status=400)

    user = get_object_or_404(User, id=user_id)

    permissions = (
        UserPermissions.objects
        .filter(ref_user=user)
        .select_related("ref_menu", "ref_menu__ref_menu")
    )

    menus = {}

    # Always add dashboard by default
    try:
        dashboard = Menu.objects.get(menu_action="DASHBOARD")
        menus[dashboard.id] = {
            "id": dashboard.id,
            "name": dashboard.menu_name,
            "url": dashboard.menu_url,
            "order": dashboard.menu_order_no,
            "submenus": []
        }
    except Menu.DoesNotExist:
        pass

    for perm in permissions:
        menu = perm.ref_menu
        if not (perm.can_view or perm.can_add or perm.can_edit or perm.can_delete or perm.can_export):
            continue

        if menu.ref_menu_id is None:  # Parent menu
            if menu.id not in menus:
                menus[menu.id] = {
                    "id": menu.id,
                    "name": menu.menu_name,
                    "url": menu.menu_url,
                    "order": menu.menu_order_no,
                    "submenus": [],
                    # "permissions": []
                }
        else:  # Submenu
            parent = menu.ref_menu
            if parent.id not in menus:
                menus[parent.id] = {
                    "id": parent.id,
                    "name": parent.menu_name,
                    "url": parent.menu_url,
                    "order": parent.menu_order_no,
                    "submenus": []
                }
            menus[parent.id]["submenus"].append({
                "id": menu.id,
                "name": menu.menu_name,
                "url": menu.menu_url
            })

    # Sort parent menus by menu_order_no
    sorted_menus = sorted(menus.values(), key=lambda m: m["order"])

    # Sort submenus by menu_order_no too
    for m in sorted_menus:
        m["submenus"] = sorted(m["submenus"], key=lambda sm: sm["id"])

    return Response({"menus": sorted_menus})
'''



@api_view(['GET'])
@permission_classes([AllowAny])
def get_countries(request):
    countries = Countries.objects.all().only('id', 'name', 'country_code')
    serializer = CountrySerializer(countries, many=True)
    return Response({
        'success': True,
        'data': serializer.data
    }, status=status.HTTP_200_OK)


# def get_country_code(request, country_id):
#     try:
#         country = Countries.objects.values('id', 'name', 'country_code').get(id=country_id)
#         return JsonResponse({'success': True, 'country': country})
#     except Countries.DoesNotExist:
#         return JsonResponse({'success': False, 'message': 'Country not found'}, status=404)


# def get_states(request):
#     country_id = request.GET.get('country_id')
#     states = State.objects.filter(country_id=country_id).values('id', 'state_name')
#     return JsonResponse({'states': list(states)})

# def get_cities(request):
#     state_id = request.GET.get('state_id')
#     cities = Cities.objects.filter(state_id=state_id).values('id', 'city_name')
#     return JsonResponse({'cities': list(cities)})


@api_view(["GET"])
@permission_classes([AllowAny])
def get_states(request):
    country_id = request.query_params.get("country_id")
    if not country_id:
        return Response({"error": "country_id is required"}, status=status.HTTP_400_BAD_REQUEST)

    states = State.objects.filter(country_id=country_id).only('id', 'name', 'country_id')
    serializer = StateSerializer(states, many=True)
    return Response({
        'success': True,
        'data': serializer.data,
    },status=status.HTTP_200_OK)


@api_view(["GET"])
@permission_classes([AllowAny])
def get_cities(request):
    state_id = request.query_params.get("state_id")
    if not state_id:
        return Response(
            {"success": False, "error": "state_id is required"},
            status=status.HTTP_400_BAD_REQUEST,
        )
    try:
        state_id = int(state_id)
    except (ValueError, TypeError):
        return Response(
            {"success": False, "error": "Invalid state_id, must be a number"},
            status=status.HTTP_400_BAD_REQUEST,
        )
    cities = Cities.objects.filter(state_id=state_id).only('id', 'name', 'state_id')
    serializer = CitySerializer(cities, many=True)
    return Response(
        {"success": True, "data": serializer.data},
        status=status.HTTP_200_OK,
    )



# @login_required
@api_view(['GET'])
@permission_classes([AllowAny])
def dashboard(request):

    # assigned_menu = UserPermissions.objects.filter(
    #     ref_user=request.user, can_view=True
    # ).select_related("ref_menu")


    return Response({
            # 'menuaction': 'DASHBOARD',
            # 'assigned_menu': ['DASHBOARD', 'USER', 'COMPANY', 'CLIENT', 'CLIENT_LOG', 'report'],  # include 'USER' here
            # "assigned_menu": assigned_menu,
            # "is_default": request.user.is_default,
            # 'is_default': 1,
        })


# def email_setup(request):
#     return render(request, '/email_setup.html')


def build_multiword_query(query_string, fields):
    """
    Splits query_string into words and builds a Q object:
    - All words must match somewhere (AND)
    - Within each word, it can match in any field (OR)
    """
    words = query_string.split()
    q_object = Q()
    for word in words:
        word_q = Q()
        for field in fields:
            word_q |= Q(**{f"{field}__icontains": word})
        q_object &= word_q
    return q_object


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def search_records(request):
    if request.method != 'GET':
        return JsonResponse({"status": False, "message": "Invalid request method."})

    query = request.GET.get('search', '').strip()

    if len(query) < 3:
        return JsonResponse(
            {"status": False, "message": "Please enter at least 3 characters."},
            status=status.HTTP_400_BAD_REQUEST
        )

    results = {"clients": [], "companies": [], "passports": [], "visas": [], "insurance": [], "assoc_company": [],
                "freq_flyer": [], "documents": []}

    # --------------------- Clients ---------------------
    client_fields = [
        "client_first_name", "client_middle_name", "client_last_name",
        "email", "contact_no", "client_code", "residential_address",
        "residential_city__name", "residential_state__name", "residential_country__name",
        "dob", "gender", "anniversary_date", "reference_from", "preferred_contact_method",
        "aadhaar_no", "pan_no", "ref_preferred_airline__airline",
        "seat_preference", "seat_preference_other", "meal_preference",
        "fare_preference", "star_rating", "stay_preference",
        "room_preference", "extra_amenities"
    ]

    clients = Client.objects.filter(build_multiword_query(query, client_fields)).distinct()

    for c in clients:
        full_name = f"{c.client_first_name or ''} {c.client_middle_name or ''} {c.client_last_name or ''}".strip()
        results["clients"].append({
            "id": c.client_id,
            "name": full_name,
            "email": c.email,
            "contact_no": c.contact_no,
        })

    # --------------------- Passports ---------------------
    passport_fields = ["passport_no", "passport_expiry_date", "ref_client__client_id"]
    passports = ClientPassport.objects.filter(build_multiword_query(query, passport_fields)).distinct()

    for p in passports:
        results["passports"].append({
            "id": p.id,
            "passport_no": p.passport_no,
            "passport_expiry_date": p.passport_expiry_date,
            "ref_client": {
                "id": p.ref_client.client_id,
                "name": f"{p.ref_client.client_first_name or ''} {p.ref_client.client_last_name or ''}".strip()
            } if p.ref_client else None,
        })


    visas_fields = ["visa_type", "visa_from_date", "visa_to_date", "ref_client__client_id", "ref_visa_country__name"]
    visas = ClientVisa.objects.filter(build_multiword_query(query, visas_fields)).distinct()

    for v in visas:
        results["visas"].append({
            "id": v.id,
            "visa_type": v.visa_type,
            "visa_country": {
                "country": v.ref_visa_country.name,
            } if v.ref_visa_country else None,
            "visa_from_date": v.visa_from_date,
            "visa_to_date": v.visa_to_date,
            "ref_client": {
                "id": v.ref_client.client_id,
                "name": f"{v.ref_client.client_first_name or ''} {v.ref_client.client_last_name or ''}".strip()
            } if v.ref_client else None,
        })



    ins_fields = ["insurance_from_date", "insurance_to_date", "ref_client__client_id"]
    insurance = ClientTravelInsurance.objects.filter(build_multiword_query(query, ins_fields)).distinct()

    for ins in insurance:
        results["insurance"].append({
            "id": ins.id,
            "insurance_from_date": ins.insurance_from_date,
            "insurance_to_date": ins.insurance_to_date,
            "ref_client": {
                "id": ins.ref_client.client_id,
                "name": f"{ins.ref_client.client_first_name or ''}-{ins.ref_client.client_last_name or ''}".strip()
            } if ins.ref_client else None,
        })

    assoc_fields = ["ref_company__company_name", "designation", "primary_company", "ref_client__client_id"]
    assoc_company = AssocClientCompany.objects.filter(build_multiword_query(query, assoc_fields)).distinct()

    for assoc in assoc_company:
        results["assoc_company"].append({
            "ref_company": {
                # "id": assoc.ref_company.id,
                "name": assoc.ref_company.company_name or ''
            } if assoc.ref_company else None,
            "primary_company": assoc.primary_company,
            "designation": assoc.designation,
            "ref_client": {
                "id": assoc.ref_client.client_id,
                "name": f"{assoc.ref_client.client_first_name or ''}-{assoc.ref_client.client_last_name or ''}".strip()
            } if assoc.ref_client else None,
        })

    
    ff_fields = ["ref_airline__airline", "ref_client__client_id", "ff_no"]
    freq_flyer = ClientFrequentFlyer.objects.filter(build_multiword_query(query, ff_fields)).distinct()

    for ff in freq_flyer:
        results["freq_flyer"].append({
            "ref_airline": {
                # "id": assoc.ref_company.id,
                "name": ff.ref_airline.airline or ''
            } if ff.ref_airline else None,
            "ff_no": ff.ff_no,
            "ref_client": {
                "id": ff.ref_client.client_id,
                "name": f"{ff.ref_client.client_first_name or ''}-{ff.ref_client.client_last_name or ''}".strip()
            } if ff.ref_client else None,
        })

    docs_fields = ["other_document_name", "ref_client__client_id"]
    documents = ClientDocument.objects.filter(build_multiword_query(query, docs_fields)).distinct()

    for docs in documents:
        results["documents"].append({
            "other_document_name": docs.other_document_name,
            "ref_client": {
                "id": docs.ref_client.client_id,
                "name": f"{docs.ref_client.client_first_name or ''}-{docs.ref_client.client_last_name or ''}".strip()
            } if docs.ref_client else None,
        })


    # --------------------- Companies ---------------------
    company_fields = [
        "company_name", "gst_no", "company_address",
        "company_city__name", "company_state__name", "company_country__name",
        "account_concerned_person", "travel_concerned_person"
    ]

    companies = Company.objects.filter(build_multiword_query(query, company_fields)).distinct()

    for comp in companies:
        results["companies"].append({
            "id": comp.id,
            "name": comp.company_name,
            "gst_no": comp.gst_no,
        })

    # --------------------- No Results ---------------------
    if not any(results.values()):
        return JsonResponse(
            {"status": False, "message": "No records found.", "data": results},
            status=status.HTTP_404_NOT_FOUND
        )

    return JsonResponse({"status": True, "data": results}, status=status.HTTP_200_OK)


'''
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def search_records(request):
    if request.method != 'GET':
        return JsonResponse({"status": False, "message": "Invalid request method."})

    query = request.GET.get('search', '').strip()

    if len(query) < 3:
        return JsonResponse({"status": False, "message": "Please enter at least 3 characters."})

    results = {
        "clients": [],
        "companies": [],
        "passports": [],
        # "users": []
    }

    # --------------------- Clients ---------------------
    clients = Client.objects.filter(
        Q(client_first_name__icontains=query) |
        Q(client_middle_name__icontains=query) |
        Q(client_last_name__icontains=query) |
        Q(email__icontains=query) |
        Q(contact_no__icontains=query) |
        Q(client_code__icontains=query) |
        Q(residential_address__icontains=query) |             
        Q(residential_city__name__icontains=query) |          
        Q(residential_state__name__icontains=query) |         
        Q(residential_country__name__icontains=query) |
        Q(dob__icontains=query) |
        Q(gender__icontains=query) |
        Q(anniversary_date__icontains=query) |
        Q(reference_from__icontains=query) |
        Q(preferred_contact_method__icontains=query) |
        Q(aadhaar_no__icontains=query) |
        Q(pan_no__icontains=query) |
        Q(ref_preferred_airline__airline__icontains=query) |
        Q(seat_preference__icontains=query) |
        Q(seat_preference_other__icontains=query) |
        Q(meal_preference__icontains=query) |
        Q(fare_preference__icontains=query) |
        Q(star_rating__icontains=query) |
        Q(stay_preference__icontains=query) |
        Q(room_preference__icontains=query) |
        Q(extra_amenities__icontains=query) 
    ).distinct()

    for c in clients:
        full_name = f"{c.client_first_name or ''} {c.client_middle_name or ''} {c.client_last_name or ''}".strip()
        results["clients"].append({
            "id": c.client_id,
            "name": full_name,
            
        })

    passports = ClientPassport.objects.filter(
        Q(passport_no__icontains=query) |
        Q(passport_expiry_date__icontains=query) |
        Q(ref_client__client_id__icontains=query)
    ).distinct()

    for p in passports:
        results["passports"].append({
            "id": p.id,
            "passport_no": p.passport_no,
            "ref_client": {
                "id": p.ref_client.client_id,
                "name": f"{p.ref_client.client_first_name or ''} {p.ref_client.client_last_name or ''}".strip(),
            } if p.ref_client else None,
        })

    # --------------------- Companies ---------------------
    companies = Company.objects.filter(
        Q(company_name__icontains=query) |
        Q(gst_no__icontains=query) |
        Q(company_address__icontains=query) |
        Q(company_city__name__icontains=query) |
        Q(company_state__name__icontains=query) |
        Q(company_country__name__icontains=query) |
        Q(account_concerned_person__icontains=query) |
        Q(travel_concerned_person__icontains=query)
    ).distinct()

    for comp in companies:
        results["companies"].append({
            "id": comp.id,
            "name": comp.company_name,
            
        })

    # --------------------- Users ---------------------
    
    # users = User.objects.filter(
    #     Q(username__icontains=query) |
    #     Q(email__icontains=query) |
    #     Q(contact_no__icontains=query) |  
    #     Q(designation__icontains=query)   
    # ).distinct()

    # for u in users:
    #     results["users"].append({
    #         "id": u.id,
    #         "username": u.username,
    #         "email": u.email,
    #         "contact_no": u.contact_no,
    #         "designation": u.designation,
    #         "status": u.status,
    #     })
    
    
    if not any(results.values()):
        return JsonResponse({"status": False, "message": "No records found.", "data": results}, status=status.HTTP_406_NOT_ACCEPTABLE)

    return JsonResponse({"status": True, "data": results}, status=status.HTTP_200_OK)
'''


@api_view(['GET'])
@permission_classes([IsAuthenticated])
def user_list(request):
    auth_header = request.META.get("HTTP_AUTHORIZATION", "")
    token = None
    if auth_header and auth_header.startswith("Bearer "):
        token = auth_header.split(" ")[1]


    menu_id = 2
    perms = get_menu_permissions(request.user, menu_id)

    search_text = request.GET.get("search[value]", "").strip()

    users = User.objects.all()
    if search_text:
        users = users.filter(
            Q(username__icontains=search_text) |
            Q(email__icontains=search_text) |
            Q(contact_no__icontains=search_text) |
            Q(designation__icontains=search_text)
        )
    
    active_count = users.filter(is_active=True, is_superuser = False).count()
    inactive_count = users.filter(is_active=False).count()

    data = []
    for index, user in enumerate(users, start=1):
        data.append({
            "id": user.id,
            "username": user.username,
            "designation": getattr(user, 'designation', ''),
            "contact_no": getattr(user, 'contact_no', ''),
            "email": user.email,
            "is_superuser": user.is_superuser,
            "status": "Active" if user.is_active else "Inactive",
        })

    return Response({
        "success": True,
        "recordsTotal": users.count(),
        "recordsFiltered": users.count(),
        "data": data,
        "active": active_count,
        "inactive": inactive_count,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)


'''
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def user_list(request):
    auth_header = request.META.get("HTTP_AUTHORIZATION", "")
    token = None
    if auth_header and auth_header.startswith("Bearer "):
        token = auth_header.split(" ")[1]

    menu_id = 2
    perms = get_menu_permissions(request.user, menu_id)

    search_text = request.GET.get("search[value]", "").strip()

    # Filter users based on requester's superuser status
    if request.user.is_superuser:
        users = User.objects.all()
    else:
        users = User.objects.filter(is_superuser=False)

    # Apply search filter if provided
    if search_text:
        users = users.filter(
            Q(username__icontains=search_text) |
            Q(email__icontains=search_text) |
            Q(contact_no__icontains=search_text) |
            Q(designation__icontains=search_text)
        )

    active_count = users.filter(is_active=True).count()
    inactive_count = users.filter(is_active=False).count()

    data = []
    for index, user in enumerate(users, start=1):
        data.append({
            "id": user.id,
            "username": user.username,
            "designation": getattr(user, 'designation', ''),
            "contact_no": getattr(user, 'contact_no', ''),
            "email": user.email,
            "status": "Active" if user.is_active else "Inactive",
        })

    return Response({
        "success": True,
        "recordsTotal": users.count(),
        "recordsFiltered": users.count(),
        "data": data,
        "active": active_count,
        "inactive": inactive_count,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)
'''


import json
from django.http import QueryDict


@api_view(['GET', 'PUT'])
@permission_classes([IsAuthenticated])
@transaction.atomic
def update_user(request, user_id):
    user = get_object_or_404(User, id=user_id)
    actions = ['view', 'add', 'edit', 'delete', 'export']

    # -------------------- GET REQUEST --------------------
    if request.method == 'GET':
        user_data = {
            "id": user.id,
            "username": user.username,
            "email": user.email,
            "designation": user.designation or "",
            "contact_no": user.contact_no or "",
            "status": user.status,
            "is_superuser": user.is_superuser,
        }

        permissions = []
        for up in UserPermissions.objects.filter(ref_user=user):
            perm_dict = {
                "menu_id": up.ref_menu_id,
                "view": up.can_view,
                "add": up.can_add,
                "edit": up.can_edit,
                "delete": up.can_delete,
                "export": up.can_export,
            }
            # add `all` flag if all are True
            perm_dict["all"] = all(perm_dict[a] for a in actions)
            permissions.append(perm_dict)

        return Response({
            "success": True,
            "user": user_data,
            "permissions": permissions
        }, status=status.HTTP_200_OK)

    # -------------------- PUT REQUEST --------------------
    elif request.method == 'PUT':
        form = UserUpdateForm(request.data, instance=user)
        if not form.is_valid():
            return Response({"errors": form.errors}, status=status.HTTP_400_BAD_REQUEST)

        # Don't save yet — we need to set updated_by
        user = form.save(commit=False)

        # Set the updated_by to the currently logged-in user
        user.updated_by = request.user

        # --- status handling ---
        if "status" in request.data:
            if str(request.data["status"]).lower() == "active":
                user.status = "active"
                user.is_active = True
            elif str(request.data["status"]).lower() == "inactive":
                user.status = "inactive"
                user.is_active = False
            else:
                return Response({
                    "success": False,
                    "error": f"Invalid status '{request.data['status']}'. Allowed: active, inactive"
                }, status=status.HTTP_400_BAD_REQUEST)

        user.save()

        # -------------------- Build permissions dict --------------------
        permissions_dict = {}

        # CASE 1: frontend sends nested permissions array
        if "permissions" in request.data and isinstance(request.data["permissions"], list):
            for perm in request.data["permissions"]:
                menu_id = str(perm.get("menu_id"))
                if not menu_id:
                    continue
                if perm.get("all", False):  # shortcut for all True
                    permissions_dict[menu_id] = {a: True for a in actions}
                else:
                    permissions_dict[menu_id] = {a: bool(perm.get(a, False)) for a in actions}

        # CASE 2: flat keys like "menu_3_view"
        else:
            for key, value in request.data.items():
                if key.startswith("menu_"):
                    try:
                        _, menu_id, action = key.split("_")
                    except ValueError:
                        continue
                    if action not in actions:
                        continue
                    if menu_id not in permissions_dict:
                        permissions_dict[menu_id] = {a: False for a in actions}
                    permissions_dict[menu_id][action] = (str(value).lower() in ["1", "true", "yes"])

        # -------------------- Save UserPermissions --------------------
        for menu_id, new_perms in permissions_dict.items():
            obj, created = UserPermissions.objects.get_or_create(
                ref_user=user,
                ref_menu_id=menu_id,
                defaults={f'can_{a}': val for a, val in new_perms.items()}
            )
            if not created:
                for action, val in new_perms.items():
                    setattr(obj, f'can_{action}', val)
                obj.save()

        # -------------------- Prepare response --------------------
        existing_perms = []
        for up in UserPermissions.objects.filter(ref_user=user):
            perm_dict = {
                "menu_id": up.ref_menu_id,
                "view": up.can_view,
                "add": up.can_add,
                "edit": up.can_edit,
                "delete": up.can_delete,
                "export": up.can_export,
            }
            # add `all` flag
            perm_dict["all"] = all(perm_dict[a] for a in actions)
            existing_perms.append(perm_dict)

        return Response({
            "success": True,
            "message": f"User '{user.username}' updated successfully.",
            "user": {
                "id": user.id,
                "username": user.username,
                "email": user.email,
                "contact_no": user.contact_no or "",
                "designation": user.designation or "",
                "status": user.status or ("Active" if user.is_active else "Inactive"),
            },
            "permissions": existing_perms
        }, status=status.HTTP_200_OK)



'''
@api_view(['PUT'])
@permission_classes([IsAuthenticated])  # change later to IsAuthenticated + custom perms
@transaction.atomic
def update_user(request, user_id):
    """
    Update user details and their menu permissions.
    """
    user = get_object_or_404(User, id=user_id)
    actions = ['view', 'add', 'edit', 'delete', 'export']

    # --- validate with form OR serializer (here reusing form) ---
    form = UserUpdateForm(request.data, instance=user)
    if not form.is_valid():
        return Response({"errors": form.errors}, status=status.HTTP_400_BAD_REQUEST)

    # save user details
    user = form.save()

    # Build permissions dictionary from request.data
    permissions_dict = {}
    for key, value in request.data.items():
        if key.startswith('menu_'):
            try:
                _, menu_id, action = key.split('_')
            except ValueError:
                continue
            if action not in actions:
                continue
            if menu_id not in permissions_dict:
                permissions_dict[menu_id] = {a: False for a in actions}  # default False
            permissions_dict[menu_id][action] = (str(value) == '1' or str(value).lower() == 'true')

    # Update or create UserPermissions
    for menu_id, new_perms in permissions_dict.items():
        obj, created = UserPermissions.objects.get_or_create(
            ref_user=user,
            ref_menu_id=menu_id,
            defaults={f'can_{a}': val for a, val in new_perms.items()}
        )
        if not created:
            for action, val in new_perms.items():
                setattr(obj, f'can_{action}', val)
            obj.save()

    # Prepare response
    existing_perms = {
        up.ref_menu_id: {
            'view': up.can_view,
            'add': up.can_add,
            'edit': up.can_edit,
            'delete': up.can_delete,
            'export': up.can_export,
        }
        for up in UserPermissions.objects.filter(ref_user=user)
    }

    return Response({
        "success": True,
        "message": f"User '{user.username}' updated successfully.",
        "user": {
            "id": user.id,
            "username": user.username,
            "email": user.email,
        },
        "permissions": existing_perms
    }, status=status.HTTP_200_OK)
'''


@api_view(['POST'])
@permission_classes([IsAuthenticated])
@transaction.atomic
def create_user(request):
    actions = ['view', 'add', 'edit', 'delete', 'export']

    if request.user.is_superuser != True:
        return Response({'error': 'Only admins can create the users.'}, status=status.HTTP_403_FORBIDDEN)
    
    data = request.data

    serializer = UserCreateForm(data=data)
    if serializer.is_valid():
        # Pass commit=False to modify the instance before saving
        user = serializer.save(commit=False)

        # Set created_by to the currently logged-in user
        user.created_by = request.user

        # Set default password
        characters = string.ascii_letters + string.digits
        default_password = ''.join(secrets.choice(characters) for _ in range(8))
        user.set_password(default_password)

        if hasattr(user, 'first_login'):
            user.first_login = True

        user.save()

    # serializer = UserCreateForm(data=data)  # assuming you already have a DRF Serializer for UserCreateForm
    # if serializer.is_valid():
    #     user = serializer.save()

    #     # Generate random default password
    #     characters = string.ascii_letters + string.digits
    #     default_password = ''.join(secrets.choice(characters) for _ in range(8))
    #     user.set_password(default_password)

    #     if hasattr(user, 'first_login'):
    #         user.first_login = True

    #     user.save()

        # Send email with default password
        send_mail(
            subject='My Value Trip! | Account Has Been Created',
            message=(
                f'Hello {user.username},\n\n'
                f'Welcome to My Value Trip!\n\n'
                f'Your account has been created.\n'
                f'Your default password is: {default_password}\n\n'
                f'Please login and change your password immediately.'
            ),
            from_email='no-reply@myvaluetrip.com',
            recipient_list=[user.email],
            fail_silently=False,
        )

        # Handle permissions from payload
        # Expect request.data['permissions'] as list of dicts:
        # [{"menu_id": 1, "view": true, "add": false, "edit": true, ...}, ...]
        permissions_data = data.get('permissions', [])
        ups = []
        for perm in permissions_data:
            menu_id = perm.get('menu_id')
            if not menu_id:
                continue
            ups.append(UserPermissions(
                ref_user=user,
                ref_menu_id=menu_id,
                can_view=perm.get('view', False),
                can_add=perm.get('add', False),
                can_edit=perm.get('edit', False),
                can_delete=perm.get('delete', False),
                can_export=perm.get('export', False),
            ))

        if ups:
            UserPermissions.objects.bulk_create(ups)

        return Response(
            {
                "success": True,
                "message": f"User '{user.username}' created successfully"
            }, status=status.HTTP_201_CREATED)

    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


# @login_required
@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def user_profile_view(request, user_id):
    user = get_object_or_404(User, id=user_id)

    if request.method == 'POST':
        user.username = request.data.get('user_name', user.username)
        user.contact_no = request.data.get('contact_no', user.contact_no)
        user.designation = request.data.get('designation', user.designation)

        old_password = request.data.get('old_password')
        new_password = request.data.get('new_password')
        confirm_password = request.data.get('confirm_password')

        # --- Password update logic ---
        if old_password or new_password or confirm_password:
            if not old_password or not new_password or not confirm_password:
                return Response({"success": False, "message": "All password fields are required."}, status=status.HTTP_400_BAD_REQUEST)

            if not user.check_password(old_password):
                return Response({"success": False, "message": "Old password is incorrect."}, status=status.HTTP_400_BAD_REQUEST)

            if new_password != confirm_password:
                return Response({"success": False, "message": "New password and confirm password do not match."}, status=status.HTTP_400_BAD_REQUEST)

            user.set_password(new_password)
            user.save()

            # Ensure session stays active
            update_session_auth_hash(request, user)

            return Response({"success": True, "message": "Password updated successfully."}, status=status.HTTP_200_OK)

        user.save()
        return Response({"success": True, "message": "Profile updated successfully."}, status=status.HTTP_200_OK)

    # --- GET request: return profile data ---
    context = {
        "success": True,
        "user": {
            "id": user.id,
            "username": user.username,
            "email": user.email,
            "designation": user.designation,
            "contact_no": user.contact_no,
        }
    }
    return Response(context, status=status.HTTP_200_OK)


@csrf_exempt
def password_validate(request, user_id):
    user = get_object_or_404(User, id=user_id)
    old_password = request.POST.get('old_password')

    if user.check_password(old_password):
        return JsonResponse(True, safe=False)
    return JsonResponse(False, safe=False)

'''
@login_required
def create_company(request):
    if request.method == 'POST':
        form = CompanyForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('company_success')  # Redirect to a success page
    else:
        form = CompanyForm()
    
    return render(request, 'company/create_company.html', {'form': form},
        {
        'menuaction': 'COMPANY',
        'assigned_menu': ['DASHBOARD', 'USER', 'COMPANY', 'CLIENT', 'CLIENT_LOG', 'report'],  # include 'USER' here
        # 'is_default': 1,
    })
'''
    

# @login_required

@transaction.atomic
@api_view(['DELETE'])
@permission_classes([IsAuthenticated])
def delete_user(request, user_id):
    if request.method in ["POST", "DELETE"]:  # allow both DELETE and POST (for flexibility)
        user = get_object_or_404(User, id=user_id)
        username = user.username

        # old_data = serialize_instance(user)
        # old_data["username"] = normalize_value(user.username)
        # old_data["created_by"] = normalize_value(user.created_by)
        # old_data["updated_by"] = normalize_value(user.updated_by)

        # user_pk = user.pk
        # user_table = user._meta.db_table
        
        # Delete the company
        user.delete()
        
        # Create audit log
        # ClientLog.objects.create(
        #     ref_user_id=None,  # company is deleted, detach log
        #     ref_table_name=user_table,
        #     ref_id=user_pk,
        #     action_type="USER DELETED",
        #     changed_data=json.loads(json.dumps(build_changes(old_data, None), cls=DjangoJSONEncoder)),
        #     performed_by=request.user,
        #     performed_at=timezone.now(),
        # )
        return JsonResponse({"success": True, "message": f"User '{username}' deleted."}, status=status.HTTP_200_OK)
    else:
        return JsonResponse({"success": False, "error": "Invalid request method."}, status=status.HTTP_400_BAD_REQUEST)
        


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def logout_view(request):
    refresh_token = request.data.get("refresh")
    if not refresh_token:
        return Response(
            {"status": False, "message": "Refresh token is required."},
            status=status.HTTP_400_BAD_REQUEST
        )
    try:
        token = RefreshToken(refresh_token)
        token.blacklist()
        return Response(
            {"status": True, "message": "Logged out successfully."},
            status=status.HTTP_205_RESET_CONTENT
        )
    except TokenError:
        return Response(
            {"status": False, "message": "Invalid or expired refresh token."},
            status=status.HTTP_400_BAD_REQUEST
        )


'''
@csrf_exempt
@login_required
def client_search(request):
    query = request.GET.get('q', '')
    query = query.strip()

    if not query:
        return JsonResponse({'results': []})  # Return empty if no query

    clients = Client.objects.filter(
        Q(client_first_name__icontains=query) |
        Q(client_last_name__icontains=query) |
        Q(email__icontains=query) |
        Client.objects.filter(client_companies__company__company_name__icontains=query) |
        Q(company__company_name__icontains=query) |
        # Q(client_first_name__icontains=query) |
        Q(client_middle_name__icontains=query) |
        # Q(client_last_name__icontains=query) |
        Q(email__icontains=query) |
        Q(contact_no__icontains=query) |
        Q(residential_city__name__icontains=query) |
        Q(ref_company__company_name__icontains=query) |
        Q(crossrelation__member_first_name__icontains=query) |
        Q(crossrelation__member_last_name__icontains=query) |
        Q(crossrelation__member_relation__icontains=query)
    ).distinct()

    data = []
    for i, client in enumerate(clients, start=1):
        data.append({
            'id': i,
            'client_code': client.client_code or '',
            'full_name': f"{client.client_first_name} {client.client_middle_name or ''} {client.client_last_name or ''}".strip(),
            'contact_no': client.contact_no or '',
            'email': client.email or '',
            'residential_city': client.residential_city.name if client.residential_city else '',
            'company': client.ref_company_id.company_name if getattr(client, 'ref_company_id', None) else '',
            'type': '-',  # Customize if needed
            'created_at': client.created_at.strftime('%d-%m-%Y') if client.created_at else '',
            'client_status': client.client_status,
            'actions': f'''
                # <div class="dropdown text-center">
                #     <button class="btn btn-sm btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
                #         <i class="fas fa-ellipsis-v"></i>
                #     </button>
                #     <ul class="dropdown-menu">
                #         <li><a class="dropdown-item" href="/client/view/{client.client_id}/">View</a></li>
                #         <li><a class="dropdown-item" href="/client/edit/{client.client_id}/">Edit</a></li>
                #         <li><a class="dropdown-item text-danger" href="#" onclick="change_status({client.client_id}, '{'inactive' if client.client_status == 'active' else 'active'}')">
                #             {'Deactivate' if client.client_status == 'active' else 'Activate'}
                #         </a></li>
                #     </ul>
                # </div>
            # '''
        # })

    # return JsonResponse({'results': data})


# @csrf_exempt
# @login_required
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def client_search(request):
    query = request.GET.get('q', '').strip()
    if not query:
        return JsonResponse({'results': []})

    clients = Client.objects.filter(
        Q(client_first_name__icontains=query) |
        Q(client_middle_name__icontains=query) |
        Q(client_last_name__icontains=query) |
        Q(email__icontains=query) |
        Q(contact_no__icontains=query) |
        Q(residential_city__name__icontains=query) 
        # Q(company__company_name__icontains=query) |  # Direct company search
        # Q(client_companies__ref_company_id__company_name__icontains=query)  # Through AssocClientCompany
    ).distinct()

    data = []
    for i, client in enumerate(clients, start=1):
        data.append({
            'id': i,
            'client_code': client.client_code or '',
            'full_name': f"{client.client_first_name} {client.client_middle_name or ''} {client.client_last_name or ''}".strip(),
            'contact_no': client.contact_no or '',
            'email': client.email or '',
            'residential_city': client.residential_city.name if client.residential_city else '',
            # 'company': client.company.company_name if client.company else '',  # Display the direct company name
            'type': client.client_type or '',  # Customize as needed
            'created_at': client.created_at.strftime('%d-%m-%Y') if client.created_at else '',
            'client_status': client.client_status,
            # 'actions': f'''
            #     <div class="dropdown text-center">
            #         <button class="btn btn-sm btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
            #             <i class="fas fa-ellipsis-v"></i>
            #         </button>
            #         <ul class="dropdown-menu">
            #             <li><a class="dropdown-item" href="/client/view/{client.client_id}/">View</a></li>
            #             <li><a class="dropdown-item" href="/client/edit/{client.client_id}/">Edit</a></li>
            #             <li><a class="dropdown-item text-danger" href="#" onclick="change_status({client.client_id}, '{'inactive' if client.client_status == 'active' else 'active'}')">
            #                 {'Deactivate' if client.client_status == 'active' else 'Activate'}
            #             </a></li>
            #         </ul>
            #     </div>
            # '''
        })

    return JsonResponse({'results': data})

'''
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def client_list(request):

    menu_id = 4
    perms = get_menu_permissions(request.user, menu_id)

    # Prefetch companies with related company info using Prefetch for efficiency
    companies_prefetch = Prefetch(
        'client_companies',
        queryset=AssocClientCompany.objects.select_related('ref_company').only(
            'id', 'designation', 'primary_company', 'ref_company__id', 'ref_company__company_name'
        ),
        to_attr='prefetched_companies'
    )

    # Fetch primary clients with select_related for residential city + prefetch companies as optimized query
    primary_clients = Client.objects.filter(ref_client__isnull=True)\
        .select_related('residential_city')\
        .prefetch_related(companies_prefetch)\
        .only(
            'client_id', 'client_first_name', 'client_middle_name', 'client_last_name',
            'client_code', 'contact_no', 'is_prepayment', 'email', 'client_type',
            'created_at', 'client_status', 'is_active', 'residential_city__city_name'
        )

    data = []

    for client in primary_clients:
        full_name = f"{client.client_first_name or ''} {client.client_middle_name or ''} {client.client_last_name or ''}".strip()
        status_label = getattr(client, 'get_client_status_display', lambda: client.client_status)()
        
        companies = []
        for assoc in getattr(client, 'prefetched_companies', []):
            companies.append({
                "company_id": assoc.ref_company.id if assoc.ref_company else None,
                "company_name": assoc.ref_company.company_name if assoc.ref_company else None,
                "designation": assoc.designation,
                "primary_company": assoc.primary_company,
            })

        client_data = {
            'id': client.client_id,
            'client_code': client.client_code or '',
            'full_name': full_name,
            'contact_no': client.contact_no or '',
            'is_prepayment': client.is_prepayment,
            'email': client.email or '',
            'residential_city': str(client.residential_city) if client.residential_city else '',
            'companies': companies,
            'type': client.client_type or '',
            'created_at': client.created_at.strftime('%d-%m-%Y') if client.created_at else '',
            'client_status': client.client_status,
            'status_label': status_label,
            'is_active': client.is_active,
            'family_members': []
        }

        # Fetch family members (linked clients) optimized similarly
        family_members = Client.objects.filter(ref_client=client.client_id)\
            .select_related('residential_city')\
            .prefetch_related(companies_prefetch)\
            .only(
                'client_id', 'client_first_name', 'client_middle_name', 'client_last_name',
                'client_code', 'contact_no', 'email', 'relation', 'crossrelation',
                'client_type', 'created_at', 'client_status', 'is_active', 'residential_city__city_name'
            )

        for fam in family_members:
            fam_name = f"{fam.client_first_name or ''} {fam.client_middle_name or ''} {fam.client_last_name or ''}".strip()
            fam_companies = []
            for assoc in getattr(fam, 'prefetched_companies', []):
                fam_companies.append({
                    "company_id": assoc.ref_company.id if assoc.ref_company else None,
                    "company_name": assoc.ref_company.company_name if assoc.ref_company else None,
                    "designation": assoc.designation,
                    "primary_company": assoc.primary_company,
                })

            fam_status_label = getattr(fam, 'get_client_status_display', lambda: fam.client_status)()

            client_data["family_members"].append({
                'id': fam.client_id,
                'client_code': fam.client_code or '',
                'full_name': fam_name,
                'contact_no': fam.contact_no or '',
                'email': fam.email or '',
                'residential_city': str(fam.residential_city) if fam.residential_city else '',
                'companies': fam_companies,
                'relation': fam.relation,
                'crossRelation': fam.crossrelation,
                'type': fam.client_type or '',
                'created_at': fam.created_at.strftime('%d-%m-%Y') if fam.created_at else '',
                'client_status': fam.client_status,
                'status_label': fam_status_label,
                'is_active': fam.is_active,
            })

        data.append(client_data)

    # Counts - keep as is
    active_count = Client.objects.filter(is_active=1).count()
    inactive_count = Client.objects.filter(is_active=0).count()
    total_count = Client.objects.count()

    return Response({
        'success': True,
        'data': data,
        'active': active_count,
        'inactive': inactive_count,
        'total_clients': total_count,
        'permissions': {
            'can_view': perms.can_view,
            'can_add': perms.can_add,
            'can_edit': perms.can_edit,
            'can_delete': perms.can_delete,
            'can_export': perms.can_export,
        } if perms else {}
    })
''' 

# @api_view(['GET'])
# @permission_classes([IsAuthenticated])
# def client_list(request):

#     menu_id =  4
#     perms = get_menu_permissions(request.user, menu_id)
#     # Only fetch primary clients (those without ref_client_id)
#     # primary_clients = Client.objects.filter(ref_client__isnull=True)
#     primary_clients = Client.objects.filter(ref_client__isnull=True)\
#         .select_related('residential_city')\
#         .prefetch_related('client_companies__ref_company')
#     data = []

#     for client in primary_clients:
#         full_name = f"{client.client_first_name or ''}{client.client_middle_name or ''} {client.client_last_name or ''}".strip()
#         actual_status = client.client_status  

#         try:
#             status_label = client.get_client_status_display()
#         except AttributeError:
#             status_label = actual_status

#         # Get associated companies
#         companies = []
#         for assoc in client.client_companies.all():
#             companies.append({
#                 "company_id": assoc.ref_company.id if assoc.ref_company else None,
#                 "company_name": assoc.ref_company.company_name if assoc.ref_company else None,
#                 "designation": assoc.designation,
#                 "primary_company": assoc.primary_company,
#             })

#         # Build parent (primary client) info
#         client_data = {
#             'id': client.client_id,
#             'client_code': client.client_code or '',
#             'full_name': full_name,
#             'contact_no': client.contact_no or '',
#             'is_prepayment': client.is_prepayment,
#             'email': client.email or '',
#             'residential_city': str(client.residential_city) if client.residential_city else '',
#             'companies': companies,
#             'type': client.client_type or '',
#             'created_at': client.created_at.strftime('%d-%m-%Y') if client.created_at else '',
#             'client_status': actual_status,
#             'status_label': status_label,
#             'is_active': client.is_active,
#             'family_members': []
#         }

#         # --- Fetch family members (linked clients)
#         # family_members = Client.objects.filter(ref_client=client.client_id)
#         family_members = Client.objects.filter(ref_client=client.client_id)\
#             .select_related('residential_city')\
#             .prefetch_related('client_companies__ref_company')
#         for fam in family_members:
#             fam_name = f"{fam.client_first_name or ''} {fam.client_middle_name or ''} {fam.client_last_name or ''}".strip()
#             fam_companies = []
#             for assoc in fam.client_companies.all():
#                 fam_companies.append({
#                     "company_id": assoc.ref_company.id if assoc.ref_company else None,
#                     "company_name": assoc.ref_company.company_name if assoc.ref_company else None,
#                     "designation": assoc.designation,
#                     "primary_company": assoc.primary_company,
#                 })

#             client_data["family_members"].append({
#                 'id': fam.client_id,
#                 'client_code': fam.client_code or '',
#                 'full_name': fam_name,
#                 'contact_no': fam.contact_no or '',
#                 'email': fam.email or '',
#                 'residential_city': str(fam.residential_city) if fam.residential_city else '',
#                 'companies': fam_companies,
#                 'relation': fam.relation,
#                 'crossRelation': fam.crossrelation,
#                 'type': fam.client_type or '',
#                 'created_at': fam.created_at.strftime('%d-%m-%Y') if fam.created_at else '',
#                 'client_status': fam.client_status,
#                 'status_label': getattr(fam, "get_client_status_display", lambda: fam.client_status)(),
#                 'is_active': fam.is_active,
#             })

#         data.append(client_data)

#     # Counts (still overall, across all clients)
#     active_count = Client.objects.filter(is_active=1).count()
#     inactive_count = Client.objects.filter(is_active=0).count()
#     total_count = Client.objects.count()

#     return Response({
#         'success': True,
#         'data': data,
#         'active': active_count,
#         'inactive': inactive_count,
#         'total_clients': total_count,
#         "permissions": {
#             "can_view": perms.can_view,
#             "can_add": perms.can_add,
#             "can_edit": perms.can_edit,
#             "can_delete": perms.can_delete,
#             "can_export": perms.can_export,
#         } if perms else {}
#     }, status=status.HTTP_200_OK)

# @api_view(['GET'])
# @permission_classes([IsAuthenticated])
# def client_list(request):
#     # Get permissions
#     menu_id = 4
#     perms = get_menu_permissions(request.user, menu_id)

#     # Optimize queryset with select_related and Prefetch
#     primary_clients = Client.objects.filter(ref_client__isnull=True).select_related(
#         'residential_city', 'country_code', 'residential_state', 'residential_country',
#         'ref_preferred_airline', 'created_by', 'updated_by'
#     ).prefetch_related(
#         Prefetch(
#             'client_companies',
#             queryset=AssocClientCompany.objects.select_related(
#                 'ref_company__company_country',
#                 'ref_company__company_state',
#                 'ref_company__company_city',
#                 'ref_company__account_concerned_person_country_code',
#                 'ref_company__travel_concerned_person_country_code'
#             )
#         ),
#         Prefetch(
#             'related_clients',
#             queryset=Client.objects.filter(ref_client__isnull=False)
#                 .select_related(
#                     'residential_city', 'country_code', 'residential_state',
#                     'residential_country', 'ref_preferred_airline', 'created_by', 'updated_by'
#                 ).prefetch_related(
#                     Prefetch(
#                         'client_companies',
#                         queryset=AssocClientCompany.objects.select_related(
#                             'ref_company__company_country',
#                             'ref_company__company_state',
#                             'ref_company__company_city',
#                             'ref_company__account_concerned_person_country_code',
#                             'ref_company__travel_concerned_person_country_code'
#                         )
#                     )
#                 )
#         )
#     )

#     # Use serializer for data transformation
#     serializer = ClientSerializer(primary_clients, many=True, context={'request': request})

#     # Counts using separate queries (as in original code)
#     active_count = Client.objects.filter(is_active=1).count()
#     inactive_count = Client.objects.filter(is_active=0).count()
#     total_count = Client.objects.count()

#     return Response({
#         'success': True,
#         'data': serializer.data,
#         'active': active_count,
#         'inactive': inactive_count,
#         'total_clients': total_count,
#         'permissions': {
#             'can_view': perms.can_view,
#             'can_add': perms.can_add,
#             'can_edit': perms.can_edit,
#             'can_delete': perms.can_delete,
#             'can_export': perms.can_export,
#         } if perms else {}
#     }, status=status.HTTP_200_OK)

# @api_view(['GET'])
# @permission_classes([IsAuthenticated])
# def client_list(request):
#     try:
#         # Get permissions
#         menu_id = 4
#         perms = get_menu_permissions(request.user, menu_id)

#         # Optimize queryset with select_related and Prefetch
#         primary_clients = Client.objects.filter(ref_client__isnull=True).select_related(
#             'residential_city', 'country_code', 'residential_state', 'residential_country',
#             'ref_preferred_airline', 'created_by', 'updated_by'
#         ).prefetch_related(
#             Prefetch(
#                 'client_companies',
#                 queryset=AssocClientCompany.objects.select_related(
#                     'ref_company__company_country',
#                     'ref_company__company_state',
#                     'ref_company__company_city',
#                     'ref_company__account_concerned_person_country_code',
#                     'ref_company__travel_concerned_person_country_code'
#                 )
#             ),
#             Prefetch(
#                 'related_clients',
#                 queryset=Client.objects.filter(ref_client__isnull=False)
#                     .select_related(
#                         'residential_city', 'country_code', 'residential_state',
#                         'residential_country', 'ref_preferred_airline', 'created_by', 'updated_by'
#                     ).prefetch_related(
#                         Prefetch(
#                             'client_companies',
#                             queryset=AssocClientCompany.objects.select_related(
#                                 'ref_company__company_country',
#                                 'ref_company__company_state',
#                                 'ref_company__company_city',
#                                 'ref_company__account_concerned_person_country_code',
#                                 'ref_company__travel_concerned_person_country_code'
#                             )
#                         )
#                     )
#             )
#         )

#         # Use serializer for data transformation
#         serializer = ClientSerializer(primary_clients, many=True, context={'request': request})

#         # Counts using separate queries (as in original code)
#         active_count = Client.objects.filter(is_active=1).count()
#         inactive_count = Client.objects.filter(is_active=0).count()
#         total_count = Client.objects.count()

#         return Response({
#             'success': True,
#             'data': serializer.data,
#             'active': active_count,
#             'inactive': inactive_count,
#             'total_clients': total_count,
#             'permissions': {
#                 'can_view': perms.can_view,
#                 'can_add': perms.can_add,
#                 'can_edit': perms.can_edit,
#                 'can_delete': perms.can_delete,
#                 'can_export': perms.can_export,
#             } if perms else {}
#         }, status=status.HTTP_200_OK)
#     except Exception as e:
#         return Response({'success': False, 'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def client_export(request):
    try:
        # Get permissions
        menu_id = 4
        perms = get_menu_permissions(request.user, menu_id)

        # Optimize queryset with select_related and Prefetch
        primary_clients = Client.objects.filter(ref_client__isnull=True).select_related(
            'residential_city',
            'country_code',
            'residential_state',
            'residential_country',
            'ref_preferred_airline',
            'created_by',
            'updated_by'
        ).prefetch_related(
            Prefetch(
                'client_companies',
                queryset=AssocClientCompany.objects.select_related(
                    'ref_company__company_country',
                    'ref_company__company_state',
                    'ref_company__company_city'
                )
            ),
            Prefetch(
                'related_clients',
                queryset=Client.objects.filter(ref_client__isnull=False).select_related(
                    'residential_city',
                    'country_code',
                    'residential_state',
                    'residential_country',
                    'ref_preferred_airline',
                    'created_by',
                    'updated_by'
                ).prefetch_related(
                    Prefetch(
                        'client_companies',
                        queryset=AssocClientCompany.objects.select_related(
                            'ref_company__company_country',
                            'ref_company__company_state',
                            'ref_company__company_city'
                        )
                    )
                )
            )
        )

        # Serialize data
        serializer = ClientExportSerializer(primary_clients, many=True, context={'request': request})
        # serializer = ClientSerializer(primary_clients, many=True, context={'request': request})

        # Use a single query for counts with annotation
        counts = Client.objects.aggregate(
            active_count=models.Count('pk', filter=models.Q(is_active=1)),
            inactive_count=models.Count('pk', filter=models.Q(is_active=0)),
            total_count=models.Count('pk')
        )

        return Response({
            'success': True,
            'data': serializer.data,
            'active': counts['active_count'],
            'inactive': counts['inactive_count'],
            'total_clients': counts['total_count'],
            'permissions': {
                'can_view': perms.can_view,
                'can_add': perms.can_add,
                'can_edit': perms.can_edit,
                'can_delete': perms.can_delete,
                'can_export': perms.can_export,
            } if perms else {}
        }, status=status.HTTP_200_OK)

    except Exception as e:
        return Response(
            {'success': False, 'error': f'Internal server error: {str(e)}'},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )
        
class CustomPageNumberPagination(PageNumberPagination):
    page_size = 10  # Default items per page
    page_size_query_param = 'limit'  # Allow ?limit=X to override page size
    max_page_size = 100  # Default max page size, can be overridden by query param
      
# last being used 7/11/2025
# @api_view(['GET'])
# @permission_classes([IsAuthenticated])
# def client_list(request):
#     try:
#         # Initialize pagination
#         paginator = CustomPageNumberPagination()
        
#         # Get max_page_size from query parameters, if provided
#         max_limit = request.query_params.get('max_limit', None)
#         if max_limit:
#             try:
#                 paginator.max_page_size = min(int(max_limit), 1000)  # Cap at a reasonable maximum
#             except ValueError:
#                 pass  # Ignore invalid max_limit values, use default

#         # Get search query from request
#         search_query = request.query_params.get('search', None)

#         # Get page navigation parameter
#         page_action = request.query_params.get('page', None)

#         # Get permissions
#         menu_id = 4
#         perms = get_menu_permissions(request.user, menu_id)

#         # Optimize queryset with select_related and Prefetch
#         primary_clients = Client.objects.filter(ref_client__isnull=True)
        
#         # Apply search filter if provided
#         if search_query:
#             primary_clients = primary_clients.filter(
#                 Q(client_code__icontains=search_query) |
#                 # Q(full_name__icontains=search_query) |
#                 Q(contact_no__icontains=search_query) |
#                 Q(email__icontains=search_query) |
#                 Q(client_first_name__icontains=search_query) |
#                 Q(client_middle_name__icontains=search_query, client_middle_name__isnull=False) |
#                 Q(client_last_name__icontains=search_query) |
#                 Q(residential_city__name__icontains=search_query, residential_city__isnull=False) |
#                 Q(residential_country__name__icontains=search_query, residential_country__isnull=False) |
#                 Q(residential_state__name__icontains=search_query, residential_state__isnull=False) |
#                 Q(client_type__icontains=search_query)
#             )
            
#         # # Apply search filter if provided
#         # if search_query:
#         #     primary_clients = primary_clients.filter(
#         #         Q(client_code__icontains=search_query) |
#         #         Q(contact_no__icontains=search_query) |
#         #         Q(email__icontains=search_query) |
#         #         Q(client_first_name__icontains=search_query) |
#         #         Q(client_middle_name__icontains=search_query, client_middle_name__isnull=False) |
#         #         Q(client_last_name__icontains=search_query) |
#         #         Q(residential_city__name__icontains=search_query, residential_city__isnull=False) |
#         #         Q(residential_country__name__icontains=search_query, residential_country__isnull=False) |
#         #         Q(residential_state__name__icontains=search_query, residential_state__isnull=False) |
#         #         Q(client_type__icontains=search_query) |
#         #         Q(client_companies__ref_company__company_name__icontains=search_query)
#         #     ).distinct()  # Add distinct to avoid duplicate clients from multiple company matches

#         primary_clients = primary_clients.select_related(
#             'residential_city',
#             'country_code',
#             'residential_state',
#             'residential_country',
#             'ref_preferred_airline',
#             'created_by',
#             'updated_by'
#         ).prefetch_related(
#             Prefetch(
#                 'client_companies',
#                 queryset=AssocClientCompany.objects.select_related(
#                     'ref_company__company_country',
#                     'ref_company__company_state',
#                     'ref_company__company_city'
#                 )
#             ),
#             Prefetch(
#                 'related_clients',
#                 queryset=Client.objects.filter(ref_client__isnull=False).select_related(
#                     'residential_city',
#                     'country_code',
#                     'residential_state',
#                     'residential_country',
#                     'ref_preferred_airline',
#                     'created_by',
#                     'updated_by'
#                 ).prefetch_related(
#                     Prefetch(
#                         'client_companies',
#                         queryset=AssocClientCompany.objects.select_related(
#                             'ref_company__company_country',
#                             'ref_company__company_state',
#                             'ref_company__company_city'
#                         )
#                     )
#                 )
#             )
#         )

#         # Handle page navigation
#         if page_action:
#             try:
#                 total_count = primary_clients.count()
#                 limit = paginator.get_page_size(request)
#                 total_pages = (total_count + limit - 1) // limit  # Ceiling division for total pages

#                 if page_action == 'first':
#                     page_number = 1
#                 elif page_action == 'last':
#                     page_number = max(1, total_pages)
#                 elif page_action == 'next':
#                     current_page = int(request.query_params.get('page_number', 1))
#                     page_number = min(current_page + 1, total_pages)
#                 elif page_action == 'previous':
#                     current_page = int(request.query_params.get('page_number', 1))
#                     page_number = max(1, current_page - 1)
#                 else:
#                     # Assume page_action is a page number
#                     page_number = int(page_action)
#                     if page_number < 1 or page_number > total_pages:
#                         raise ValueError("Invalid page number")

#                 # Update request query parameters for pagination
#                 query_params = request.query_params.copy()
#                 query_params['page'] = str(page_number)
#                 request._request.GET = query_params
#             except ValueError:
#                 # Handle invalid page_action or page_number, fallback to page 1
#                 query_params = request.query_params.copy()
#                 query_params['page'] = '1'
#                 request._request.GET = query_params

#         # Apply pagination
#         paginated_clients = paginator.paginate_queryset(primary_clients, request)

#         # Serialize data
#         serializer = ClientSerializer(paginated_clients, many=True, context={'request': request,'search': search_query })

#         # Use a single query for counts with annotation
#         counts = Client.objects.aggregate(
#             active_count=models.Count('pk', filter=models.Q(is_active=1)),
#             inactive_count=models.Count('pk', filter=models.Q(is_active=0)),
#             total_count=models.Count('pk')
#         )

#         return Response({
#             'success': True,
#             'data': serializer.data,
#             'count': paginator.page.paginator.count,  # Fixed: Use paginator.page.paginator.count
#             'current_page': paginator.page.number,
#             'total_pages': paginator.page.paginator.num_pages,
#             'next': paginator.get_next_link(),
#             'previous': paginator.get_previous_link(),
#             'active': counts['active_count'],
#             'inactive': counts['inactive_count'],
#             'total_clients': counts['total_count'],
#             'permissions': {
#                 'can_view': perms.can_view,
#                 'can_add': perms.can_add,
#                 'can_edit': perms.can_edit,
#                 'can_delete': perms.can_delete,
#                 'can_export': perms.can_export,
#             } if perms else {},
#             'max_limit': paginator.max_page_size
#         }, status=status.HTTP_200_OK)

#     except Exception as e:
#         return Response(
#             {'success': False, 'error': f'Internal server error: {str(e)}'},
#             status=status.HTTP_500_INTERNAL_SERVER_ERROR
#         )

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def client_list(request):
    try:
        # Initialize pagination
        paginator = CustomPageNumberPagination()
        
        # Allow dynamic max_page_size via query param
        max_limit = request.query_params.get('max_limit', None)
        if max_limit:
            try:
                paginator.max_page_size = min(int(max_limit), 1000)
            except ValueError:
                pass

        # Get search query
        search_query = request.query_params.get('search', '').strip()

        # Page navigation
        page_action = request.query_params.get('page', None)

        # Permissions
        menu_id = 4
        perms = get_menu_permissions(request.user, menu_id)
        
        client_fields = [
            "client_first_name", "client_middle_name", "client_last_name",
            "email", "contact_no", "client_code", "residential_address",
            "residential_city__name", "residential_state__name", "residential_country__name",
            "dob", "gender", "anniversary_date", "reference_from", "preferred_contact_method",
            "aadhaar_no", "pan_no", "ref_preferred_airline__airline",
            "seat_preference", "seat_preference_other", "meal_preference",
            "fare_preference", "star_rating", "stay_preference",
            "room_preference", "extra_amenities"
        ]

        # === Build reusable search filter ===
        search_filter = Q()
        if search_query:
            search_filter = (
                Q(client_code__icontains=search_query) |
                Q(contact_no__icontains=search_query) |
                Q(email__icontains=search_query) |
                Q(client_first_name__icontains=search_query) |
                Q(client_middle_name__icontains=search_query) |
                Q(client_last_name__icontains=search_query) |
                Q(residential_city__name__icontains=search_query) |
                Q(residential_country__name__icontains=search_query) |
                Q(residential_state__name__icontains=search_query) |
                Q(client_type__icontains=search_query)
                # Optional: Include company name
                # Q(client_companies__ref_company__company_name__icontains=search_query)
            )

        # === 1. Primary Clients (ref_client__isnull=True) ===
        primary_clients = Client.objects.filter()

        if search_query:
            primary_clients = primary_clients.filter(build_multiword_query(search_query,client_fields)).distinct()
            # primary_clients = primary_clients.filter(search_filter).distinct()

        # === 2. Family Members Base Query ===
        family_base_qs = Client.objects.filter(ref_client__isnull=False)

        # Apply search to family members
        family_filtered_qs = family_base_qs
        if search_query:
            # family_filtered_qs = family_base_qs.filter(search_filter).distinct()
            family_filtered_qs = family_base_qs.filter(build_multiword_query(search_query,client_fields)).distinct()

        # === Optimize Family Members with select_related & prefetch ===
        family_prefetch_qs = family_filtered_qs.select_related(
            'residential_city',
            'country_code',
            'residential_state',
            'residential_country',
            'ref_preferred_airline',
            'created_by',
            'updated_by'
        ).prefetch_related(
            Prefetch(
                'client_companies',
                queryset=AssocClientCompany.objects.select_related(
                    'ref_company__company_country',
                    'ref_company__company_state',
                    'ref_company__company_city'
                )
            )
        )

        # === Final Primary Clients Query with Prefetched Data ===
        primary_clients = primary_clients.select_related(
            'residential_city',
            'country_code',
            'residential_state',
            'residential_country',
            'ref_preferred_airline',
            'created_by',
            'updated_by'
        ).prefetch_related(
            Prefetch(
                'client_companies',
                queryset=AssocClientCompany.objects.select_related(
                    'ref_company__company_country',
                    'ref_company__company_state',
                    'ref_company__company_city'
                )
            ),
            Prefetch(
                'related_clients',
                queryset=family_prefetch_qs  # Only matching family members
            )
        )

        # === Handle Pagination Navigation ===
        if page_action:
            try:
                total_count = primary_clients.count()
                limit = paginator.get_page_size(request)
                total_pages = (total_count + limit - 1) // limit

                if page_action == 'first':
                    page_number = 1
                elif page_action == 'last':
                    page_number = max(1, total_pages)
                elif page_action == 'next':
                    current_page = int(request.query_params.get('page_number', 1))
                    page_number = min(current_page + 1, total_pages)
                elif page_action == 'previous':
                    current_page = int(request.query_params.get('page_number', 1))
                    page_number = max(1, current_page - 1)
                else:
                    page_number = int(page_action)
                    if page_number < 1 or page_number > total_pages:
                        raise ValueError("Invalid page")

                # Update query params
                query_params = request.query_params.copy()
                query_params['page'] = str(page_number)
                request._request.GET = query_params
            except (ValueError, ZeroDivisionError):
                query_params = request.query_params.copy()
                query_params['page'] = '1'
                request._request.GET = query_params

        # === Paginate ===
        paginated_clients = paginator.paginate_queryset(primary_clients, request)

        # === Serialize ===
        serializer = ClientSerializer(
            paginated_clients,
            many=True,
            context={
                'request': request,
            }
        )

        # === Counts (single query) ===
        counts = Client.objects.aggregate(
            active_count=models.Count('pk', filter=Q(is_active=True)),
            inactive_count=models.Count('pk', filter=Q(is_active=False)),
            total_count=models.Count('pk')
        )

        # === Response ===
        return Response({
            'success': True,
            'data': serializer.data,
            'count': paginator.page.paginator.count,
            'current_page': paginator.page.number,
            'total_pages': paginator.page.paginator.num_pages,
            'next': paginator.get_next_link(),
            'previous': paginator.get_previous_link(),
            'active': counts['active_count'],
            'inactive': counts['inactive_count'],
            'total_clients': counts['total_count'],
            'permissions': {
                'can_view': perms.can_view if perms else False,
                'can_add': perms.can_add if perms else False,
                'can_edit': perms.can_edit if perms else False,
                'can_delete': perms.can_delete if perms else False,
                'can_export': perms.can_export if perms else False,
            },
            'max_limit': paginator.max_page_size
        }, status=status.HTTP_200_OK)

    except Exception as e:
        return Response(
            {'success': False, 'error': f'Internal server error: {str(e)}'},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR
        )

'''
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def client_list(request):
    menu_id = 4
    perms = get_menu_permissions(request.user, menu_id)

    clients_qs = (
        Client.objects.filter(ref_client__isnull=True)
        .select_related(
            "residential_city",
            "residential_state",
            "residential_country",
        )
        .prefetch_related(
            Prefetch(
                "client_companies",
                queryset=AssocClientCompany.objects.select_related("ref_company"),
            ),
            Prefetch(
                "related_clients",  # correct reverse accessor
                queryset=Client.objects.select_related(
                    "residential_city",
                    "residential_state",
                    "residential_country",
                ).prefetch_related(
                    Prefetch(
                        "client_companies",
                        queryset=AssocClientCompany.objects.select_related("ref_company"),
                    )
                ),
            ),
        )
    )

    serializer = ClientDataSerializer(clients_qs, many=True, context={"request": request})

    active_count = Client.objects.filter(client_status="active").count()
    inactive_count = Client.objects.filter(client_status="inactive").count()

    return Response(
        {
            "success": True,
            "data": serializer.data,
            "active": active_count,
            "inactive": inactive_count,
            "permissions": {
                "can_view": perms.can_view,
                "can_add": perms.can_add,
                "can_edit": perms.can_edit,
                "can_delete": perms.can_delete,
                "can_export": perms.can_export,
            }
            if perms else {},
        }, status=status.HTTP_200_OK)
'''


'''
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def client_list(request):
    menu_id = 4
    perms = get_menu_permissions(request.user, menu_id)

    # Fetch primary and family members separately
    primary_clients = Client.objects.filter(ref_client__isnull=True)
    family_clients = Client.objects.filter(ref_client__isnull=False)

    data = []

    # ---------- Primary Clients ----------
    for client in primary_clients:
        full_name = f"{client.client_first_name or ''} {client.client_middle_name or ''} {client.client_last_name or ''}".strip()
        actual_status = client.client_status  

        try:
            status_label = client.get_client_status_display()
        except AttributeError:
            status_label = actual_status

        companies = [
            {
                "company_id": assoc.ref_company.id if assoc.ref_company else None,
                "company_name": assoc.ref_company.company_name if assoc.ref_company else None,
                "designation": assoc.designation,
                "primary_company": assoc.primary_company,
            }
            for assoc in client.client_companies.all()
        ]

        data.append({
            "id": client.client_id,
            "client_code": client.client_code or "",
            "full_name": full_name,
            "contact_no": client.contact_no or "",
            "email": client.email or "",
            "residential_city": str(client.residential_city) if client.residential_city else "",
            "companies": companies,
            "type": client.client_type or "",
            "created_at": client.created_at.strftime('%d-%m-%Y') if client.created_at else "",
            "client_status": actual_status,
            "status_label": status_label,
            "is_active": client.is_active,
            # "is_primary": True,
        })

    # ---------- Family Members ----------
    for fam in family_clients:
        fam_name = f"{fam.client_first_name or ''} {fam.client_middle_name or ''} {fam.client_last_name or ''}".strip()
        fam_companies = [
            {
                "company_id": assoc.ref_company.id if assoc.ref_company else None,
                "company_name": assoc.ref_company.company_name if assoc.ref_company else None,
                "designation": assoc.designation,
                "primary_company": assoc.primary_company,
            }
            for assoc in fam.client_companies.all()
        ]

        data.append({
            "id": fam.client_id,
            "client_code": fam.client_code or "",
            "full_name": fam_name,
            "contact_no": fam.contact_no or "",
            "email": fam.email or "",
            "residential_city": str(fam.residential_city) if fam.residential_city else "",
            "companies": fam_companies,
            "relation": fam.relation,
            "crossRelation": fam.crossrelation,
            "type": fam.client_type or "",
            "created_at": fam.created_at.strftime('%d-%m-%Y') if fam.created_at else "",
            "client_status": fam.client_status,
            "status_label": getattr(fam, "get_client_status_display", lambda: fam.client_status)(),
            "is_active": fam.is_active,
            "primary_client_id": fam.ref_client_id,
        })

    # Counts
    active_count = Client.objects.filter(client_status="active").count()
    inactive_count = Client.objects.filter(client_status="inactive").count()

    return Response({
        "success": True,
        "data": data,
        "active": active_count,
        "inactive": inactive_count,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)
'''


'''
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def client_list(request):
    clients = Client.objects.all()
    data = []

    for client in clients:
        full_name = f"{client.client_first_name or ''} {client.client_middle_name or ''} {client.client_last_name or ''}".strip()
        actual_status = client.client_status  

        # if client_status has choices defined in model, use display name
        try:
            status_label = client.get_client_status_display()
        except AttributeError:
            status_label = actual_status

        # Get associated companies
        companies = []
        for assoc in client.client_companies.all():
            companies.append({
                "company_id": assoc.ref_company_id.id if assoc.ref_company_id else None,
                "company_name": assoc.ref_company_id.company_name if assoc.ref_company_id else None,
                "designation": assoc.designation,
                "primary_company": assoc.primary_company,
            })
            # print(companies)

        active_count = clients.filter(client_status="active").count()
        inactive_count = clients.filter(client_status="inactive").count()
        # print(active_count, inactive_count)


        data.append({
            'id': client.client_id,
            'client_code': client.client_code or '',
            'full_name': full_name,
            'contact_no': client.contact_no or '',
            'email': client.email or '',
            'residential_city': str(client.residential_city) if client.residential_city else '',
            # 'company': str(client.company) if client.company else '-',
            'companies': companies,
            'type': client.client_type or '',  # Add actual logic if needed
            'created_at': client.created_at.strftime('%d-%m-%Y') if client.created_at else '',
            'client_status': actual_status,
            'status_label': status_label,
            'is_active': client.is_active,
        })
        # print(client.client_type)
    return Response({
        'success': True,
        'data': data,
        'active': active_count,
        'inactive': inactive_count,
    }, status=status.HTTP_200_OK)
'''


# @api_view(['GET'])
# @permission_classes([IsAuthenticated])
# def client_view(request, client_id):
#     # Get client by pk or client_id field
#     # try:
#     #     client = Client.objects.get(pk=client_id)
#     # except Client.DoesNotExist:
#     #     client = get_object_or_404(Client, client_id=client_id)

#     client = (
#         Client.objects
#         .select_related('country_code', 'ref_preferred_airline')
#         .only(
#             'client_id', 'client_first_name', 'client_last_name', 'client_code',
#             'country_code__country_code', 
#             'contact_no', 'is_prepayment', 'client_type', 'email',
#             'dob', 'gender', 'anniversary_date', 'marital_status',
#             'reference_from', 'reference_id', 'reference_remark',
#             'residential_address', 'occupation', 
#             'preferred_contact_method', 'aadhaar_no', 'aadhaar_card_file',
#             'pan_no', 'pan_card_file', 'star_rating', 'seat_preference',
#             'stay_preference', 'room_preference', 'extra_amenities',
#             'meal_preference', 'fare_preference', 'is_active', 'client_status',
#             'ref_preferred_airline__id', 'ref_preferred_airline__airline'
#         )
#         .filter(client_id=client_id)
#         .first()
#     )

#     if not client:
#         client = get_object_or_404(Client, pk=client_id)

#     # Base client info
#     client_data = {
#         "client_id": client.client_id,
#         "client_name": f"{client.client_first_name} {client.client_last_name}".strip(),
#         "client_code": client.client_code,
#         "full_contact_no": f"+{client.country_code.country_code} - {client.contact_no}" if client.contact_no else "",
#         "is_prepayment": client.is_prepayment,
#         "client_type": client.client_type,
#         "email": client.email,
#         "dateOfBirth": client.dob,
#         "gender": client.gender,
#         "anniversary_date": client.anniversary_date,
#         "marital_status": client.marital_status,
#         "reference_from": client.reference_from,
#         "reference_id": client.reference_id,
#         # "clientRef_id": client.clientRef_id.client_id if client.clientRef_id else None,
#         # "clientRef": {
#         #     "id": client.clientRef_id.client_id if client.clientRef_id else None,
#         #     "name": f"{client.clientRef_id.client_first_name} {client.clientRef_id.client_last_name}".strip() if client.clientRef_id else None,
#         # },
#         "reference_remark": client.reference_remark,
#         "ResidentialAddress": client.residential_address,
#         "occupation": client.occupation,
#         # "preferred_contact_method": client.get_preferred_contact_method_display().split(", ") if client.preferred_contact_method else [],
#         "preferred_contact_method": [
#             v.strip() for v in client.get_preferred_contact_method_display().split(",")
#         ] if client.preferred_contact_method else [],
#         "aadhaar_no": client.aadhaar_no,
#         "aadhaar_card_file": client.aadhaar_card_file.url if client.aadhaar_card_file else None,
#         "pan_no": client.pan_no,
#         "pan_card_file": client.pan_card_file.url if client.pan_card_file else None,
#         "ref_preferred_airline": {
#             "id": client.ref_preferred_airline.id,
#             "name": client.ref_preferred_airline.airline 
#         } if client.ref_preferred_airline else None,
#         "star_rating": client.star_rating,
#         "seat_preference": client.seat_preference,
#         "stay_preference": client.stay_preference,
#         "room_preference": client.room_preference,
#         "extra_amenities": client.extra_amenities,
#         "meal_preference": client.meal_preference,
#         "fare_preference": client.fare_preference,
#         "is_active": client.is_active,
#         "client_status": client.client_status,
#         # "created_at": client.created_at.strftime('%d-%m-%Y'),
#         # "created_by": client.created_by,
#     }

#     # companies = AssocClientCompany.objects.filter(ref_client_id=client)
#     companies = (
#         AssocClientCompany.objects
#         .select_related('ref_company')
#         .only('id', 'designation', 'primary_company', 'ref_company__id', 'ref_company__company_name')
#         .filter(ref_client_id=client)
#     )
#     client_data["companies_data"] = [
#         {
#             "id": c.id,
#             "designation": c.designation,
#             "primary_company": c.primary_company,
#             "company": {
#                 "id": c.ref_company.id,
#                 "name": c.ref_company.company_name,
#                 "gst_no": c.ref_company.gst_no,
#             } if c.ref_company else None
#         } for c in companies
#     ]

#     # Passports
#     client_data["passport_data"] = list(
#         ClientPassport.objects.filter(ref_client_id=client).values()
#     )

#     # Frequent Flyer
#     # flyers = ClientFrequentFlyer.objects.filter(ref_client_id=client)
#     flyers = (
#         ClientFrequentFlyer.objects
#         .select_related('ref_airline')
#         .only('id', 'ff_no', 'ref_airline__id', 'ref_airline__airline')
#         .filter(ref_client_id=client)
#     )
#     client_data["flyer_data"] = [
#         {
#             "id": f.id,
#             "ff_no": f.ff_no,
#             "airline": {
#                 "id": f.ref_airline.id,
#                 "name": f.ref_airline.airline
#             } if f.ref_airline else None
#         } for f in flyers
#     ]

#     # Family members (linked clients)
#     client_data["family_data"] = list(
#         Client.objects.filter(ref_client_id=client.client_id).values()
#     )

#     # Holding Visa
#     client_data["holding_visa"] = list(
#         ClientVisa.objects.filter(ref_client_id=client).values()
#     )

#     # Travel Insurance
#     client_data["travel_insurance"] = list(
#         ClientTravelInsurance.objects.filter(ref_client_id=client).values()
#     )

#     # Other Documents
#     client_data["clients_documents"] = list(
#         ClientDocument.objects.filter(ref_client_id=client).values()
#     )

#     return Response({
#         'success': True,
#         'client_data': client_data,
#     }, status=status.HTTP_200_OK)

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def client_view(request, client_id):
    # Optimize queryset with select_related and only
    client = (
        Client.objects
        .select_related('country_code', 'ref_preferred_airline')
        .only(
            'client_id', 'client_first_name', 'client_last_name', 'client_code',
            'country_code__country_code', 
            'contact_no', 'is_prepayment', 'client_type', 'email',
            'dob', 'gender', 'anniversary_date', 'marital_status',
            'reference_from', 'reference_id', 'reference_remark',
            'residential_address', 'occupation', 
            'preferred_contact_method', 'aadhaar_no', 'aadhaar_card_file',
            'pan_no', 'pan_card_file', 'star_rating', 'seat_preference',
            'stay_preference', 'room_preference', 'extra_amenities',
            'meal_preference', 'fare_preference', 'is_active', 'client_status',
            'ref_preferred_airline__id', 'ref_preferred_airline__airline'
        )
        .filter(client_id=client_id)
        .first()
    )

    if not client:
        client = get_object_or_404(Client, pk=client_id)

    # Base client info
    client_data = {
        "client_id": client.client_id,
        "client_name": f"{client.client_first_name or ''} {client.client_last_name or ''}".strip(),
        "client_code": client.client_code,
        "full_contact_no": (
            f"+{client.country_code.country_code} - {client.contact_no}"
            if client.country_code and client.contact_no else ""
        ),
        "is_prepayment": client.is_prepayment,
        "client_type": client.client_type,
        "email": client.email,
        "dateOfBirth": client.dob,
        "gender": client.gender,
        "anniversary_date": client.anniversary_date,
        "marital_status": client.marital_status,
        "reference_from": client.reference_from,
        "reference_id": client.reference_id,
        "reference_remark": client.reference_remark,
        "ResidentialAddress": client.residential_address,
        "occupation": client.occupation,
        "preferred_contact_method": (
            [v.strip() for v in client.get_preferred_contact_method_display().split(",")]
            if client.preferred_contact_method else []
        ),
        "aadhaar_no": client.aadhaar_no,
        "aadhaar_card_file": client.aadhaar_card_file.url if client.aadhaar_card_file else None,
        "pan_no": client.pan_no,
        "pan_card_file": client.pan_card_file.url if client.pan_card_file else None,
        "ref_preferred_airline": {
            "id": client.ref_preferred_airline.id,
            "name": client.ref_preferred_airline.airline 
        } if client.ref_preferred_airline else None,
        "star_rating": client.star_rating,
        "seat_preference": client.seat_preference,
        "stay_preference": client.stay_preference,
        "room_preference": client.room_preference,
        "extra_amenities": client.extra_amenities,
        "meal_preference": client.meal_preference,
        "fare_preference": client.fare_preference,
        "is_active": client.is_active,
        "client_status": client.client_status,
    }

    # Companies
    companies = (
        AssocClientCompany.objects
        .select_related('ref_company')
        .only('id', 'designation', 'primary_company', 'ref_company__id', 'ref_company__company_name', 'ref_company__gst_no')
        .filter(ref_client_id=client)
    )
    client_data["companies_data"] = [
        {
            "id": c.id,
            "designation": c.designation,
            "primary_company": c.primary_company,
            "company": {
                "id": c.ref_company.id,
                "name": c.ref_company.company_name,
                "gst_no": c.ref_company.gst_no,
            } if c.ref_company else None
        } for c in companies
    ]

    # Passports
    client_data["passport_data"] = list(
        ClientPassport.objects.filter(ref_client_id=client).values()
    )

    # Frequent Flyer
    flyers = (
        ClientFrequentFlyer.objects
        .select_related('ref_airline')
        .only('id', 'ff_no', 'ref_airline__id', 'ref_airline__airline')
        .filter(ref_client_id=client)
    )
    client_data["flyer_data"] = [
        {
            "id": f.id,
            "ff_no": f.ff_no,
            "airline": {
                "id": f.ref_airline.id,
                "name": f.ref_airline.airline
            } if f.ref_airline else None
        } for f in flyers
    ]

    # # Family members (linked clients)
    # client_data["family_data"] = list(
    #     Client.objects.filter(ref_client_id=client.client_id).values()
    # )
    # # client_data["family_data"] = list(
    # #     Client.objects.filter(ref_client_id=client.client_id).select_related(
    # #         'country_code', 'residential_country', 'residential_state', 'residential_city', 'ref_preferred_airline','client_passport'
    # #     ).prefetch_related(
    # #         'client_passport', 'client_visa', 'travel_insurance', 'client_companies', 'client_documents',
    # #         'client_frequent_flyers'
    # #     ).values()
    # # )
    
    # print("here we are printing client id", client_data["family_data"][0]['client_id'])
    
    # client_data["family_passport_data"] = list(
    #     ClientPassport.objects.filter(ref_client_id=10356).values()
    # )
    
    # Fetch family members for the given client
    client_data["family_data"] = list(
        Client.objects.filter(ref_client_id=client.client_id).values()
    )

    # Fetch passport data for all family members
    family_member_ids = [member['client_id'] for member in client_data["family_data"]]
    client_data["family_passport_data"] = list(
        ClientPassport.objects.filter(ref_client_id__in=family_member_ids).values()
    )

    # Combine family data with their respective passport data
    for family_member in client_data["family_data"]:
        family_member_id = family_member['client_id']
        # Filter passports for the current family member
        family_member['passport_data'] = [
            passport for passport in client_data["family_passport_data"]
            if passport['ref_client_id'] == family_member_id
        ]

    # Remove separate family_passport_data since it's now nested
    del client_data["family_passport_data"]

    # Holding Visa
    client_data["holding_visa"] = list(
        ClientVisa.objects.filter(ref_client_id=client).values()
    )

    # Travel Insurance
    client_data["travel_insurance"] = list(
        ClientTravelInsurance.objects.filter(ref_client_id=client).values()
    )

    # Other Documents
    client_data["clients_documents"] = list(
        ClientDocument.objects.filter(ref_client_id=client).values()
    )

    return Response({
        'success': True,
        'client_data': client_data,
    }, status=status.HTTP_200_OK)

@api_view(["GET"])
@permission_classes([AllowAny])
def airlines_list(request):
    try:
        airlines = Airlines.objects.all().only('id', 'airline', 'iata').order_by('airline')
        serializer = AirlinesSerializer(airlines, many=True)
        return Response({
            "success": True,
            "data": serializer.data
        }, status=status.HTTP_200_OK)
    except Exception as e:
        return Response({
            "success": False,
            "errors": str(e)
        }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


'''
def unwrap_single_value_lists(obj, skip_keys=None):
    """
    Recursively unwrap single-element lists for normal fields,
    but keep lists for skip_keys (nested many=True serializers).
    """
    skip_keys = skip_keys or []

    if isinstance(obj, dict):
        new_dict = {}
        for k, v in obj.items():
            if k in skip_keys:
                new_dict[k] = keep_lists(v)  # force list normalization
            else:
                new_dict[k] = unwrap_single_value_lists(v, skip_keys)
        return new_dict

    elif isinstance(obj, list):
        # Only unwrap if it's a single primitive value (not dict/list)
        if len(obj) == 1 and not isinstance(obj[0], (dict, list)):
            return unwrap_single_value_lists(obj[0], skip_keys)
        else:
            return [unwrap_single_value_lists(v, skip_keys) for v in obj]

    else:
        # Convert empty string to None
        return None if obj == "" else obj

def keep_lists(obj):
    """Force values to always remain as lists (for nested many=True fields)."""
    if isinstance(obj, dict):
        return [obj]  # wrap single dict in a list
    elif isinstance(obj, list):
        return [keep_lists(v) if isinstance(v, dict) else v for v in obj]
    else:
        return [obj]
'''



from django.http import QueryDict
import re

# def convert_numeric_dicts_to_lists(obj):
#     if isinstance(obj, dict):
#         if all(k.isdigit() for k in obj.keys()):
#             return [convert_numeric_dicts_to_lists(obj[k]) for k in sorted(obj.keys(), key=int)]
#         else:
#             return {k: convert_numeric_dicts_to_lists(v) for k, v in obj.items()}
#     elif isinstance(obj, list):
#         return [convert_numeric_dicts_to_lists(v) for v in obj]
#     else:
#         return obj

# def parse_nested_formdata(data):
#     result = {}
#     for key, value in data.items():
#         parts = key.replace("]", "").split("[")
#         d = result
#         for p in parts[:-1]:
#             if p not in d:
#                 d[p] = {}
#             d = d[p]
#         d[parts[-1]] = value
#     return convert_numeric_dicts_to_lists(result)


# def normalize_formdata(obj, skip_keys=None):
#     """
#     - Convert empty strings to None
#     - Flatten single-element lists into scalars
#     - Keep lists for skip_keys (nested many=True fields)
#     """
#     skip_keys = skip_keys or []

#     if isinstance(obj, dict):
#         new_dict = {}
#         for k, v in obj.items():
#             if k in skip_keys:
#                 if isinstance(v, dict):
#                     new_dict[k] = [normalize_formdata(v, skip_keys)]
#                 elif isinstance(v, list):
#                     new_dict[k] = [normalize_formdata(x, skip_keys) for x in v]
#                 else:
#                     new_dict[k] = [v]
#             else:
#                 new_dict[k] = normalize_formdata(v, skip_keys)
#         return new_dict

#     elif isinstance(obj, list):
#         if len(obj) == 1 and not isinstance(obj[0], (dict, list)):
#             return normalize_formdata(obj[0], skip_keys)
#         else:
#             return [normalize_formdata(x, skip_keys) for x in obj]

#     else:
#         return None if obj == "" else obj

# def normalize_formdata(obj, skip_keys=None):
#     """
#     Normalize form data:
#     - Convert empty strings and "null" to None
#     - Flatten single-element lists into scalars for non-nested fields
#     - Preserve lists for skip_keys (nested many=True fields)
#     - Preserve None values explicitly
#     """
#     skip_keys = skip_keys or []

#     if isinstance(obj, dict):
#         new_dict = {}
#         for k, v in obj.items():
#             if k in skip_keys:
#                 # Handle nested fields (e.g., client_passport, family_members)
#                 if isinstance(v, dict):
#                     new_dict[k] = [normalize_formdata(v, skip_keys)]
#                 elif isinstance(v, list):
#                     new_dict[k] = [normalize_formdata(x, skip_keys) for x in v if x is not None]
#                 elif v is None:
#                     new_dict[k] = []
#                 else:
#                     new_dict[k] = [normalize_formdata(v, skip_keys)]
#             else:
#                 new_dict[k] = normalize_formdata(v, skip_keys)
#         return new_dict

#     elif isinstance(obj, list):
#         if len(obj) == 1 and not isinstance(obj[0], (dict, list)):
#             # Flatten single-element lists for scalar fields
#             return normalize_formdata(obj[0], skip_keys)
#         else:
#             # Preserve lists for nested fields
#             return [normalize_formdata(x, skip_keys) for x in obj if x is not None]

#     else:
#         # Convert empty strings and "null" to None, preserve other values
#         if obj in ["", "null", None]:
#             return None
#         return obj

# def normalize_formdata(obj, skip_keys=None):
#     """
#     Normalize form data:
#     - Convert empty strings and "null" to None
#     - Flatten single-element lists into scalars for non-nested fields
#     - Preserve lists for skip_keys (nested many=True fields)
#     - Preserve None values explicitly
#     """
#     skip_keys = skip_keys or []

#     if isinstance(obj, dict):
#         new_dict = {}
#         for k, v in obj.items():
#             if k in skip_keys:
#                 if isinstance(v, dict):
#                     new_dict[k] = [normalize_formdata(v, skip_keys)]
#                 elif isinstance(v, list):
#                     new_dict[k] = [normalize_formdata(x, skip_keys) for x in v if x is not None]
#                 elif v is None:
#                     new_dict[k] = []
#                 else:
#                     new_dict[k] = [normalize_formdata(v, skip_keys)]
#             else:
#                 new_dict[k] = normalize_formdata(v, skip_keys)
#         return new_dict

#     elif isinstance(obj, list):
#         if len(obj) == 1 and not isinstance(obj[0], (dict, list)):
#             return normalize_formdata(obj[0], skip_keys)
#         else:
#             return [normalize_formdata(x, skip_keys) for x in obj if x is not None]

#     else:
#         if obj in ["", "null", None]:
#             return None
#         return obj

def normalize_formdata(obj, skip_keys=None):
    """
    Normalize form data:
    - Convert empty strings and "null" to None
    - Flatten single-element lists into scalars for non-nested fields
    - Preserve lists for skip_keys (nested many=True fields)
    - Preserve None values explicitly
    """
    skip_keys = skip_keys or []

    if isinstance(obj, dict):
        new_dict = {}
        for k, v in obj.items():
            if k in skip_keys:
                if isinstance(v, dict):
                    new_dict[k] = [normalize_formdata(v, skip_keys)]
                elif isinstance(v, list):
                    new_dict[k] = [normalize_formdata(x, skip_keys) for x in v if x is not None]
                elif v is None:
                    new_dict[k] = []
                else:
                    new_dict[k] = [normalize_formdata(v, skip_keys)]
            else:
                new_dict[k] = normalize_formdata(v, skip_keys)
        return new_dict

    elif isinstance(obj, list):
        if len(obj) == 1 and not isinstance(obj[0], (dict, list)):
            return normalize_formdata(obj[0], skip_keys)
        else:
            return [normalize_formdata(x, skip_keys) for x in obj if x is not None]

    else:
        if obj in ["", "null", None]:
            return None
        return obj
    
# def normalize_formdata(obj, skip_keys=None):
#     """
#     Normalize form-data:
#     - Scalars: Convert "", "null", None to None; flatten single-element lists
#     - Nested (skip_keys): Preserve as lists, clean internals
#     """
#     skip_keys = skip_keys or []
#     if isinstance(obj, dict):
#         return {
#             k: [normalize_formdata(x, skip_keys) for x in v] if k in skip_keys and isinstance(v, list)
#             else [normalize_formdata(v, skip_keys)] if k in skip_keys and isinstance(v, dict)
#             else [] if k in skip_keys and v in [None, "", "null"]
#             else normalize_formdata(v, skip_keys)
#             for k, v in obj.items()
#         }
#     elif isinstance(obj, list):
#         return [normalize_formdata(x, skip_keys) for x in obj] if len(obj) > 1 else normalize_formdata(obj[0], skip_keys) if obj else None
#     return None if obj in ["", "null", None, [""]] else obj

# def normalize_formdata(obj, skip_keys=None, file_fields=None, request=None):
#     """
#     Normalize form-data:
#     - Scalars: Convert "", "null", None to None; flatten single-element lists
#     - Nested (skip_keys): Preserve as lists, clean internals
#     - File fields (file_fields): Only include if in request.FILES or explicitly null
#     """
#     skip_keys = skip_keys or []
#     file_fields = file_fields or [
#         "aadhaar_card_file", "pan_card_file", "passport_file", "other_document",
#         "passport_size_photograph", "insurance_document", "aadhaarCard", "panCard"
#     ]
#     if isinstance(obj, dict):
#         result = {}
#         request_files = request.FILES if request else {}
#         request_data = request.data if request else {}
#         for k, v in obj.items():
#             if k in skip_keys and isinstance(v, list):
#                 result[k] = [normalize_formdata(x, skip_keys, file_fields, request) for x in v]
#             elif k in skip_keys and isinstance(v, dict):
#                 result[k] = [normalize_formdata(v, skip_keys, file_fields, request)]
#             elif k in skip_keys and v in [None, "", "null"]:
#                 result[k] = []
#             elif k in file_fields:
#                 # Only include if in request.FILES or explicitly null in request.data
#                 if k in request_files:
#                     result[k] = request_files[k]
#                 elif k in request_data and request_data[k] in ["", "null", None]:
#                     result[k] = None
#                 # Else, exclude to preserve existing file
#             else:
#                 result[k] = normalize_formdata(v, skip_keys, file_fields, request)
#         return result
#     elif isinstance(obj, list):
#         return [normalize_formdata(x, skip_keys, file_fields, request) for x in obj] if len(obj) > 1 else normalize_formdata(obj[0], skip_keys, file_fields, request) if obj else None
#     return None if obj in ["", "null", None, [""]] else obj

def deep_clean(obj):
    """
    Recursively remove keys with None, "", [], {} values.
    """
    if isinstance(obj, dict):
        return {
            k: deep_clean(v)
            for k, v in obj.items()
            if v not in [None, "", [], {}]
        }
    elif isinstance(obj, list):
        return [deep_clean(x) for x in obj if x not in [None, "", [], {}]]
    else:
        return obj

# def parse_nested_formdata(data):
#     """Convert flat form data into nested dictionary structure."""
#     result = {}
#     for key, value in data.items():
#         parts = key.replace("]", "").split("[")
#         d = result
#         for p in parts[:-1]:
#             d = d.setdefault(p, {})
#         d[parts[-1]] = value
#     return convert_numeric_dicts_to_lists(result)

# def parse_nested_formdata(data):
#     """Convert flat form data into nested dictionary structure."""
#     result = {}
#     for key, value in data.items():
#         parts = key.replace("]", "").split("[")
#         d = result
#         for p in parts[:-1]:
#             d = d.setdefault(p, {})
#         # Preserve None values explicitly
#         d[parts[-1]] = value if value is not None else None
#     return convert_numeric_dicts_to_lists(result)

# def parse_nested_formdata(data):
#     """
#     Convert flat form data into nested dictionary structure.
#     """
#     result = {}
#     for key, value in data.items():
#         parts = key.replace("]", "").split("[")
#         d = result
#         for p in parts[:-1]:
#             d = d.setdefault(p, {})
#         d[parts[-1]] = value if value is not None else None
#     return convert_numeric_dicts_to_lists(result)

def parse_nested_formdata(data):
    """
    Convert flat form-data keys into nested dictionary structure.
    """
    result = {}
    for key, value in data.items():
        parts = key.replace("]", "").split("[")
        current = result
        for part in parts[:-1]:
            current = current.setdefault(part, {})
        current[parts[-1]] = None if value in ["", "null", None] else value
    return convert_numeric_dicts_to_lists(result)

# def convert_numeric_dicts_to_lists(data):
#     """Convert numeric-keyed dictionaries to lists."""
#     if isinstance(data, dict):
#         if all(k.isdigit() for k in data.keys()):
#             return [convert_numeric_dicts_to_lists(data[str(i)]) for i in range(len(data))]
#         return {k: convert_numeric_dicts_to_lists(v) for k, v in data.items()}
#     elif isinstance(data, list):
#         return [convert_numeric_dicts_to_lists(item) for item in data]
#     return data

# def convert_numeric_dicts_to_lists(data):
#     if isinstance(data, dict):
#         if all(k.isdigit() for k in data.keys()):
#             return [convert_numeric_dicts_to_lists(v) for v in data.values() if v is not None]
#         return {k: convert_numeric_dicts_to_lists(v) for k, v in data.items()}
#     elif isinstance(data, list):
#         return [convert_numeric_dicts_to_lists(v) for v in data if v is not None]
#     return data

def convert_numeric_dicts_to_lists(data):
    """
    Convert numerically indexed dictionaries to lists for proper nested data handling.
    """
    if isinstance(data, dict):
        if all(k.isdigit() for k in data.keys()):
            return [convert_numeric_dicts_to_lists(data[str(i)]) for i in sorted(map(int, data.keys()))]
        return {k: convert_numeric_dicts_to_lists(v) for k, v in data.items()}
    elif isinstance(data, list):
        return [convert_numeric_dicts_to_lists(item) for item in data]
    return data

# def normalize_formdata(obj, skip_keys=None):
#     """Normalize form data: convert empty strings to None, flatten single-item lists."""
#     skip_keys = skip_keys or []
#     if isinstance(obj, dict):
#         return {
#             k: normalize_formdata(v, skip_keys) if k not in skip_keys else v
#             for k, v in obj.items()
#             if v not in [None, "", [], {}, "[]", "null"]
#         }
#     elif isinstance(obj, list):
#         return [normalize_formdata(x, skip_keys) for x in obj if x not in [None, "", [], {}, "[]", "null"]]
#     elif isinstance(obj, str) and obj.strip() in ["", "[]", "null"]:
#         return None
#     elif isinstance(obj, list) and len(obj) == 1 and not isinstance(obj[0], (dict, list)):
#         return normalize_formdata(obj[0], skip_keys)
#     return obj
    
SKIP_KEYS = [
    "client_passport",
    "client_visa",
    "travel_insurance",
    "client_companies",
    "client_documents",
    "client_frequent_flyers",
    "family_members",
    "fam_passports",
    "relationsWithOthers",
]


# @api_view(["POST"])
# @permission_classes([IsAuthenticated])
# @parser_classes([MultiPartParser, FormParser])
# def client_create(request):
#     # Parse + normalize + clean
#     normalized_data = parse_nested_formdata({**request.data, **request.FILES})
#     normalized_data = normalize_formdata(normalized_data, skip_keys=SKIP_KEYS)
#     normalized_data = deep_clean(normalized_data)


#     serializer = FullClientSerializer(
#         data=normalized_data,
#         context={"request": request}
#     )
#     if serializer.is_valid():
#         client = serializer.save(created_by=request.user)
#         client.refresh_from_db()
#         return Response({
#             "success": True,
#             "data": FullClientSerializer(client, context={"request": request}).data
#         }, status=status.HTTP_201_CREATED)

#     return Response({
#         "success": False,
#         "errors": serializer.errors
#     }, status=status.HTTP_400_BAD_REQUEST)

# @api_view(["POST"])
# @permission_classes([IsAuthenticated])
# @parser_classes([MultiPartParser, FormParser])
# def client_create(request):
#     # Parse + normalize + clean
#     # print(f"Raw request data: {request.data}, files: {request.FILES}")
#     normalized_data = parse_nested_formdata({**request.data, **request.FILES})
#     # print(f"After parse_nested_formdata: {normalized_data}")
#     normalized_data = normalize_formdata(normalized_data, skip_keys=SKIP_KEYS)
#     # print(f"After normalize_formdata: {normalized_data}")
#     normalized_data = deep_clean(normalized_data)
#     # print(f"After deep_clean: {normalized_data}")

#     serializer = FullClientSerializer(
#         data=normalized_data,
#         context={"request": request}
#     )
#     if serializer.is_valid():
#         # print(f"Serializer validated data: {serializer.validated_data}")
#         client = serializer.save(created_by=request.user, updated_by=request.user)
#         # print(f"Client created: pk={client.pk}, data={FullClientSerializer(client, context={'request': request}).data}")
        
#         return Response({
#             "success": True,
#             "data": FullClientSerializer(client, context={"request": request}).data
#         }, status=status.HTTP_201_CREATED)

#     # print(f"Serializer errors: {serializer.errors}")
#     return Response({
#         "success": False,
#         "errors": serializer.errors
#     }, status=status.HTTP_400_BAD_REQUEST)

from users.utils import serialize_client_with_related

def build_changes_client(old_data, new_data):
    changes = {}
    # Convert model instances to dictionaries
    if not isinstance(new_data, dict):
        new_data = model_to_dict(new_data, fields=[field.name for field in new_data._meta.fields])
    if old_data and not isinstance(old_data, dict):
        old_data = model_to_dict(old_data, fields=[field.name for field in old_data._meta.fields])
    
    # Handle FieldFile objects
    for key in new_data:
        if isinstance(new_data[key], models.fields.files.FieldFile):
            new_data[key] = new_data[key].url if new_data[key] else None
    if old_data:
        for key in old_data:
            if isinstance(old_data[key], models.fields.files.FieldFile):
                old_data[key] = old_data[key].url if old_data[key] else None
    
    all_keys = set(old_data.keys() if old_data else []).union(new_data.keys())
    for key in all_keys:
        old_value = old_data.get(key) if old_data else None
        new_value = new_data.get(key) if new_data else None
        if old_value != new_value:
            changes[key] = {"old": old_value, "new": new_value}
    return changes

@api_view(["POST"])
@permission_classes([IsAuthenticated])
@parser_classes([MultiPartParser, FormParser])
def client_create(request):
    # Parse + normalize + clean
    normalized_data = parse_nested_formdata({**request.data, **request.FILES})
    normalized_data = normalize_formdata(normalized_data, skip_keys=SKIP_KEYS)
    normalized_data = deep_clean(normalized_data)

    serializer = FullClientSerializer(
        data=normalized_data,
        context={"request": request}
    )
    if serializer.is_valid():
        client = serializer.save(created_by=request.user, updated_by=request.user)
        ClientLog.objects.create(
            ref_client_id=client,
            ref_table_name=client._meta.db_table,
            ref_id=client.pk,
            action_type="CLIENT CREATED",
            changed_data=json.loads(json.dumps(
                build_changes(None,serialize_client_with_related(client)),
                cls=DjangoJSONEncoder
            )),
            performed_by=client.created_by,
            performed_at=timezone.now(),
        )
        return Response({
            "success": True,
            "data": FullClientSerializer(client, context={"request": request}).data
        }, status=status.HTTP_201_CREATED)

    return Response({
        "success": False,
        "errors": serializer.errors
    }, status=status.HTTP_400_BAD_REQUEST)
    
'''
@api_view(["GET", "PUT"])
def client_detail_update(request, pk):
    try:
        client = Client.objects.get(pk=pk)
    except Client.DoesNotExist:
        return Response({"success": False, "error": "Client not found"}, status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        serializer = FullClientSerializer(client)
        return Response({"success": True, "data": serializer.data})

    elif request.method == "PUT":
        normalized_data = parse_nested_formdata(request.data)
        serializer = FullClientSerializer(client, data=normalized_data, partial=True)
        if serializer.is_valid():
            client = serializer.save(
                updated_by=request.user
            )
            response_serializer = FullClientSerializer(client)
            return Response({
                "success": True,
                "data": response_serializer.data
            })
        else:
            return Response({
                "success": False,
                "errors": serializer.errors
            }, status=status.HTTP_400_BAD_REQUEST)
'''
    
'''
def parse_form_to_nested(data: QueryDict):
    nested = {}

    for key, value in data.items():
        # Extract all parts inside [] and the prefix
        # parts = re.findall(r'\[([^\]]*)\]', key)
        prefix = key.split("[", 1)[0]

        # Start building the nested dict
        current = nested
        if prefix not in current:
            # if numeric → list, else dict
            current[prefix] = [] if (parts and parts[0].isdigit()) else {}
        current = current[prefix]

        for i, part in enumerate(parts):
            is_last = i == len(parts) - 1

            if part.isdigit():  # list index
                idx = int(part)
                while len(current) <= idx:
                    current.append({})
                if is_last:
                    current[idx] = value
                else:
                    if not isinstance(current[idx], dict):
                        current[idx] = {}
                    current = current[idx]
            else:  # dictionary key
                if is_last:
                    current[part] = value
                else:
                    if part not in current:
                        # check next part: if digit → list, else dict
                        if i + 1 < len(parts) and parts[i+1].isdigit():
                            current[part] = []
                        else:
                            current[part] = {}
                    current = current[part]

    return nested

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def client_create(request):
    print(request.data)
    serializer = ClientCreateSerializer(data=request.data, context={"request": request})
    if serializer.is_valid():
        with transaction.atomic():
            client = serializer.save()
            # log create
            ClientLog.objects.create(
                ref_client_id=client,
                ref_table_name="Client",
                ref_id=client.id,
                action_type="CREATE",
                changed_data=serialize_instance(client),
                performed_by=request.user,
                performed_at=timezone.now(),
            )
        return Response({"success": True, "clientId": client.client_id}, status=status.HTTP_201_CREATED)
    return Response({"success": False, "errors": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
'''

'''
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def client_detail(request, client_id):
    """
    Fetch single client with nested relations.
    Works for both Primary and Family Members.
    """
    try:
        client = Client.objects.get(pk=client_id)
    except Client.DoesNotExist:
        return Response({
            "success": False,
            "message": "Client not found"
        }, status=status.HTTP_404_NOT_FOUND)

    serializer = FullClientSerializer(client, context={"request": request})
    return Response({
        "success": True,
        "data": serializer.data
    }, status=status.HTTP_200_OK)
'''


# @api_view(["GET"])
# @permission_classes([IsAuthenticated])
# def client_detail(request, client_id):
#     """
#     Fetch single client with related passports, visas, travel insurance,
#     companies, documents, frequent flyers, and family member data,
#     including file URLs.
#     """
#     try:
#         client = Client.objects.get(pk=client_id)
#     except Client.DoesNotExist:
#         return Response({
#             "success": False,
#             "message": "Client not found"
#         }, status=status.HTTP_404_NOT_FOUND)

#     # Basic client info with file URLs
#     client_data = {
#         "client_id": client.client_id,
#         "client_code": client.client_code,
#         "client_salutation": client.client_salutation,
#         "client_first_name": client.client_first_name,
#         "client_last_name": client.client_last_name,
#         "client_middle_name": client.client_middle_name,
#         "is_prepayment": client.is_prepayment,
#         "country_code": {
#             "countrycode": client.country_code.country_code,
#             "name": client.country_code.name
#         } if client.country_code else None,
#         "contact_no": client.contact_no,
#         "email": client.email,
#         "dob": client.dob,
#         "gender": client.gender,
#         "ResidentialAddress": client.residential_address,
#         "occupation": client.occupation,
#         "anniversary_date": client.anniversary_date,
#         "reference_from": client.reference_from,
#         "reference_id": client.reference_id,
#         "reference_remark": client.reference_remark,
#         "marital_status": client.marital_status,
#         "preferred_contact_method": client.get_preferred_contact_method_display().split(", ") if client.preferred_contact_method else [],
#         # "preferred_contact_method": (
#         #     client.get_preferred_contact_method_display().split(", ")
#         #     if client.preferred_contact_method else []
#         # ),
#         "aadhaar_no": client.aadhaar_no,
#         "aadhaar_card_file": client.aadhaar_card_file.url if client.aadhaar_card_file else None,
#         # "aadhaar_card_file": request.build_absolute_uri(client.aadhaar_card_file.url) if client.aadhaar_card_file else None,
#         "pan_no": client.pan_no,
#         "pan_card_file": client.pan_card_file.url if client.pan_card_file else None,
#         # "pan_card_file": request.build_absolute_uri(client.pan_card_file.url) if client.pan_card_file else None,
#         "ref_preferred_airline": {
#             "id": client.ref_preferred_airline.id,
#             "name": client.ref_preferred_airline.airline
#         } if client.ref_preferred_airline else None,

#         "residential_address": client.residential_address,
#         "residential_country": {
#             "id": client.residential_country.id,
#             "name": client.residential_country.name
#         } if client.residential_country else None,

#         "residential_state": {
#             "id": client.residential_state.id,
#             "name": client.residential_state.name
#         } if client.residential_state else None,

#         "residential_city": {
#             "id": client.residential_city.id,
#             "name": client.residential_city.name
#         } if client.residential_city else None,

#         "residential_pincode": client.residential_pincode,

#         "ref_preferred_airline": {
#             "id": client.ref_preferred_airline.id,
#             "name": client.ref_preferred_airline.airline 
#         } if client.ref_preferred_airline else None,


#         "star_rating": client.star_rating,
#         "seat_preference": client.seat_preference,
#         "seat_preference_other": client.seat_preference_other,
#         "stay_preference": client.stay_preference,
#         "room_preference": client.room_preference,
#         "extra_amenities": client.extra_amenities,
#         "meal_preference": client.meal_preference,
#         "fare_preference": client.fare_preference,
#     }

#     # Related models with file URLs
#     client_data["client_passport"] = [
#         {
#             "id": p.id,
#             "ref_client_id": p.ref_client.client_id,
#             "passport_no": p.passport_no,
#             "passport_file": p.passport_file.url if p.passport_file else None,
#             "passport_expiry_date": p.passport_expiry_date,
#         }
#         for p in ClientPassport.objects.filter(ref_client_id=client_id)
#     ]

#     client_data["client_visa"] = [
#         {
#             "id": v.id,
#             "ref_client_id": v.ref_client.client_id,
#             "ref_visa_country": v.ref_visa_country.id if v.ref_visa_country else None,
#             "visa_type": v.visa_type,
#             "passport_size_photograph": v.passport_size_photograph.url if v.passport_size_photograph else None,
#             "visa_from_date": v.visa_from_date,
#             "visa_to_date": v.visa_to_date,
#         }
#         for v in ClientVisa.objects.filter(ref_client_id=client_id)
#     ]

#     client_data["travel_insurance"] = [
#         {
#             "id": t.id,
#             "ref_client_id": t.ref_client.client_id,
#             "insurance_document": t.insurance_document.url if t.insurance_document else None,
#             "insurance_from_date": t.insurance_from_date,
#             "insurance_to_date": t.insurance_to_date,
#         }
#         for t in ClientTravelInsurance.objects.filter(ref_client_id=client_id)
#     ]

#     client_data["client_documents"] = [
#         {
#             "id": cd.id,
#             "ref_client_id": cd.ref_client.client_id,
#             "other_document_name": cd.other_document_name,
#             "other_document": cd.other_document.url if cd.other_document else None,
#         }
#         for cd in ClientDocument.objects.filter(ref_client_id=client_id)
#     ]

#     client_data["client_frequent_flyers"] = [
#         {
#             "id": ff.id,
#             "ref_client_id": ff.ref_client.client_id,
#             "ref_airline": ff.ref_airline.id if ff.ref_airline else None,
#             "ff_no": ff.ff_no,
#         }
#         for ff in ClientFrequentFlyer.objects.filter(ref_client_id=client_id)
#     ]

#     client_data["client_companies"] = [
#         {
#             "id": a.id,
#             "ref_client_id": a.ref_client.client_id,
#             "ref_company": {
#                 "id": a.ref_company.id,
#                 "company_name": a.ref_company.company_name,
#             } if a.ref_company else None,
#             "designation": a.designation,
#             "primary_company": a.primary_company,
#         }
#         for a in AssocClientCompany.objects.filter(ref_client_id=client_id)
#     ]

#     # Family members with their file URLs
#     family_members = Client.objects.filter(ref_client=client)
#     client_data["family_members"] = []
#     for fam in family_members:
#         fam_data = {
#             "id": fam.client_id,
#             "name": f"{fam.client_first_name} {fam.client_last_name}",
#             "aadhaar_no": fam.aadhaar_no,
#             "aadhaar_card_file": request.build_absolute_uri(fam.aadhaar_card_file.url) if fam.aadhaar_card_file else None,
#             "pan_no": fam.pan_no,
#             "pan_card_file": request.build_absolute_uri(fam.pan_card_file.url) if fam.pan_card_file else None,
#             "client_first_name": fam.client_first_name,
#             "client_last_name": fam.client_last_name,
#             "client_middle_name": fam.client_middle_name,
#             "email": fam.email,
#             "dob": fam.dob,
#             "gender": fam.gender,
#             "contact_no": fam.contact_no,
#         }
#         client_data["family_members"].append(fam_data)

#     return Response({
#         "success": True,
#         "data": client_data
#     }, status=status.HTTP_200_OK)

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def client_detail(request, client_id):
    """
    Fetch single client with related passports, visas, travel insurance,
    companies, documents, frequent flyers, and family member data,
    including file URLs. Optimized with select_related and prefetch_related.
    """
    try:
        # Fetch client with related foreign key objects
        client = Client.objects.select_related(
            'residential_city', 'country_code', 'residential_state', 'residential_country',
            'ref_preferred_airline', 'created_by', 'updated_by'
        ).get(pk=client_id)
    except Client.DoesNotExist:
        return Response({
            "success": False,
            "message": "Client not found"
        }, status=status.HTTP_404_NOT_FOUND)

    def get_absolute_url(file_field):
        """Helper to return absolute URL for a file field, or None if empty."""
        return request.build_absolute_uri(file_field.url) if file_field else None

    def get_foreign_key_data(obj, fields):
        """Helper to extract foreign key data safely."""
        if not obj:
            return None
        return {field: getattr(obj, field, None) for field in fields}

    # Basic client info with file URLs and foreign key validations
    client_data = {
        "client_id": client.client_id,
        "client_type": client.client_type,
        "client_code": client.client_code,
        "client_salutation": client.client_salutation,
        "client_first_name": client.client_first_name,
        "client_last_name": client.client_last_name,
        "client_middle_name": client.client_middle_name,
        "is_prepayment": client.is_prepayment,
        "country_code": get_foreign_key_data(client.country_code, ['id','country_code', 'name']),
        "contact_no": client.contact_no,
        "email": client.email,
        "dob": client.dob,
        "gender": client.gender,
        "residential_address": client.residential_address,
        "occupation": client.occupation,
        "anniversary_date": client.anniversary_date,
        "reference_from": client.reference_from,
        "reference_id": client.reference_id,
        "reference_remark": client.reference_remark,
        "marital_status": client.marital_status,
        "preferred_contact_method": (
            client.get_preferred_contact_method_display().split(", ")
            if client.preferred_contact_method else []
        ),
        "aadhaar_no": client.aadhaar_no,
        "aadhaar_card_file": get_absolute_url(client.aadhaar_card_file),
        "pan_no": client.pan_no,
        "pan_card_file": get_absolute_url(client.pan_card_file),
        "ref_preferred_airline": get_foreign_key_data(client.ref_preferred_airline, ['id', 'airline']),
        "residential_country": get_foreign_key_data(client.residential_country, ['id', 'name']),
        "residential_state": get_foreign_key_data(client.residential_state, ['id', 'name']),
        "residential_city": get_foreign_key_data(client.residential_city, ['id', 'name']),
        "residential_pincode": client.residential_pincode,
        "star_rating": client.star_rating,
        "seat_preference": client.seat_preference,
        "seat_preference_other": client.seat_preference_other,
        "stay_preference": client.stay_preference,
        "room_preference": client.room_preference,
        "extra_amenities": client.extra_amenities,
        "meal_preference": client.meal_preference,
        "fare_preference": client.fare_preference,
    }

    # Related models with foreign key validations
    client_data["client_passport"] = [
        {
            "id": p.id,
            "ref_client_id": p.ref_client.client_id,
            "passport_no": p.passport_no,
            "passport_file": get_absolute_url(p.passport_file),
            "passport_expiry_date": p.passport_expiry_date,
        }
        for p in ClientPassport.objects.filter(ref_client_id=client_id)
    ]

    client_data["client_visa"] = [
        {
            "id": v.id,
            "ref_client_id": v.ref_client.client_id,
            "ref_visa_country": v.ref_visa_country.id if v.ref_visa_country else None,
            "visa_type": v.visa_type,
            "passport_size_photograph": get_absolute_url(v.passport_size_photograph),
            "visa_from_date": v.visa_from_date,
            "visa_to_date": v.visa_to_date,
        }
        for v in ClientVisa.objects.filter(ref_client_id=client_id)
    ]

    client_data["travel_insurance"] = [
        {
            "id": t.id,
            "ref_client_id": t.ref_client.client_id,
            "insurance_document": get_absolute_url(t.insurance_document),
            "insurance_from_date": t.insurance_from_date,
            "insurance_to_date": t.insurance_to_date,
        }
        for t in ClientTravelInsurance.objects.filter(ref_client_id=client_id)
    ]

    client_data["client_documents"] = [
        {
            "id": cd.id,
            "ref_client_id": cd.ref_client.client_id,
            "other_document_name": cd.other_document_name,
            "other_document": get_absolute_url(cd.other_document),
        }
        for cd in ClientDocument.objects.filter(ref_client_id=client_id)
    ]

    client_data["client_frequent_flyers"] = [
        {
            "id": ff.id,
            "ref_client_id": ff.ref_client.client_id,
            "ref_airline": ff.ref_airline.id if ff.ref_airline else None,
            "ff_no": ff.ff_no,
        }
        for ff in ClientFrequentFlyer.objects.filter(ref_client_id=client_id)
    ]

    client_data["client_companies"] = [
        {
            "id": a.id,
            "ref_client_id": a.ref_client.client_id,
            "ref_company": get_foreign_key_data(a.ref_company, ['id', 'company_name']),
            "designation": a.designation,
            "primary_company": a.primary_company,
        }
        for a in AssocClientCompany.objects.filter(ref_client_id=client_id)
    ]

    # Family members with file URLs
    client_data["family_members"] = [
        {
            "id": fam.client_id,
            "name": f"{fam.client_first_name} {fam.client_last_name}",
            "aadhaar_no": fam.aadhaar_no,
            "aadhaar_card_file": get_absolute_url(fam.aadhaar_card_file),
            "pan_no": fam.pan_no,
            "pan_card_file": get_absolute_url(fam.pan_card_file),
            "client_first_name": fam.client_first_name,
            "client_last_name": fam.client_last_name,
            "client_middle_name": fam.client_middle_name,
            "email": fam.email,
            "dob": fam.dob,
            "gender": fam.gender,
            "contact_no": fam.contact_no,
        }
        for fam in Client.objects.filter(ref_client=client)
    ]

    # Validate sensitive fields (e.g., Aadhaar and PAN numbers)
    for field in ['aadhaar_no', 'pan_no']:
        if client_data[field] and not isinstance(client_data[field], str):
            return Response({
                "success": False,
                "message": f"Invalid {field} format"
            }, status=status.HTTP_400_BAD_REQUEST)

    # Additional validation for family members' sensitive fields
    for fam in client_data["family_members"]:
        for field in ['aadhaar_no', 'pan_no']:
            if fam[field] and not isinstance(fam[field], str):
                return Response({
                    "success": False,
                    "message": f"Invalid {field} format for family member {fam['name']}"
                }, status=status.HTTP_400_BAD_REQUEST)

    return Response({
        "success": True,
        "data": client_data
    }, status=status.HTTP_200_OK)


'''
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def client_detail(request, client_id):
    """
    Fetch single client with nested relations.
    Includes passports, visas, companies, frequent flyers, documents, etc.
    """
    try:
        client = Client.objects.prefetch_related(
            "client_passport",
            "client_visa",
            "travel_insurance",
            "client_companies",
            "client_documents",
            "frequent_flyers",
            "ref_client__client_passport",   # nested family members passports
            "ref_client__client_visa",
        ).get(pk=client_id)
    except Client.DoesNotExist:
        return Response({
            "success": False,
            "message": "Client not found"
        }, status=status.HTTP_404_NOT_FOUND)

    serializer = FullClientSerializer(client, context={"request": request})
    print(serializer.data)
'''

'''
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def client_detail(request, client_id):
    """
    Fetch single client with all nested relations,
    in the same data format as the old client_view API.
    """
    try:
        client = Client.objects.prefetch_related(
            'client_passport',
            'client_visa',
            'travel_insurance',
            'client_documents',
            'frequent_flyers',
            'ref_client',  # family members
        ).get(pk=client_id)
    except Client.DoesNotExist:
        return Response({
            "success": False,
            "message": "Client not found"
        }, status=status.HTTP_404_NOT_FOUND)

    # Build client_data same as old client_view
    client_data = {
        "client_id": client.client_id,
        "client_name": f"{client.client_first_name} {client.client_last_name}".strip(),
        "client_code": client.client_code,
        "full_contact_no": f"{client.country_code} {client.contact_no}" if client.contact_no else "",
        "email": client.email,
        "dateOfBirth": client.dob,
        "gender": client.gender,
        "ResidentialAddress": client.residential_address,
        "occupation": client.occupation,
        "preferred_contact_method": client.get_preferred_contact_method_display().split(", ") if client.preferred_contact_method else [],
        "aadhaar_no": client.aadhaar_no,
        "aadhaar_card_file": client.aadhaar_card_file.url if client.aadhaar_card_file else None,
        "pan_no": client.pan_no,
        "pan_card_file": client.pan_card_file.url if client.pan_card_file else None,
        "ref_preferred_airline": {
            "id": client.ref_preferred_airline.id,
            "airline": getattr(client.ref_preferred_airline, 'airline', '')  # your actual field name
        } if client.ref_preferred_airline else None,
        "star_rating": client.star_rating,
        "seat_preference": client.seat_preference,
        "stay_preference": client.stay_preference,
        "room_preference": client.room_preference,
        "extra_amenities": client.extra_amenities,
        "meal_preference": client.meal_preference,
        "fare_preference": client.fare_preference,
    }

    # Nested related objects
    client_data["passport_data"] = [
        {
            **p.__dict__,
            'id': p.id
        } for p in client.client_passport.all()
    ]
    client_data["client_frequent_flyer"] = [
        {
            **f.__dict__,
            'id': f.id
        } for f in client.frequent_flyers.all()
    ]
    client_data["holding_visa"] = [
        {
            **v.__dict__,
            'id': v.id
        } for v in client.client_visa.all()
    ]
    client_data["travel_insurance"] = [
        {
            **t.__dict__,
            'id': t.id
        } for t in client.travel_insurance.all()
    ]
    client_data["clients_documents"] = [
        {
            **d.__dict__,
            'id': d.id
        } for d in client.client_documents.all()
    ]
    family_qs = client.family_members.all()  # use your related_name
    client_data["family_data"] = [
        {
            "client_id": f.client_id,
            "client_name": f"{f.client_first_name} {f.client_last_name}".strip(),
            "client_code": f.client_code,
            "full_contact_no": f"{f.country_code} {f.contact_no}" if f.contact_no else "",
            "email": f.email,
        }
        for f in family_qs
    ]

    return Response({
        "success": True,
        "client_data": client_data
    }, status=status.HTTP_200_OK)
'''


# @api_view(["PUT"])
# @permission_classes([IsAuthenticated])
# @parser_classes([MultiPartParser, FormParser])
# @transaction.atomic
# def client_update(request, client_id):
#     try:
#         client = Client.objects.get(pk=client_id)
#     except Client.DoesNotExist:
#         return Response({
#             "success": False,
#             "message": "Client not found"
#         }, status=status.HTTP_404_NOT_FOUND)

#     # Parse + normalize + clean
#     normalized_data = parse_nested_formdata({**request.data, **request.FILES})
#     normalized_data = normalize_formdata(normalized_data, skip_keys=SKIP_KEYS)
#     # normalized_data = deep_clean(normalized_data)

#     serializer = FullClientSerializer(
#         client,
#         data=normalized_data,
#         partial=True,
#         context={"request": request}
#     )
#     if serializer.is_valid():
#         client = serializer.save(updated_by=request.user)
#         client.refresh_from_db()
#         return Response({
#             "success": True,
#             "message": "Client updated successfully",
#             "data": FullClientSerializer(client, context={"request": request}).data
#         }, status=status.HTTP_200_OK)

#     return Response({
#         "success": False,
#         "errors": serializer.errors
#     }, status=status.HTTP_400_BAD_REQUEST)
    
from users.utils import serialize_client_with_related
    
# @api_view(["PUT"])
# @permission_classes([IsAuthenticated])
# @parser_classes([MultiPartParser, FormParser])
# @transaction.atomic
# def client_update(request, client_id):
#     """
#     Update a Client instance with nested relationships and file uploads.

#     Expected Input (multipart/form-data or JSON):
#     - client_first_name, client_code, etc. (scalars, not lists)
#     - Nested fields: client_passport, client_visa, family_members, etc. (list of dicts)
#     - Files: aadhaar_card_file, pan_card_file, family_members[0][aadhaarCard], etc.
#     - Choice fields (e.g., gender, marital_status) should match model choices (e.g., "Male", "Married")

#     Response:
#     - 200: Success with updated client data
#     - 400: Validation errors with detailed messages
#     - 401: Unauthorized if user is not authenticated
#     - 404: Client not found
#     - 500: Server error for invalid configurations
#     """
#     if not request.user.is_authenticated:
#         return Response({
#             "success": False,
#             "message": "User is not authenticated"
#         }, status=status.HTTP_401_UNAUTHORIZED)

#     try:
#         client = Client.objects.select_related(
#             'country_code', 'residential_country', 'residential_state', 'residential_city', 'ref_preferred_airline'
#         ).get(pk=client_id)
#     except Client.DoesNotExist:
#         return Response({
#             "success": False,
#             "message": f"Client with ID {client_id} not found"
#         }, status=status.HTTP_404_NOT_FOUND)
#     except ValueError as e:
#         return Response({
#             "success": False,
#             "message": f"Invalid prefetch_related configuration: {str(e)}"
#         }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
    
   

#     client._old_data = getattr(client, "_old_data", None)
    
#     if client._old_data:
#         print(f"Old data found for client {client.pk}: {client._old_data}")
#         old_data_filtered = {
#             k: v for k, v in client._old_data.items()
#             # if k not in ["client_status", "created_at", "updated_at", "updated_by"]
#         }
#     client._old_status = client.client_status
    
#     new_data_filtered = serialize_client_with_related(client)
#     new_data_filtered = {
#                 k: v for k, v in new_data_filtered.items()
#                 # if k not in ["client_status", "created_at", "updated_at", "updated_by"]
#             }

#     # Parse and normalize data
#     normalized_data = parse_nested_formdata({**request.data, **request.FILES})
#     normalized_data = normalize_formdata(normalized_data, skip_keys=[
#         "client_passport", "client_visa", "travel_insurance", "client_companies",
#         "client_documents", "client_frequent_flyers", "family_members", "fam_passport",
#         "relationsWithOthers"
#     ])
#     # normalized_data = deep_clean(normalized_data)


#     # client._old_data = serialize_client_with_related(client)
#     # client._old_status = client.client_status

#     serializer = FullClientSerializer(
#         client,
#         data=normalized_data,
#         partial=True,
#         context={"request": request}
#     )
#     if serializer.is_valid():
#         updated_client = serializer.save()
#         return Response({
#             "success": True,
#             "message": "Client updated successfully",
#             "data": FullClientSerializer(updated_client, context={"request": request}).data
#         }, status=status.HTTP_200_OK)

#     return Response({
#         "success": False,
#         "message": "Validation failed",
#         "errors": serializer.errors
#     }, status=status.HTTP_400_BAD_REQUEST)
    

# @api_view(["PUT"])
# @permission_classes([IsAuthenticated])
# @parser_classes([MultiPartParser, FormParser])
# @transaction.atomic
# def client_update(request, client_id):
#     """
#     Update a Client instance with nested relationships and file uploads.

#     Expected Input (multipart/form-data or JSON):
#     - client_first_name, client_code, etc. (scalars, not lists)
#     - Nested fields: client_passport, client_visa, family_members, etc. (list of dicts)
#     - Files: aadhaar_card_file, pan_card_file, family_members[0][aadhaarCard], etc.
#     - Choice fields (e.g., gender, marital_status) should match model choices (e.g., "Male", "Married")

#     Response:
#     - 200: Success with updated client data
#     - 400: Validation errors with detailed messages
#     - 401: Unauthorized if user is not authenticated
#     - 404: Client not found
#     - 500: Server error for invalid configurations
#     """
#     if not request.user.is_authenticated:
#         return Response({
#             "success": False,
#             "message": "User is not authenticated"
#         }, status=status.HTTP_401_UNAUTHORIZED)

#     try:
#         client = Client.objects.select_related(
#             'country_code', 'residential_country', 'residential_state', 'residential_city', 'ref_preferred_airline'
#         ).get(pk=client_id)
#     except Client.DoesNotExist:
#         return Response({
#             "success": False,
#             "message": f"Client with ID {client_id} not found"
#         }, status=status.HTTP_404_NOT_FOUND)
#     except ValueError as e:
#         return Response({
#             "success": False,
#             "message": f"Invalid prefetch_related configuration: {str(e)}"
#         }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

#     # Capture old data and status before any updates
#     old_data = serialize_client_with_related(client)
#     old_status = client.client_status
#     old_first = old_data.get('client_first_name', '')
#     old_last = old_data.get('client_last_name', '')
#     old_name = f"{old_first} {old_last}".strip()

#     # Parse and normalize input data
#     normalized_data = parse_nested_formdata({**request.data, **request.FILES})
#     normalized_data = normalize_formdata(normalized_data, skip_keys=[
#         "client_passport", "client_visa", "travel_insurance", "client_companies",
#         "client_documents", "client_frequent_flyers", "family_members", "fam_passport",
#         "relationsWithOthers"
#     ])

#     serializer = FullClientSerializer(
#         client,
#         data=normalized_data,
#         partial=True,
#         context={"request": request}
#     )
#     if serializer.is_valid():
#         updated_client = serializer.save()

#         # Capture new data after save
#         new_data = serialize_client_with_related(updated_client)
#         new_name = f"{updated_client.client_first_name or ''} {updated_client.client_last_name or ''}".strip()
#         new_status = updated_client.client_status
#         status_changed = old_status != new_status

#         # Filter out irrelevant fields for comparison
#         old_data_filtered = {
#             k: v for k, v in old_data.items()
#             if k not in ["client_status", "created_at", "updated_at", "updated_by"]
#         }
#         new_data_filtered = {
#             k: v for k, v in new_data.items()
#             if k not in ["client_status", "created_at", "updated_at", "updated_by"]
#         }

#         # Compute changes
#         changes = build_changes(old_data_filtered, new_data_filtered)
#         print(f"Computed changes: {changes}")

#         # Log only if there are changes
#         if changes:
#             log_data = {
#                 "description": changes,
#                 "client_name": new_name
#             }
#             try:
#                 ClientLog.objects.create(
#                     ref_client_id=updated_client,
#                     ref_table_name=updated_client._meta.db_table,
#                     ref_id=updated_client.pk,
#                     action_type="CLIENT UPDATED",
#                     changed_data=json.loads(json.dumps(log_data, cls=DjangoJSONEncoder)),
#                     performed_by=updated_client.updated_by,
#                     performed_at=timezone.now(),
#                 )
#                 print(f"Created CLIENT UPDATED log for client {updated_client.pk}: {changes}")
#             except Exception as e:
#                 print(f"Error creating CLIENT UPDATED log for client {updated_client.pk}: {e}")

#         # Log status changes
#         if status_changed:
#             try:
#                 ClientLog.objects.create(
#                     ref_client_id=updated_client,
#                     ref_table_name=updated_client._meta.db_table,
#                     ref_id=updated_client.pk,
#                     action_type="CLIENT - STATUS UPDATED",
#                     changed_data=json.loads(json.dumps({
#                         "client_name": {"old": old_name, "new": new_name},
#                         "client_status": {"old": old_status, "new": new_status}
#                     }, cls=DjangoJSONEncoder)),
#                     performed_by=updated_client.updated_by,
#                     performed_at=timezone.now(),
#                 )
#                 print(f"Created CLIENT - STATUS UPDATED log for client {updated_client.pk}")
#             except Exception as e:
#                 print(f"Error creating CLIENT - STATUS UPDATED log for client {updated_client.pk}: {e}")

#         return Response({
#             "success": True,
#             "message": "Client updated successfully",
#             "data": FullClientSerializer(updated_client, context={"request": request}).data
#         }, status=status.HTTP_200_OK)

#     return Response({
#         "success": False,
#         "message": "Validation failed",
#         "errors": serializer.errors
#     }, status=status.HTTP_400_BAD_REQUEST)
    

# @api_view(["PUT"])
# @permission_classes([IsAuthenticated])
# @parser_classes([MultiPartParser, FormParser])
# @transaction.atomic
# def client_update(request, client_id):
#     """
#     Update a Client instance with nested relationships and file uploads.

#     Expected Input (multipart/form-data or JSON):
#     - client_first_name, client_code, etc. (scalars, not lists)
#     - Nested fields: client_passport, client_visa, family_members, etc. (list of dicts)
#     - Files: aadhaar_card_file, pan_card_file, family_members[0][aadhaarCard], etc.
#     - Choice fields (e.g., gender, marital_status) should match model choices (e.g., "Male", "Married")

#     Response:
#     - 200: Success with updated client data
#     - 400: Validation errors with detailed messages
#     - 401: Unauthorized if user is not authenticated
#     - 404: Client not found
#     - 500: Server error for invalid configurations
#     """
#     if not request.user.is_authenticated:
#         return Response({
#             "success": False,
#             "message": "User is not authenticated"
#         }, status=status.HTTP_401_UNAUTHORIZED)

#     try:
#         client = Client.objects.select_related(
#             'country_code', 'residential_country', 'residential_state', 'residential_city', 'ref_preferred_airline'
#         ).get(pk=client_id)
#     except Client.DoesNotExist:
#         return Response({
#             "success": False,
#             "message": f"Client with ID {client_id} not found"
#         }, status=status.HTTP_404_NOT_FOUND)
#     except ValueError as e:
#         return Response({
#             "success": False,
#             "message": f"Invalid prefetch_related configuration: {str(e)}"
#         }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

#     # Capture old data and status before any updates
#     old_data = serialize_client_with_related(client)
#     old_status = client.client_status
#     old_first = old_data.get('client_first_name', '')
#     old_last = old_data.get('client_last_name', '')
#     old_name = f"{old_first} {old_last}".strip()

#     # Parse and normalize input data
#     normalized_data = parse_nested_formdata({**request.data, **request.FILES})
#     normalized_data = normalize_formdata(normalized_data, skip_keys=[
#         "client_passport", "client_visa", "travel_insurance", "client_companies",
#         "client_documents", "client_frequent_flyers", "family_members", "fam_passport",
#         "relationsWithOthers"
#     ])

#     serializer = FullClientSerializer(
#         client,
#         data=normalized_data,
#         partial=True,
#         context={"request": request}
#     )
#     if serializer.is_valid():
#         updated_client = serializer.save()

#         # Capture new data after save
#         new_data = serialize_client_with_related(updated_client)
#         new_name = f"{updated_client.client_first_name or ''} {updated_client.client_last_name or ''}".strip()
#         new_status = updated_client.client_status
#         status_changed = old_status != new_status

#         # Filter out irrelevant fields for comparison
#         old_data_filtered = {
#             k: v for k, v in old_data.items()
#             if k not in ["client_status", "created_at", "updated_at", "updated_by"]
#         }
#         new_data_filtered = {
#             k: v for k, v in new_data.items()
#             if k not in ["client_status", "created_at", "updated_at", "updated_by"]
#         }

#         # Compute changes
#         changes = build_changes(old_data_filtered, new_data_filtered)
#         print(f"Computed changes: {changes}")

#         # Log only if there are changes
#         if changes:
#             log_data = {
#                 "description": changes,
#                 "client_name": new_name
#             }
#             try:
#                 ClientLog.objects.create(
#                     ref_client_id=updated_client,
#                     ref_table_name=updated_client._meta.db_table,
#                     ref_id=updated_client.pk,
#                     action_type="CLIENT UPDATED",
#                     changed_data=json.loads(json.dumps(log_data, cls=DjangoJSONEncoder)),
#                     performed_by=updated_client.updated_by,
#                     performed_at=timezone.now(),
#                 )
#                 print(f"Created CLIENT UPDATED log for client {updated_client.pk}: {changes}")
#             except Exception as e:
#                 print(f"Error creating CLIENT UPDATED log for client {updated_client.pk}: {e}")

#         # # Log status changes
#         # if status_changed:
#         #     try:
#         #         ClientLog.objects.create(
#         #             ref_client_id=updated_client,
#         #             ref_table_name=updated_client._meta.db_table,
#         #             ref_id=updated_client.pk,
#         #             action_type="CLIENT - STATUS UPDATED",
#         #             changed_data=json.loads(json.dumps({
#         #                 "client_name": {"old": old_name, "new": new_name},
#         #                 "client_status": {"old": old_status, "new": new_status}
#         #             }, cls=DjangoJSONEncoder)),
#         #             performed_by=updated_client.updated_by,
#         #             performed_at=timezone.now(),
#         #         )
#         #         print(f"Created CLIENT - STATUS UPDATED log for client {updated_client.pk}")
#         #     except Exception as e:
#         #         print(f"Error creating CLIENT - STATUS UPDATED log for client {updated_client.pk}: {e}")

#         return Response({
#             "success": True,
#             "message": "Client updated successfully",
#             "data": FullClientSerializer(updated_client, context={"request": request}).data
#         }, status=status.HTTP_200_OK)

#     return Response({
#         "success": False,
#         "message": "Validation failed",
#         "errors": serializer.errors
#     }, status=status.HTTP_400_BAD_REQUEST)
    
    
@api_view(["PUT"])
@permission_classes([IsAuthenticated])
@parser_classes([MultiPartParser, FormParser])
@transaction.atomic
def client_update(request, client_id):
    """
    Update a Client instance with nested relationships and file uploads.

    Expected Input (multipart/form-data or JSON):
    - client_first_name, client_code, etc. (scalars, not lists)
    - Nested fields: client_passport, client_visa, family_members, etc. (list of dicts)
    - Files: aadhaar_card_file, pan_card_file, family_members[0][aadhaarCard], etc.
    - Choice fields (e.g., gender, marital_status) should match model choices (e.g., "Male", "Married")

    Response:
    - 200: Success with updated client data
    - 400: Validation errors with detailed messages
    - 401: Unauthorized if user is not authenticated
    - 404: Client not found
    - 500: Server error for invalid configurations
    """
    if not request.user.is_authenticated:
        return Response({
            "success": False,
            "message": "User is not authenticated"
        }, status=status.HTTP_401_UNAUTHORIZED)

    try:
        client = Client.objects.select_related(
            'country_code', 'residential_country', 'residential_state', 'residential_city', 'ref_preferred_airline'
        ).get(pk=client_id)
    except Client.DoesNotExist:
        return Response({
            "success": False,
            "message": f"Client with ID {client_id} not found"
        }, status=status.HTTP_404_NOT_FOUND)
    except ValueError as e:
        return Response({
            "success": False,
            "message": f"Invalid prefetch_related configuration: {str(e)}"
        }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    # Capture old data and status before any updates
    old_data = serialize_client_with_related(client)
    old_status = client.client_status
    old_first = old_data.get('client_first_name', '')
    old_last = old_data.get('client_last_name', '')
    old_name = f"{old_first} {old_last}".strip()

    # Parse and normalize input data
    normalized_data = parse_nested_formdata({**request.data, **request.FILES})
    normalized_data = normalize_formdata(normalized_data, skip_keys=[
        "client_passport", "client_visa", "travel_insurance", "client_companies",
        "client_documents", "client_frequent_flyers", "family_members", "fam_passport",
        "relationsWithOthers"
    ])

    serializer = FullClientSerializer(
        client,
        data=normalized_data,
        partial=True,
        context={"request": request}
    )
    if serializer.is_valid():
        updated_client = serializer.save(updated_by=request.user)

        # Capture new data after save
        new_data = serialize_client_with_related(updated_client)
        new_name = f"{updated_client.client_first_name or ''} {updated_client.client_last_name or ''}".strip()

        # Filter out irrelevant fields for comparison
        old_data_filtered = {
            k: v for k, v in old_data.items()
            if k not in ["client_status", "created_at", "updated_at","is_active"]
        }
        new_data_filtered = {
            k: v for k, v in new_data.items()
            if k not in ["client_status", "created_at", "updated_at","is_active"]
        }

        # Compute changes
        changes = build_changes(old_data_filtered, new_data_filtered)
        # if changes:
        #     changes['updated_by'] = str(updated_client.updated_by)

        # Log only if there are changes
        if changes:
            log_data = {
                "description": changes,
                "client_name": new_name
            }
            try:
                ClientLog.objects.create(
                    ref_client_id=updated_client,
                    ref_table_name=updated_client._meta.db_table,
                    ref_id=updated_client.pk,
                    action_type="CLIENT UPDATED",
                    changed_data=json.loads(json.dumps(log_data, cls=DjangoJSONEncoder)),
                    performed_by=updated_client.updated_by,
                    performed_at=timezone.now(),
                )
                print(f"Created CLIENT UPDATED log for client {updated_client.pk}: {changes}")
            except Exception as e:
                print(f"Error creating CLIENT UPDATED log for client {updated_client.pk}: {e}")

       
        return Response({
            "success": True,
            "message": "Client updated successfully",
            "data": FullClientSerializer(updated_client, context={"request": request}).data
        }, status=status.HTTP_200_OK)

    return Response({
        "success": False,
        "message": "Validation failed",
        "errors": serializer.errors
    }, status=status.HTTP_400_BAD_REQUEST)
    
'''
def normalize_family_members(data):
    """
    Convert form-data like family_members[0][field]
    into proper list of dicts.
    """
    family_data = defaultdict(dict)

    for key, value in data.items():
        if key.startswith("family_members"):
            parts = key.split("[")
            fam_index = int(parts[1][:-1])

            if len(parts) == 3:  # family_members[0][field]
                field = parts[2][:-1]
                family_data[fam_index][field] = value

            elif len(parts) == 5:  # family_members[0][passports][0][field]
                sub_field = parts[4][:-1]
                passport_index = int(parts[3][:-1])
                if "passports" not in family_data[fam_index]:
                    family_data[fam_index]["passports"] = []
                while len(family_data[fam_index]["passports"]) <= passport_index:
                    family_data[fam_index]["passports"].append({})
                family_data[fam_index]["passports"][passport_index][sub_field] = value

    return list(family_data.values())
'''

from collections import defaultdict

def normalize_family_members(data):
    """
    Convert form-data like family_members[0][field]
    into proper list of dicts for serializer.
    Handles nested passports properly.
    """
    family_data = defaultdict(dict)

    for key, value in data.items():
        if key.startswith("family_members"):
            parts = key.split("[")

            fam_index = int(parts[1][:-1])  # family index

            # --- Simple fields ---
            if len(parts) == 3:  # family_members[0][field]
                field = parts[2][:-1]
                family_data[fam_index][field] = value

            # --- Nested passport fields ---
            elif len(parts) == 5:  # family_members[0][client_passport][0][field]
                passport_index = int(parts[3][:-1])
                sub_field = parts[4][:-1]

                if "client_passport" not in family_data[fam_index]:
                    family_data[fam_index]["client_passport"] = []

                while len(family_data[fam_index]["client_passport"]) <= passport_index:
                    family_data[fam_index]["client_passport"].append({})

                # Normalize passport field names
                if sub_field in ["passportNo", "passport_no"]:
                    sub_field = "passport_no"
                elif sub_field in ["passport_expiry_date", "expiry"]:
                    sub_field = "passport_expiry_date"
                elif sub_field in ["passportFile", "passport_file"]:
                    sub_field = "passport_file"

                family_data[fam_index]["client_passport"][passport_index][sub_field] = value

    return list(family_data.values())


# @api_view(["GET", "POST"])
# @permission_classes([IsAuthenticated])
# @parser_classes([MultiPartParser, FormParser])
# @transaction.atomic
# def add_family_member(request, client_id):
#     primary_client = get_object_or_404(Client, pk=client_id, client_type="Primary Member")

#     if request.method == "GET":
#         return Response({
#             "success": True,
#             "primary_member": FullClientSerializer(primary_client, context={"request": request}).data
#         }, status=status.HTTP_200_OK)

#     elif request.method == "POST":
#         combined_data = request.POST.copy()
#         for key in ["aadhaar_card_file", "pan_card_file"]:
#             combined_data[key] = request.FILES.get(key)

#         family_members_data = normalize_family_members(combined_data)

#         serializer = AddFamilyMemberSerializer(
#             data=family_members_data,
#             many=True,
#             context={
#                 "request": request,
#                 "primary_client": primary_client,
#                 "created_by": request.user,
#             }
#         )

#         if serializer.is_valid():
#             family_members = serializer.save(created_by=request.user)  # created_by = request.user

#             response_serializer = AddFamilyMemberSerializer(
#                 family_members,
#                 many=True,
#                 context=serializer.context
#             )

#             return Response({
#                 "success": True,
#                 "message": "Family member(s) added successfully",
#                 "data": response_serializer.data
#             }, status=status.HTTP_201_CREATED)

#         return Response({
#             "success": False,
#             "errors": serializer.errors
#         }, status=status.HTTP_400_BAD_REQUEST)

# @api_view(["GET", "POST"])
# @permission_classes([IsAuthenticated])
# @parser_classes([MultiPartParser, FormParser])
# @transaction.atomic
# def add_family_member(request, client_id):
#     primary_client = get_object_or_404(Client, pk=client_id, client_type="Primary Member")

#     if request.method == "GET":
#         return Response({
#             "success": True,
#             "primary_member": FullClientSerializer(primary_client, context={"request": request}).data
#         }, status=status.HTTP_200_OK)

#     elif request.method == "POST":
#         # Combine POST and FILES data
#         combined_data = request.POST.copy()
#         for key in request.FILES:
#             combined_data[key] = request.FILES[key]

#         # Normalize family member data
#         family_members_data = normalize_family_members(combined_data)

#         # Initialize serializer
#         serializer = AddFamilyMemberSerializer(
#             data=family_members_data,
#             many=True,
#             context={
#                 "request": request,
#                 "primary_client": primary_client,
#                 "created_by": request.user,
#             }
#         )

#         # Validate and save
#         if serializer.is_valid():
#             try:
#                 family_members = serializer.save()
#                 response_serializer = AddFamilyMemberSerializer(
#                     family_members,
#                     many=True,
#                     context=serializer.context
#                 )
#                 ClientLog.objects.create(
#                     ref_client_id=response_serializer,
#                     ref_table_name=response_serializer._meta.db_table,
#                     ref_id=response_serializer.pk,
#                     action_type="FAMILY MEMBER ADDED",
#                     changed_data=json.loads(json.dumps(
#                         build_changes(None,serialize_client_with_related(response_serializer)),
#                         cls=DjangoJSONEncoder
#                     )),
#                     performed_by=response_serializer.created_by,
#                     performed_at=timezone.now(),
#                 )
#                 return Response({
#                     "success": True,
#                     "message": "Family member(s) added successfully",
#                     "data": response_serializer.data
#                 }, status=status.HTTP_201_CREATED)
#             except Exception as e:
#                 return Response({
#                     "success": False,
#                     "errors": {"detail": f"Failed to save family members: {str(e)}"}
#                 }, status=status.HTTP_400_BAD_REQUEST)
#         else:
#             return Response({
#                 "success": False,
#                 "errors": serializer.errors
#             }, status=status.HTTP_400_BAD_REQUEST)

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
@parser_classes([MultiPartParser, FormParser])
@transaction.atomic
def add_family_member(request, client_id):
    primary_client = get_object_or_404(Client, pk=client_id, client_type="Primary Member")

    if request.method == "GET":
        return Response({
            "success": True,
            "primary_member": FullClientSerializer(primary_client, context={"request": request}).data
        }, status=status.HTTP_200_OK)

    elif request.method == "POST":
        # Combine POST and FILES data
        combined_data = request.POST.copy()
        for key in request.FILES:
            combined_data[key] = request.FILES[key]

        # Normalize family member data
        family_members_data = normalize_family_members(combined_data)

        # Initialize serializer
        serializer = AddFamilyMemberSerializer(
            data=family_members_data,
            many=True,
            context={
                "request": request,
                "primary_client": primary_client,
                "created_by": request.user,
            }
        )

        # Validate and save
        if serializer.is_valid():
            try:
                family_members = serializer.save()
                response_serializer = AddFamilyMemberSerializer(
                    family_members,
                    many=True,
                    context=serializer.context
                )

                # Create a ClientLog entry for each family member
                for family_member in family_members:
                    ClientLog.objects.create(
                        ref_client_id=family_member,  # Use the model instance
                        ref_table_name=family_member._meta.db_table,  # Access _meta from the model
                        ref_id=family_member.pk,  # Use the model's primary key
                        action_type="FAMILY MEMBER ADDED",
                        changed_data=json.loads(json.dumps(
                            build_changes(None, serialize_client_with_related(family_member)),
                            cls=DjangoJSONEncoder
                        )),
                        performed_by=request.user,  # Use request.user directly
                        performed_at=timezone.now(),
                    )

                return Response({
                    "success": True,
                    "message": "Family member(s) added successfully",
                    "data": response_serializer.data
                }, status=status.HTTP_201_CREATED)
            except Exception as e:
                return Response({
                    "success": False,
                    "errors": {"detail": f"Failed to save family members: {str(e)}"}
                }, status=status.HTTP_400_BAD_REQUEST)
        else:
            return Response({
                "success": False,
                "errors": serializer.errors
            }, status=status.HTTP_400_BAD_REQUEST)

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def change_log(request):

    menu_id = 12
    perms = get_menu_permissions(request.user, menu_id)

    """
    Combined API:
    - Returns active clients & users
    - Returns last 100 client logs
    - Supports multi-select OR single select filters for clients & users, plus date range
    """

    # --- Clients (dropdown) ---
    clients = (
        Client.objects.filter(is_active=True)
        .annotate(
            client_name=Concat(
                "client_salutation", Value(" "),
                "client_first_name", Value(" "),
                "client_middle_name", Value(" "),
                "client_last_name"
            )
        )
        .values("client_id", "client_name")
        .order_by("client_first_name")
    )

    # --- Users (dropdown) ---
    users = (
        User.objects.filter(is_active=True)
        .order_by("username")
        .values("id", "username")
    )

    # --- Filters ---
    client_input = request.data.get("client")          # can be single string or empty
    performed_by_input = request.data.get("performedBy")  # can be single string or empty
    date_from = request.data.get("fromDate")
    date_to = request.data.get("toDate")

    # Convert single value or comma-separated strings to list
    client_ids = []
    if client_input:
        if isinstance(client_input, list):
            client_ids = client_input
        else:
            client_ids = [client_input]

    user_ids = []
    if performed_by_input:
        if isinstance(performed_by_input, list):
            user_ids = performed_by_input
        else:
            user_ids = [performed_by_input]

    # --- Build OR filter ---
    filters = Q()
    if client_ids:
        filters |= Q(ref_client_id__client_id__in=client_ids)
    if user_ids:
        filters |= Q(performed_by_id__in=user_ids)

    logs_qs = ClientLog.objects.filter(filters) if filters else ClientLog.objects.all()

    # --- Date filters ---
    if date_from:
        try:
            date_from_obj = datetime.strptime(date_from, "%Y-%m-%d")
            logs_qs = logs_qs.filter(performed_at__date__gte=date_from_obj.date())
        except ValueError:
            return Response({"success": False, "message": "Invalid fromDate format"}, status=400)

    if date_to:
        try:
            date_to_obj = datetime.strptime(date_to, "%Y-%m-%d")
            logs_qs = logs_qs.filter(performed_at__date__lte=date_to_obj.date())
        except ValueError:
            return Response({"success": False, "message": "Invalid toDate format"}, status=400)

    logs_qs = logs_qs.select_related("performed_by", "ref_client_id").order_by("-performed_at")[:100]

    # --- JSON-safe logs ---
    logs = []
    for log in logs_qs:
        logs.append({
            "id": log.id,
            "client_id": log.ref_client_id.client_id if log.ref_client_id else None,
            "client_name": log.ref_client_id.full_name if log.ref_client_id else None,
            "action": log.action_type,
            "description": log.changed_data,
            # "description": log.changed_data.description if log.changed_data.description else log.changed_data,
            "perform_by": log.performed_by.username if log.performed_by else None,
            "performed_on": localtime(log.performed_at).strftime("%d/%m/%Y %H:%M") if log.performed_at else None,
            "table": log.ref_table_name,
        })

    return Response({
        "success": True,
        "clients": list(clients),
        "users": list(users),
        "logs": logs,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)


'''
@api_view(["POST"])
@permission_classes([IsAuthenticated])
def client_log(request):
    """
    Combined API:
    - Returns active clients & users
    - Returns last 100 client logs with filters (client_id, user_id, date range)
    """

    # --- Clients (for dropdown) ---
    clients = (
        Client.objects.filter(client_status=1)
        .annotate(
            client_name=Concat(
                "client_salutation",
                Value(" "),
                "client_first_name",
                Value(" "),
                "client_middle_name",
                Value(" "),
                "client_last_name",
            )
        )
        .order_by("client_first_name")
        .values("client_id", "client_name")
    )

    # --- Users (for dropdown) ---
    users = (
        User.objects.filter(is_active=True)
        .order_by("username")
        .values("id", "username")
    )

    # --- Logs (with filters) ---
    filters = {}
    client_id = request.data.get("client_id")
    user_id = request.data.get("user_id")
    date_from = request.data.get("date_from")  # Expecting "YYYY-MM-DD"
    date_to = request.data.get("date_to")      # Expecting "YYYY-MM-DD"

    if client_id:
        filters["ref_client_id"] = client_id
    if user_id:
        filters["performed_by_id"] = user_id

    logs_qs = ClientLog.objects.filter(**filters)

    # Apply date range filter if provided
    if date_from:
        try:
            date_from_obj = datetime.strptime(date_from, "%Y-%m-%d")
            logs_qs = logs_qs.filter(performed_at__date__gte=date_from_obj.date())
        except ValueError:
            return Response({"success": False, "message": "Invalid date_from format. Use YYYY-MM-DD"}, status=400)

    if date_to:
        try:
            date_to_obj = datetime.strptime(date_to, "%Y-%m-%d")
            logs_qs = logs_qs.filter(performed_at__date__lte=date_to_obj.date())
        except ValueError:
            return Response({"success": False, "message": "Invalid date_to format. Use YYYY-MM-DD"}, status=400)

    # Only join what exists
    logs_qs = logs_qs.select_related("performed_by").order_by("-performed_at")[:100]


    logs = []
    for log in logs_qs:
        client_id_val = log.ref_client_id
        client_name = None

        if client_id_val:
            client = Client.objects.filter(pk=client_id_val).first()
            if client:
                client_name = f"{client.client_salutation or ''} {client.client_first_name or ''} {client.client_middle_name or ''} {client.client_last_name or ''}".strip()

        logs.append({
            "id": log.id,
            "client_id": client_id_val,
            "client_name": client_name,
            "action": log.action_type,
            "description": log.changed_data,
            "created_by": log.performed_by.username if log.performed_by else None,
            "created_at": log.performed_at,
            "table": log.ref_table_name,
        })

    
    # logs = []
    # for log in logs_qs:
    #     client_obj = None
    #     client_id_val = None
    #     if hasattr(log, "ref_client_id") and log.ref_client_id:
    #         # If it's a FK, Django gives the related object
    #         try:
    #             client_id_val = log.ref_client_id.pk
    #             client_obj = str(log.ref_client_id)
    #         except AttributeError:
    #             # If it's just an integer field
    #             client_id_val = log.ref_client_id
    #             client_obj = None

    #     logs.append({
    #         "id": log.id,
    #         "client_id": client_id_val,
    #         "client_name": client_obj,
    #         "action": log.action_type,
    #         "description": log.changed_data,
    #         "created_by": log.performed_by.username if log.performed_by else None,
    #         "created_at": log.performed_at,
    #         "table": log.ref_table_name,
    #     })
        

    return Response(
        {
            "success": True,
            "clients": list(clients),
            "users": list(users),
            "logs": logs,
        },
        status=status.HTTP_200_OK,
    )
'''

'''
# @login_required
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def client_log(request):
    """
    DRF equivalent of index() in CI4:
    Returns clients (active) and users (active).
    """
    # user = request.user
    # menuaction = "CLIENT_LOG"

    # # Permission check
    # if not has_menu_permission(user, menuaction, "view"):
    #     return Response(
    #         {"status": False, "message": f"Unauthorized Access to {menuaction}"},
    #         status=403,
    #     )

    # Clients
    clients = (
        Client.objects.filter(client_status=1)
        .annotate(
            client_name=Concat(
                "client_salutation",
                Value(" "),
                "client_first_name",
                Value(" "),
                "client_middle_name",
                Value(" "),
                "client_last_name",
            )
        )
        .order_by("client_first_name")
        .values("client_id", "client_name")
    )

    # Users
    users = (
        User.objects.filter(is_active=True)
        .order_by("username")
        .values("id", "username")
    )

    return Response(
        {"success": True, "clients": list(clients), "users": list(users)}
    )


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def get_client_logs(request):
    """
    DRF equivalent of getDataTableLog():
    Returns last 100 logs (with optional filters from request.data).
    """
    # user = request.user
    # menuaction = "CLIENT_LOG"

    # if not has_menu_permission(user, menuaction, "view"):
    #     return Response(
    #         {"status": False, "message": f"Unauthorized Access to {menuaction}"},
    #         status=403,
    #     )

    # Get filters (if any)
    filters = {}
    client_id = request.data.get("client_id")
    user_id = request.data.get("user_id")

    if client_id:
        filters["ref_client_id"] = client_id
    if user_id:
        filters["created_by_id"] = user_id

    logs = (
        ClientLog.objects.filter(**filters)
        .select_related("ref_client", "created_by")
        .order_by("-created_at")[:100]
    )

    data = []
    for log in logs:
        data.append({
            "id": log.id,
            "client": str(log.ref_client) if log.ref_client else None,
            "action": log.action,
            "description": log.description,
            "created_by": log.created_by.username if log.created_by else None,
            "created_at": log.created_at,
        })

    return Response({"success": True, "data": data}, status=status.HTTP_200_OK)
'''

'''
@api_view(["GET"])
@permission_classes([IsAuthenticated])
def view_log_detail(request, log_id):
    """
    DRF equivalent of viewDetailLog()
    """
    # user = request.user
    # menuaction = "CLIENT_LOG"

    # if not has_menu_permission(user, menuaction, "view"):
    #     return Response(
    #         {"status": False, "message": f"Unauthorized Access to {menuaction}"},
    #         status=403,
    #     )

    try:
        log = ClientLog.objects.select_related("ref_client", "created_by").get(id=log_id)
    except ClientLog.DoesNotExist:
        return Response(
            {"status": False, "message": "Log not found", "data": {}},
            status=404,
        )

    data = {
        "id": log.id,
        "client": str(log.ref_client) if log.ref_client else None,
        "action": log.action,
        "description": log.description,
        "created_by": log.created_by.username if log.created_by else None,
        "created_at": log.created_at,
    }

    return Response({"success": True, "message": "", "data": data}, status=status.HTTP_200_OK)
'''


'''
@login_required
def client_log_list(request, client_id):
    """
    API endpoint to fetch all change logs for a specific client.
    Returns JSON with list of changes.
    """
    if request.method != "POST":
        # return JsonResponse({"error": "Only GET method allowed"}, status=405)

        client = get_object_or_404(Client, id=client_id)
        logs = ClientLog.objects.filter(ref_client_id=client).order_by('-performed_at')

        data = []
        for log in logs:
            data.append({
                "id": log.id,
                "client_id": log.ref_client_id.id if log.ref_client_id else None,
                "ref_table_name": log.ref_table_name,
                "ref_id": log.ref_id,
                "action_type": log.action_type,
                "changed_data": log.changed_data,
                "performed_by": log.performed_by.username if log.performed_by else None,
                "performed_at": localtime(log.performed_at).strftime("%Y-%m-%d %H:%M:%S") if log.performed_at else None,
            })

        return JsonResponse({"client": client.id, "logs": data})

    return render(request, 'home/client_log/index.html', {
        'assigned_menu': ['DASHBOARD', 'USER', 'COMPANY', 'CLIENT', 'CLIENT_LOG'],
        'menuaction': 'CLIENT_LOG',
        'is_default': 1,
        'client': None,
    })
'''

'''
@csrf_exempt
@login_required
def client_update(request, client_id):
    if request.method == 'POST':
        if not client_id:
            return JsonResponse({'error': 'Client ID is required.'}, status=400)

        new_status = request.POST.get('client_status')
        if not new_status:
            return JsonResponse({'error': 'New status is required.'}, status=400)
        
        salutations = ['Mr.', 'Mrs.', 'Miss.', 'Ms.', 'Master.', 'Dr.']
        relations = ['Father', 'Mother', 'Spouse', 'Son', 'Daughter', 'Brother', 'Sister']
        contact_methods = ['Call', 'WhatsApp', 'Mail', 'Group Message']
        gender_choices = ['Male', 'Female', 'Other']
        marital_status_list = ['Married', 'Unmarried', 'Divorced']
        reference_list = ['BNI', 'Atul Sir', 'Jignesh Sir', 'Counter / Internal Staff', 'Client']
        seat_preferences = ["Window", "Aisle", "Middle", "Double Seat", "First Row", "Emergency Exit Row", "Other"]
        meal_options = ["Veg", "Jain Food", "Non Veg"]
        fare_preference_options = ["F - First class", "J - Business class", "W - Premium economy", "Y - Economy class"]
        visa_types = ["Business Visa", "Employment Visa", "Tourist Visa", "Medical Visa", "Student Visa"]
        visa_types = ["Business Visa", "Employment Visa", "Tourist Visa", "Medical Visa", "Student Visa"]

        client = get_object_or_404(Client, client_id=client_id)
        client.client_status = new_status
        client.save()

        return JsonResponse(context={
            'success': True,
            # 'form': form,
            'cc_min': 10,
            'cc_max': 12,
            'message': f'Status updated to {new_status.capitalize()}',
            'client_id': client.client_id,
            'client_status': client.client_status,
            'salutations': salutations,
            'relations': relations,
            'contact_methods': contact_methods,
            'gender_choices': gender_choices,
            'marital_status_list': marital_status_list,
            'reference_list': reference_list,
            'seat_preferences': seat_preferences,
            'meal_options': meal_options,
            'fare_preference_options': fare_preference_options,
            'visa_types': visa_types,
        })

    return render(request, 'home/client/updated_update.html', {
        'menuaction': 'CLIENT',
        'assigned_menu': ['DASHBOARD', 'CLIENT', 'USER', 'COMPANY'],
        'is_default': 1,
        'can_add': 1,
        'can_export': 1
    })
'''

def parse_date_safe(date_str):
    """Try parsing date in multiple formats, return None if invalid."""
    if not date_str:
        return None
    for fmt in ("%Y-%m-%d", "%d/%m/%Y"):
        try:
            return datetime.strptime(date_str, fmt).date()
        except ValueError:
            continue
    return None


# @api_view(["POST"])
# @permission_classes([IsAuthenticated])
# @parser_classes([MultiPartParser, FormParser, JSONParser])
# @transaction.atomic
# def add_family_member(request, client_id):
#     from .models import Client
#     parent = Client.objects.get(pk=client_id)
#     s = AddFamilyMemberSerializer(data=request.data)
#     s.is_valid(raise_exception=True)
#     temp_payload = {"clientDetails": {
#         "clientCode": parent.client_code, "firstName": parent.client_first_name
#     }, "familyMembers": [s.validated_data]}
#     client = ClientCreateSerializer().create({"clientDetails": {}, "familyMembers":[s.validated_data]})
#     return Response({"success": True, "familyClientId": client.client_id}, status=201)


'''
@transaction.atomic
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def client_add_member(request, master_id):
    main_client = get_object_or_404(Client, pk=master_id)

    if request.method == 'POST':
        # Basic Info
        first_name = request.POST.get('client_first_name', '').strip()
        middle_name = request.POST.get('client_middle_name')
        last_name = request.POST.get('client_last_name')
        salutation = request.POST.get('client_salutation')
        client_code = request.POST.get('client_code')
        dob = request.POST.get('dob')
        gender = request.POST.get('gender')
        relation = request.POST.get('member_relation')
        crossrelation = request.POST.get('member_crossrelation')
        email = request.POST.get('email')
        contact_no = request.POST.get('contact_no')
        country_code = request.POST.get('country_code')
        preferred_contact_method = request.POST.getlist('preferred_contact_method[]')

        # Documents
        aadhaar_no = request.POST.get('aadhaar_no')
        pan_no = request.POST.get('pan_no')
        aadhaar_card_file = request.FILES.get('aadhaar_card_file')
        pan_card_file = request.FILES.get('pan_card_file')

        # Passport (only one for now)
        passport_file = request.FILES.get('passport_file[1]')
        passport_no = request.POST.get('passport_no[1]')
        passport_expiry = request.POST.get('passport_expiry_date[1]')

        if not first_name:
            return JsonResponse({'status': False, 'message': 'First name is required'}, status=400)

        # Create family member as a Client WITHOUT passport fields
        member = Client.objects.create(
            ref_client_id=main_client.client_id,
            relation=relation,
            crossrelation=crossrelation,
            client_code=client_code,
            client_salutation=salutation,
            client_first_name=first_name,
            client_middle_name=middle_name,
            client_last_name=last_name,
            dob=parse_date_safe(dob),  # assuming you have this helper function
            gender=gender,
            contact_no=contact_no,
            email=email,
            country_code_id=country_code if country_code else None,
            preferred_contact_method=",".join(preferred_contact_method),
            aadhaar_no=aadhaar_no,
            aadhaar_card_file=aadhaar_card_file,
            pan_no=pan_no,
            pan_card_file=pan_card_file,
            client_status='Active',
            is_active=True,
            client_type='Family Member',
            created_by=request.user,
            updated_by=request.user,
            created_at=timezone.now(),
            updated_at=timezone.now(),
        )
        # print(crossrelation)
        def parse_date_flexible(date_str):
            for fmt in ('%d/%m/%Y', '%Y-%m-%d'):
                try:
                    return datetime.strptime(date_str, fmt).date()
                except (ValueError, TypeError):
                    continue
            return None

        passport_expiry_date = parse_date_flexible(passport_expiry)
        # Now create the passport info separately if any passport data exists
        if passport_no or passport_expiry or passport_file:
            ClientPassport.objects.create(
                ref_client=member,
                passport_no=passport_no,
                passport_expiry_date=passport_expiry_date,
                passport_file=passport_file
            )

        return JsonResponse({'status': True, 'message': 'Family member added successfully'})

    # For GET requests, render the add member page
    return Response({
        'menuaction': 'CLIENT',
        'assigned_menu': ['DASHBOARD', 'CLIENT', 'USER', 'COMPANY', 'CLIENT_LOG', 'report'],
        'is_default': 1,
        'master_id': master_id,
        'client_data': main_client,
        'country_data': Countries.objects.all()
    })
'''


'''
@csrf_exempt
@login_required
def client_add_member(request, master_id):
    client = get_object_or_404(Client, pk=master_id)

    if request.method == 'POST':
        name = request.POST.get('name')
        age = request.POST.get('age')
        relation = request.POST.get('relation')
        document_file = request.FILES.get('document_file')  # if you use a file field

        # Example: Save a related Member model
        ClientMember.objects.create(
            client=client,
            name=name,
            age=age,
            relation=relation,
            document=document_file
        )

        return JsonResponse({'status': True, 'message': 'Member added successfully'})
    return render(request, 'home/client/updated_add_member.html', {
        'menuaction': 'CLIENT',
        'assigned_menu': ['DASHBOARD', 'CLIENT', 'USER', 'COMPANY'],
        'is_default': 1,
        'can_add': 1,
        'can_export': 1,
        'master_id': master_id,
    })
'''


from django.db import models
from django.utils import timezone

@api_view(['PUT'])
@permission_classes([IsAuthenticated])
def client_active_status(request, client_id):
    client = get_object_or_404(Client, client_id=client_id)

    status_value = request.data.get("status")
    if status_value not in [0, 1, '0', '1', None]:
        return Response({"success": False, "error": "Invalid status. Allowed values: 1, 0"}, status=400)

    old_status = client.is_active

    if status_value is None:
        new_status = 0 if old_status else 1
    else:
        new_status = int(status_value)

    client.is_active = new_status
    client.client_status = new_status

    # ✅ Assign the actual logged-in user

    client.updated_by = request.user
    client.save()
    # client._status_updated_logged = True
    

    return Response({
        "success": True,
        "message": f"Client '{client.client_first_name} {client.client_last_name}' status updated successfully.",
        "client": {
            "id": client.client_id,
            "name": f"{client.client_first_name} {client.client_last_name}",
            # "updated_by": client.updated_by,
            "status": new_status,
        }
    }, status=status.HTTP_200_OK)

'''
@api_view(['PUT'])
@permission_classes([IsAuthenticated])
def client_active_status(request, client_id):
    client = get_object_or_404(Client, client_id=client_id)

    status_value = request.data.get("status")
    
    # if not status_value:
    #     return Response({"success": False, "error": "Missing 'status' field. Allowed values: active, inactive"}, status=400)

    # status_value = str(status_value).lower()
    # if status_value not in ["active", "inactive"]:
    #     return Response({"success": False, "error": f"Invalid status '{status_value}'. Allowed values: active, inactive"}, status=400)
    

    if status_value in [1,0]:
        # Toggle status if not provided
        # client.is_active = not client.is_active
        client.is_active = 0 if client.is_active == status_value else status_value
    else:
        try:
            status_value = int(status_value) if status_value is not None else None
        except ValueError:
            return Response(
            {"success": False, "error": f"Invalid status '{status_value}'. Allowed values: 1, 0"},
            status=400,
        )

        if status_value is None:
    # Toggle if not provided
            client.is_active = 0 if client.is_active == 1 else 1
        else:
            
            if status_value not in [1, 0]:
                return Response({"success": False, "error": f"Invalid status '{status_value}'. Allowed values: 1, 0"}, status=400)
            client.is_active = status_value
    


    # Set status
    client.is_active = status_value
    client.client_status = status_value
    # client.is_active = 1 if status_value else client.is_active = 0
    # client.client_status = 1 if status_value else client.client_status = 0
    client.save()

    # Convert datetime to string before logging
    changed_data = {
        "is_active": client.is_active,
        "client_status": client.client_status,
        "updated_at": timezone.localtime(client.updated_at).strftime("%Y-%m-%d %H:%M:%S") if client.updated_at else None,
        "aadhaar_card_url": client.aadhaar_card_file.url if client.aadhaar_card_file else None
    }

    # Log the status change
    ClientLog.objects.create(
        ref_client_id=client,
        ref_table_name=client._meta.db_table,
        ref_id=client.pk,
        action_type="STATUS UPDATED",
        changed_data=changed_data,
        performed_by=request.user,
        performed_at=timezone.now(),
    )

    return Response({
        "success": True,
        "message": f"Client '{client.client_first_name} {client.client_last_name}' status updated successfully.",
        "client": {
            "id": client.client_id,
            "name": f"{client.client_first_name} {client.client_last_name}",
            "status": 1 if client.is_active else 0
        }
    }, status=status.HTTP_200_OK)
'''

import datetime
from django.utils.timezone import now

# Client with their all family members Deletion
'''
@api_view(['DELETE'])
@permission_classes([IsAuthenticated])
def client_delete(request, client_id):
    try:
        client = Client.objects.get(pk=client_id)

        # Case 1: If client is a Primary Member, delete their family members too
        if client.client_type == "Primary Member":
            family_members = Client.objects.filter(
                ref_client=client
            ) | Client.objects.filter(old_ref_client_id=client.client_id)

            family_count = family_members.count()

            # Log family deletions
            for member in family_members:
                ClientLog.objects.create(
                    ref_client_id=member,
                    ref_table_name="clients",
                    ref_id=member.client_id,
                    action_type="DELETED",
                    changed_data={
                        "deleted_member_id": member.client_id,
                        "member_name": f"{member.client_first_name} {member.client_last_name}".strip(),
                        "relation": member.relation,
                        "deleted_by": request.user.username,
                    },
                    performed_by=request.user,
                    performed_at=now()
                )

            family_members.delete()

            # Log primary client deletion
            ClientLog.objects.create(
                ref_client_id=client,
                ref_table_name="clients",
                ref_id=client.client_id,
                action_type="DELETED",
                changed_data={
                    "deleted_client_id": client.client_id,
                    "client_name": f"{client.client_first_name} {client.client_last_name}".strip(),
                    "deleted_family_members": family_count,
                    "deleted_by": request.user,
                },
                performed_by=request.user,
                performed_at=now()
            )

            client.delete()

            return JsonResponse({
                "success": True,
                "message": f"Primary client {client.client_first_name} and {family_count} family members deleted successfully"
            }, status=status.HTTP_200_OK)

        # Case 2: If client is a Family Member, delete only that member
        else:
            ClientLog.objects.create(
                ref_client_id=client,
                ref_table_name="clients",
                ref_id=client.client_id,
                action_type="DELETED",
                changed_data={
                    "deleted_member_id": client.client_id,
                    "member_name": f"{client.client_first_name} {client.client_last_name}".strip(),
                    "relation": client.relation,
                    "deleted_by": request.user.username,
                },
                performed_by=request.user,
                performed_at=now()
            )

            client.delete()

            return JsonResponse({
                "success": True,
                "message": f"Family member {client.client_first_name} deleted successfully"
            }, status=status.HTTP_200_OK)

    except Client.DoesNotExist:
        return JsonResponse({
            "success": False,
            "error": "Client not found"
        }, status=status.HTTP_404_NOT_FOUND)
'''

# Only DELETE Client
'''
@api_view(['DELETE'])
@permission_classes([IsAuthenticated])
def client_delete(request, client_id):
    client = get_object_or_404(Client, pk=client_id)

    client_name = f"{client.client_first_name} {client.client_last_name}".strip()

    # Optional: detach logs so deletion can happen
    # client.client_logs.update(ref_client_id=None)

    # # Serialize all fields except 'id'
    old_data = serialize_instance(client, exclude_fields=["id"])
    # changes = build_changes(old_data, None)

    # ClientLog.objects.create(
    #     ref_client_id=client,
    #     ref_table_name=client._meta.db_table,
    #     ref_id=client.pk,
    #     action_type="DELETED",
    #     changed_data=json.loads(json.dumps(changes, cls=DjangoJSONEncoder)),
    #     performed_by=request.user,
    #     performed_at=timezone.now(),
    # )

    client.delete()

    return JsonResponse({
        "success": True,
        # "message": f"Client '{old_data.get('client_first_name', '')} {old_data.get('client_last_name', '')}'.strip() deleted successfully",
        "message": f"Client '{client_name}' deleted successfully",
        "client_name": client_name,
    }, status=status.HTTP_200_OK)
'''


@api_view(['DELETE'])
@permission_classes([IsAuthenticated])
def client_delete(request, client_id):
    try:
        client = Client.objects.get(pk=client_id)

        # Set updated_by so signal can access the deleting user
        client.updated_by = request.user
        client.save(update_fields=['updated_by'])

        client_name = f"{client.client_first_name} {client.client_last_name}".strip()

        client.delete()

        return JsonResponse({
            "success": True,
            "message": f"{client_name} deleted successfully",
        }, status=status.HTTP_200_OK)

    except Client.DoesNotExist:
        return JsonResponse({
            "success": False,
            "error": "Client not found",
        }, status=status.HTTP_404_NOT_FOUND)


'''
@api_view(['DELETE'])
@permission_classes([IsAuthenticated])
def client_delete(request, client_id):
    client = get_object_or_404(Client, pk=client_id)

    client_name = f"{client.client_first_name} {client.client_last_name}".strip()

    # Optional: detach logs so deletion can happen
    client.client_logs.update(ref_client_id=None)

    old_data = {
        "client_id": client.client_id,
        "client_name": f"{client.client_first_name} {client.client_last_name}".strip(),
        "aadhaar_card_file": client.aadhaar_card_file.url if client.aadhaar_card_file else None,
    }
    changed_data = {
        "old": old_data,
        "new": None,
        "deleted_by": request.user.username,
    }

    ClientLog.objects.create(
        ref_client_id=client,
        ref_table_name=client._meta.db_table,
        ref_id=client.pk,
        action_type="DELETED",
        changed_data=changed_data,
        performed_by=request.user,
        performed_at=timezone.now(),
    )

    client.delete()

    return JsonResponse({
        "success": True,
        "message": f"Client '{old_data['client_name']}' deleted successfully",
    }, status=status.HTTP_200_OK)
'''


@api_view(['POST'])
@permission_classes([IsAuthenticated])
@parser_classes([MultiPartParser])
def client_import_preview(request):
    try:
        file = request.FILES.get('file')
        if not file:
            return JsonResponse({'error': 'No file uploaded'}, status=400)

        ext = file.name.split('.')[-1].lower()
        if ext not in ['xls', 'xlsx', 'csv']:
            return JsonResponse({'error': 'Unsupported file format'}, status=400)

        # Step 1: Read file
        if file.name.endswith('.csv'):
            df = pd.read_csv(file)
        else:
            df = pd.read_excel(file)

        # Step 2: Normalize columns
        df.columns = [col.strip().lower().replace(' ', '_') for col in df.columns]
        df = df.where(pd.notnull(df), '')  # Replace NaNs with None

        # Step 3: Column mapping
        column_mapping = {
            'salutation': 'client_salutation',
            'first_name': 'client_first_name',
            'last_name': 'client_last_name',
            'contact_no': 'contact_no',
            'client_code': 'client_code',
            'email': 'email',
            'primary_member_code': 'ref_client_id',
            # 'relation' : 'relation',
            'dob': 'dob',
            'gender': 'gender',
            'address' : 'residential_address',
            'country' : 'residential_country',
            'state' : 'residential_state',
            'city': 'residential_city',
            'pincode': 'residential_pincode'
        }
        df.rename(columns=column_mapping, inplace=True)

        # Step 4: Required fields
        required_fields = ['client_code', 'client_first_name', 'client_last_name', 'contact_no', 'email']
        missing_fields = [f for f in required_fields if f not in df.columns]
        if missing_fields:
            return JsonResponse({'error': f'Missing required fields: {missing_fields}'}, status=400)

        # Step 5: Get existing clients from DB
        existing_client_codes = set(Client.objects.values_list('client_code', flat=True))  # Assuming Client model exists

        seen_client_codes = set()
        preview_data = []
        has_errors = False

        # Step 6: Row-by-row validation
        for idx, row in df.iterrows():
            client_code = str(row.get('client_code')).strip() if row.get('client_code') else ""
            # relation = str(row.get('relation')).strip() if row.get('relation') else ""
            try:
                ref_client_id = Client.objects.get(client_code=row.get('ref_client_id')).client_id if row.get('ref_client_id') else ""
            except Client.DoesNotExist:
                ref_client_id = ""
            except Client.MultipleObjectsReturned:
                ref_client_id = ""  # Or log an error, depending on requirements
            error = ""

            if not client_code:
                error = "Client code is required"
            elif client_code in seen_client_codes:
                error = "Duplicate client in file"
            elif client_code in existing_client_codes:
                error = "Client already exists"
                
            country = row.get('residential_country')
            state = row.get('residential_state')
            city = row.get('residential_city') 
            
            if not country and state :
                error = "country is required for state"
            if not country and not state and city :
                error = "country and state are required for city"
            if not country and state and city :
                error = "country is required "
            if country and not state and city :
                error = "state is required "
                
            try:
                country = Countries.objects.filter(name=row.get('residential_country').strip().lower()).get() if row.get('residential_country') else None
            except (Countries.DoesNotExist, Countries.MultipleObjectsReturned):
                error = "Country Does Not Exist"

            try:
                state = State.objects.filter(name=row.get('residential_state').strip().lower()).get() if row.get('residential_state') else None
            except (State.DoesNotExist, State.MultipleObjectsReturned):
                error = "state Does Not Exist"

            try:
                city = Cities.objects.filter(name=row.get('residential_city').strip().lower()).get() if row.get('residential_city') else None
            except (Cities.DoesNotExist, Cities.MultipleObjectsReturned):
                error = "city Does Not Exist"

            seen_client_codes.add(client_code)

            preview_data.append({
                "row_number": idx + 2,  # +2 to account for header row
                'client_salutation': row.get('client_salutation'),
                "client_code": client_code,
                "client_first_name": row.get('client_first_name'),
                "client_last_name": row.get('client_last_name'),
                'ref_client_id': ref_client_id,
                # 'relation':  relation,
                'dob' : row.get('dob'),
                'gender' : row.get('gender'),
                "contact_no": row.get('contact_no'),
                "email": row.get('email'),
                'residential_address' : row.get('residential_address'),
                'residential_country' : row.get('residential_country'),
                'residential_state' : row.get('residential_state'),
                'residential_city' : row.get('residential_city'),
                'residential_pincode' : row.get('residential_pincode'),
                "error": error
            })

            if error:
                has_errors = True

        return JsonResponse({
            'rows': preview_data,
            'has_errors': has_errors,
            'message': 'Preview generated successfully'
        }, status=200)

    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)


@api_view(['POST'])
@permission_classes([IsAuthenticated])
@parser_classes([MultiPartParser])
def client_import(request):
    try:
        file = request.FILES.get('file')
        if not file:
            return JsonResponse({'error': 'No file uploaded'}, status=400)

        ext = file.name.split('.')[-1].lower()
        if ext not in ['xls', 'xlsx', 'csv']:
            return JsonResponse({'error': 'Unsupported file format'}, status=400)

        # Step 1: Read file
        if file.name.endswith('.csv'):
            df = pd.read_csv(file)
        else:
            df = pd.read_excel(file)

        # Step 2: Normalize column names
        df.columns = [col.strip().lower().replace(' ', '_') for col in df.columns]
        df = df.where(pd.notnull(df), '')  # Replace NaNs with None
        
        # Step 3: Column mapping
        column_mapping = {
            'salutation': 'client_salutation',
            'first_name': 'client_first_name',
            'last_name': 'client_last_name',
            'contact_no': 'contact_no',
            'client_code': 'client_code',
            'email': 'email',
            'primary_member_code': 'ref_client_id',
            # 'relation' : 'relation',
            'dob': 'dob',
            'gender': 'gender',
            'address' : 'residential_address',
            'country' : 'residential_country',
            'state' : 'residential_state',
            'city': 'residential_city',
            'pincode': 'residential_pincode'
        }
        df.rename(columns=column_mapping, inplace=True)

        # Step 4: Check required fields
        required_fields = ['client_code', 'client_first_name', 'client_last_name', 'contact_no', 'email']
        missing_fields = [field for field in required_fields if field not in df.columns]
        if missing_fields:
            return JsonResponse({'error': f'Missing required fields: {missing_fields}'}, status=400)

        # Step 5: Existing client codes
        existing_client_codes = set(Client.objects.values_list('client_code', flat=True))

        success_count = 0
        failed_rows = []

        for idx, row in df.iterrows():
            
            try:
                client_code = str(row.get('client_code')).strip() if row.get('client_code') else None
                relation = str(row.get('relation')).strip() if row.get('relation') else ""
                try:
                    ref_client_id = Client.objects.get(client_id=int(row.get('ref_client_id'))).client_id if row.get('ref_client_id') else None
                except (Client.DoesNotExist, Client.MultipleObjectsReturned):
                    ref_client_id = None
                try:
                    country = Countries.objects.filter(name=row.get('residential_country').strip().lower()).get() if row.get('residential_country') else None
                except (Countries.DoesNotExist, Countries.MultipleObjectsReturned):
                    country = None

                try:
                    state = State.objects.filter(name=row.get('residential_state').strip().lower()).get() if row.get('residential_state') else None
                except (State.DoesNotExist, State.MultipleObjectsReturned):
                    state = None

                try:
                    city = Cities.objects.filter(name=row.get('residential_city').strip().lower()).get() if row.get('residential_city') else None
                except (Cities.DoesNotExist, Cities.MultipleObjectsReturned):
                    city = None

                if not client_code:
                    raise ValueError("Client code is required")

                if client_code in existing_client_codes:
                    raise ValueError("Client already exists")
                
                Client.objects.create(
                    client_code=client_code,
                    client_first_name=row.get('client_first_name'),
                    client_last_name=row.get('client_last_name'),
                    email=row.get('email'),
                    contact_no=row.get('contact_no') or '',
                    client_salutation = row.get('client_salutation'),
                    ref_client_id = ref_client_id,
                    relation =  relation,
                    dob = row.get('dob'),
                    gender = row.get('gender'),
                    residential_address = row.get('residential_address'),
                    residential_country = country,
                    residential_state = state,
                    residential_city = city,
                    residential_pincode = row.get('residential_pincode'),
                    client_status='active',
                    is_active=True
                )
                success_count += 1
                existing_client_codes.add(client_code)  # Avoid re-adding in the same file

            except Exception as e:
                failed_rows.append({
                    "row_number": idx + 2,  # +2 accounts for header and 1-based index
                    "client_code": row.get('client_code'),
                    "error": str(e)
                })

        return JsonResponse({
            "status": True,
            "successfully_imported": success_count,
            "failed_count": len(failed_rows),
            "failures": failed_rows,
        }, status=200)

    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)


'''
# @csrf_exempt
# @login_required
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def client_import(request):
    if request.method == 'POST':
        try:
            file = request.FILES.get('file')
            if not file:
                return JsonResponse({'error': 'No file uploaded'}, status=400)

            ext = file.name.split('.')[-1].lower()
            if ext not in ['xls', 'xlsx', 'csv']:
                return JsonResponse({'error': 'Unsupported file format'}, status=400)

            # Step 1: Read file
            if file.name.endswith('.csv'):
                df = pd.read_csv(file)
            elif file.name.endswith(('.xls', '.xlsx')):
                df = pd.read_excel(file)
            else:
                return JsonResponse({'error': 'Unsupported file format'}, status=400)

            # Step 2: Normalize column names (convert spaces to underscores, lowercase)
            df.columns = [col.strip().lower().replace(' ', '_') for col in df.columns]

            # Step 3: Optional mapping for known alternate names
            column_mapping = {
                'first_name': 'client_first_name',
                'last_name': 'client_last_name',
                'contact_no': 'contact_no',
                'client_code': 'client_code',
                'email': 'email',
            }

            df.rename(columns=column_mapping, inplace=True)

            # Step 4: Check required fields
            required_fields = ['client_code', 'client_first_name', 'client_last_name', 'contact_no', 'email']
            for field in required_fields:
                if field not in df.columns:
                    return JsonResponse({'error': f'Missing required field: {field}'}, status=400)
                
            print("Received columns:", df.columns.tolist())

            clients_to_create = []
            for _, row in df.iterrows():
                client = Client(
                    client_code=row.get('client_code'),
                    client_first_name=row.get('client_first_name'),
                    client_last_name=row.get('client_last_name'),
                    email=row.get('email'),
                    contact_no=row.get('contact_no') or '',
                    client_status='active',
                    is_active=True
                    # Add any additional fields here
                )
                clients_to_create.append(client)

            Client.objects.bulk_create(clients_to_create)

            return JsonResponse({'message': f'{len(clients_to_create)} clients imported successfully.'})

        except Exception as e:
            return JsonResponse({'error': str(e)}, status=500)

    # Render template for GET request
    return Response({
        'menuaction': 'CLIENT',
        'assigned_menu': ['CLIENT', 'DASHBOARD', 'COMPANY', 'USER', 'CLIENT_LOG', 'report'],
        'is_default': 1,
    })
'''

'''
@login_required
def visa_expiry(request):
    visas = []

    if request.method == 'POST':
        time_period = request.POST.get('time_period')
        from_date_str = request.POST.get('report_from_date')
        to_date_str = request.POST.get('report_to_date')
        visa_country = request.POST.get('visa_country')

        visas = ClientVisa.objects.all()

        # Apply country filter
        if visa_country:
            visas = visas.filter(ref_visa_country_id=visa_country)

        # Apply time period filter
        if time_period and time_period != "Custom Dates":
            today = date.today()
            if time_period == "1 Week":
                visas = visas.filter(expiry_date__lte=today + timedelta(weeks=1))
            elif time_period == "2 Weeks":
                visas = visas.filter(expiry_date__lte=today + timedelta(weeks=2))
            elif time_period == "3 Weeks":
                visas = visas.filter(expiry_date__lte=today + timedelta(weeks=3))
            elif time_period == "1 Month":
                visas = visas.filter(expiry_date__lte=today + timedelta(days=30))
            elif time_period == "3 Months":
                visas = visas.filter(expiry_date__lte=today + timedelta(days=90))
            elif time_period == "6 Months":
                visas = visas.filter(expiry_date__lte=today + timedelta(days=180))
            elif time_period == "1 Year":
                visas = visas.filter(expiry_date__lte=today + timedelta(days=365))

        # Apply custom date range
        elif time_period == "Custom Dates" and from_date_str and to_date_str:
            from_date = parse_date(from_date_str)
            to_date = parse_date(to_date_str)
            if from_date and to_date:
                visas = visas.filter(expiry_date__range=[from_date, to_date])

    return render(request, 'home/reports/visa_expiry/index.html', {
        'menuaction': 'REPORT_VISA_EXPIRY',
        'assigned_menu': ['DASHBOARD', 'CLIENT', 'USER', 'COMPANY', 'REPORT', 'REPORT_VISA_EXPIRY'],
        'is_default': 1,
        'can_add': 0,
        'can_export': 1,
        'visas': visas
    })
'''


# @login_required
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def visa_expiry_report(request):
    if request.method == "GET":
        time_period = request.GET.get("time_period")
        visa_country = request.GET.get("visa_country")
        report_from_date = request.GET.get("report_from_date")
        report_to_date = request.GET.get("report_to_date")

        visa_report = ClientVisa().get_visa_expiry_report(
            time_period, visa_country, report_from_date, report_to_date
        )

#  # --- Apply filters ---
#     if time_period == "today":
#         qs = qs.filter(
#             anniversary_date__day=today.day,
#             anniversary_date__month=today.month,
#         )

    


#     # --- Build response ---
#     data = []
#     for c in qs.order_by("anniversary_date"):
#         data.append({
#             "client_id": c.client_id,
#             "client_name": f"{c.client_first_name} {c.client_last_name}".strip(),
#             "anniversary_date": c.anniversary_date,
#         })

#     return Response({
#         "success": True,
#         "message": "",
#         "data": data,
#         "permissions": {
#             "can_view": perms.can_view,
#             "can_add": perms.can_add,
#             "can_edit": perms.can_edit,
#             "can_delete": perms.can_delete,
#             "can_export": perms.can_export,
#         } if perms else {}
#     }, status=status.HTTP_200_OK)




        context = {"visa_report": visa_report}
        report_data = Response(
            context
        ).content.decode("utf-8")

        return JsonResponse({"status": True, "message": "", "Data": report_data})

    return JsonResponse({"status": False, "message": "Invalid request"}, status=400)


'''
@login_required
@require_menu_perm('VISA_EXPIRY', 'view')
def visa_expiry_list(request):
    """
    Render the Visa Expiry Report page with filter options.
    """
    perms = get_module_perms(request, 'VISA_EXPIRY')

    # Example filter dropdowns (countries, clients, etc.)
    with connection.cursor() as cursor:
        cursor.execute("SELECT id, country_name FROM countries ORDER BY country_name")
        countries = cursor.fetchall()

    context = {
        "perms": perms,
        "countries": countries,
    }
    return render(request, "home/reports/visa_expiry/index.html", context)
'''


def has_menu_permission(user, menuaction, action="view"):
    """
    Check if user has given permission for a menuaction
    """
    try:
        perm = UserPermissions.objects.get(
            user=user, ref_menu__menu_action=menuaction
        )
        return getattr(perm, f"can_{action}", False)
    except UserPermissions.DoesNotExist:
        return False


def parse_date(value):
    """Convert string (DD-MM-YYYY or YYYY-MM-DD) to date safely"""
    if not value:
        return None
    for fmt in ("%d-%m-%Y", "%Y-%m-%d"):
        try:
            return datetime.strptime(value, fmt).date()
        except ValueError:
            continue
    return None


@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def visa_expiry(request):
    """
    Visa Expiry Report API
    - Filters: today, 1Week, 2Weeks, 3Weeks, 1Month, 3Months, 6Months, 1Year, custom
    - Optional filter by visa country
    - Returns visa expiry details
    """

    menu_id = 6
    perms = get_menu_permissions(request.user, menu_id)

    # --- Input filters ---
    time_period = request.data.get("time_period") or request.query_params.get("time_period")
    report_from_date = request.data.get("report_from_date") or request.query_params.get("report_from_date")
    report_to_date = request.data.get("report_to_date") or request.query_params.get("report_to_date")
    ref_visa_country = request.data.get("countryId") or request.query_params.get("countryId")

    today = timezone.now().date()

    qs = ClientVisa.objects.exclude(visa_to_date=None).select_related("ref_client", "ref_visa_country")

    # --- Apply filters ---

    if time_period == "1Week":
        qs = qs.filter(visa_to_date__range=[today, today + timedelta(days=7)])

    elif time_period == "2Weeks":
        qs = qs.filter(visa_to_date__range=[today, today + timedelta(days=15)])

    elif time_period == "3Weeks":
        qs = qs.filter(visa_to_date__range=[today, today + timedelta(days=21)])

    elif time_period == "1Month":
        qs = qs.filter(visa_to_date__range=[today, today + timedelta(days=30)])

    elif time_period == "3Months":
        qs = qs.filter(visa_to_date__range=[today, today + timedelta(days=90)])

    elif time_period == "6Months":
        qs = qs.filter(visa_to_date__range=[today, today + timedelta(days=180)])

    elif time_period == "1Year":
        qs = qs.filter(visa_to_date__range=[today, today + timedelta(days=365)])

    elif time_period == "custom" and report_from_date and report_to_date:
        qs = qs.filter(visa_to_date__range=[report_from_date, report_to_date])

    # --- Country filter (optional) ---
    if ref_visa_country:
        qs = qs.filter(ref_visa_country_id=ref_visa_country)

    # --- Build response ---
    data = []
    for v in qs.order_by("visa_to_date"):
        if not v.ref_client:
            continue
        remaining_days = (v.visa_to_date - today).days if v.visa_to_date else None

        data.append({
            "id": v.id,
            "client_name": f"{v.ref_client.client_first_name or ''} {v.ref_client.client_last_name or ''}".strip(),
            "country_name": v.ref_visa_country.name if v.ref_visa_country else None,
            "visa_type": v.visa_type,
            "visa_from_date": v.visa_from_date,
            "visa_to_date": v.visa_to_date,
            "remaining_days": remaining_days,
            "passport_size_photograph": v.passport_size_photograph.url if v.passport_size_photograph else None,
        })

    return Response({
        "success": True,
        "message": "" if data else "No visa records found for the given criteria",
        "data": data,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)


'''
@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def visa_expiry(request):
    menu_id = 6
    perms = get_menu_permissions(request.user, menu_id)

    # Payload (camelCase + snake_case support)
    time_period = request.data.get("timePeriod") or request.data.get("time_period") or request.query_params.get("time_period")
    visa_country = request.data.get("countryId") or request.data.get("visa_country") or request.query_params.get("visa_country")
    start_date = parse_date(request.data.get("startDate") or request.data.get("report_from_date") or request.query_params.get("start_date"))
    end_date = parse_date(request.data.get("endDate") or request.data.get("report_to_date") or request.query_params.get("end_date"))

    today = timezone.now().date()
    visas = ClientVisa.objects.select_related("ref_client", "ref_visa_country")

    # --- Apply country filter ---
    if visa_country:
        visas = visas.filter(ref_visa_country_id=visa_country)

    # --- Normalize time period ---
    if time_period:
        original_value = time_period
        time_period = str(time_period).strip().lower().replace(" ", "_")

        print(">>> Visa Expiry time_period:", original_value, "->", time_period)

        if time_period == "1_week":
            visas = visas.filter(visa_to_date__range=[today, today + timedelta(weeks=1)])

        elif time_period == "2_weeks":
            visas = visas.filter(visa_to_date__range=[today, today + timedelta(weeks=2)])

        elif time_period == "3_weeks":
            visas = visas.filter(visa_to_date__range=[today, today + timedelta(weeks=3)])

        elif time_period == "1_month":
            visas = visas.filter(visa_to_date__range=[today, today + timedelta(days=30)])

        elif time_period == "3_months":
            visas = visas.filter(visa_to_date__range=[today, today + timedelta(days=90)])

        elif time_period == "6_months":
            visas = visas.filter(visa_to_date__range=[today, today + timedelta(days=180)])

        elif time_period == "1_year":
            visas = visas.filter(visa_to_date__range=[today, today + timedelta(days=365)])

        elif time_period == "custom" and start_date and end_date:
            visas = visas.filter(visa_to_date__range=[start_date, end_date])

        elif time_period == "expired":
            visas = visas.filter(visa_to_date__lt=today)

        elif time_period == "valid":
            visas = visas.filter(visa_to_date__gte=today)

    # --- Build response ---
    data = []
    for v in visas.order_by("visa_to_date"):
        if not v.ref_client:
            continue
        remaining_days = None
        if v.visa_to_date:
            remaining_days = (v.visa_to_date - today).days

        data.append({
            "id": v.id,
            "client_name": f"{v.ref_client.client_first_name or ''} {v.ref_client.client_last_name or ''}".strip(),
            "country_name": v.ref_visa_country.name if v.ref_visa_country else None,
            "visa_type": v.visa_type,
            "passport_size_photograph": v.passport_size_photograph.url if v.passport_size_photograph else None,
            "visa_from_date": v.visa_from_date,
            "visa_to_date": v.visa_to_date,
            "remaining_days": remaining_days,
        })

    return Response({
        "success": True,
        "message": "" if data else "No visa records found for the given criteria",
        "data": data,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)
'''


'''
@login_required
@require_menu_perm('VISA_EXPIRY', 'export')
def visa_expiry_export(request):
    """
    Export Visa Expiry data as CSV.
    Accepts same filters as ajax.
    """
    country_id = request.GET.get("country_id")
    client_id = request.GET.get("client_id")
    expiry_before = request.GET.get("expiry_before")

    query = """
        SELECT c.client_name, v.visa_no, v.visa_expiry_date, co.country_name
        FROM visas v
        JOIN clients c ON v.client_id = c.id
        JOIN countries co ON v.country_id = co.id
        WHERE 1=1
    """
    params = []

    if country_id:
        query += " AND co.id = %s"
        params.append(country_id)

    if client_id:
        query += " AND c.id = %s"
        params.append(client_id)

    if expiry_before:
        query += " AND v.visa_expiry_date <= %s"
        params.append(expiry_before)

    with connection.cursor() as cursor:
        cursor.execute(query, params)
        rows = cursor.fetchall()

    # Generate CSV response
    response = HttpResponse(content_type="text/csv")
    response["Content-Disposition"] = 'attachment; filename="visa_expiry_report.csv"'

    writer = csv.writer(response)
    writer.writerow(["Client Name", "Visa No", "Visa Expiry Date", "Country"])

    for row in rows:
        writer.writerow(row)

    return response
'''


'''
@login_required
def visa_expiry_index(request):
    # permission_service = UserPermissions()
    # menuaction = "REPORT_VISA_EXPIRY"
    # user_id = request.user.id
    # menu_permissions = permission_service.get_permissions(user_id, menuaction)

    # if menu_permissions and not menu_permissions.can_view:
    #     return redirect("unauthorized")

    # country_data = Countries().get_all_data("countries")
    return render(request, "home/reports/visa_expiry/index.html", {
        # "country_data": country_data,
        # "menuaction": menuaction,
        'menuaction': 'REPORT_PASSPORT_EXPIRY',
        'assigned_menu': ['DASHBOARD', 'CLIENT', 'USER', 'COMPANY', 'REPORT', 'REPORT_VISA_EXPIRY', 'REPORT_PASSPORT_EXPIRY'],
        'is_default': 1,
        'can_add': 0,
        'can_export': 1,

    })
'''


'''
from django.utils.timezone import now
@login_required
@require_menu_perm('REPORT', 'view')
@transaction.atomic
def visa_expiry_report(request):
    """
    Simple visa expiry report:
    - Lists clients whose visa is expiring in the next 30 days
    - Or already expired
    """

    today = now().date()
    upcoming_date = today + timedelta(days=30)

    expiring_clients = Client.objects.filter(
        visa_expiry_date__isnull=False,
        visa_expiry_date__lte=upcoming_date
    ).order_by('visa_expiry_date')

    if request.headers.get("x-requested-with") == "XMLHttpRequest":
        # return JSON for AJAX
        data = [
            {
                "id": c.id,
                "name": c.client_name,
                "passport_no": c.passport_no,
                "visa_expiry_date": c.visa_expiry_date.strftime("%Y-%m-%d") if c.visa_expiry_date else None,
                "status": "Expired" if c.visa_expiry_date < today else "Expiring Soon",
            }
            for c in expiring_clients
        ]
        return JsonResponse({"data": data})

    return render(request, "home/reports/visa_expiry/ajax_report.html", {
        "clients": expiring_clients,
        "today": today,
        "upcoming": upcoming_date,
    })
'''


def parse_date(value):
    """Convert string (YYYY-MM-DD) to date safely"""
    if not value:
        return None
    try:
        return datetime.strptime(value, "%d-%m-%Y").date()  # match your UI format
    except ValueError:
        try:
            return datetime.strptime(value, "%Y-%m-%d").date()
        except ValueError:
            return None

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def passports_expiry_report(request):
    menu_id = 7
    perms = get_menu_permissions(request.user, menu_id)

    # Request payload (accept both camelCase & snake_case)
    time_period = request.data.get("timePeriod") or request.data.get("time_period") or request.query_params.get("time_period")
    start_date = request.data.get("startDate") or request.data.get("report_from_date") or request.query_params.get("report_from_date")
    end_date = request.data.get("endDate") or request.data.get("report_to_date") or request.query_params.get("report_to_date")


    # Strip spaces if any
    report_from_date = start_date.strip() if isinstance(start_date, str) else start_date
    report_to_date = end_date.strip() if isinstance(end_date, str) else end_date


    today = timezone.now().date()
    qs = ClientPassport.objects.select_related("ref_client")

    # --- Apply filters ---
    if time_period:
        tp = str(time_period).strip().lower().replace(" ", "_")

        if tp == "1week":
            qs = qs.filter(passport_expiry_date__range=[today, today + timedelta(weeks=1)])

        elif tp == "2weeks":
            qs = qs.filter(passport_expiry_date__range=[today, today + timedelta(weeks=2)])

        elif tp == "3weeks":
            qs = qs.filter(passport_expiry_date__range=[today, today + timedelta(weeks=3)])

        elif tp == "1month":
            qs = qs.filter(passport_expiry_date__range=[today, today + timedelta(days=30)])

        elif tp == "3months":
            qs = qs.filter(passport_expiry_date__range=[today, today + timedelta(days=90)])

        elif tp == "6months":
            qs = qs.filter(passport_expiry_date__range=[today, today + timedelta(days=180)])

        elif tp == "1year":
            qs = qs.filter(passport_expiry_date__range=[today, today + timedelta(days=365)])

        elif tp == "custom" and report_from_date and report_to_date:
            qs = qs.filter(passport_expiry_date__range=[report_from_date, report_to_date])

    # --- Build response ---
    data = []
    for p in qs.exclude(passport_expiry_date__isnull=True).order_by("passport_expiry_date"):
        if not p.ref_client:
            continue

        remaining_days = (p.passport_expiry_date - today).days if p.passport_expiry_date else None

        data.append({
            "client_id": p.ref_client.client_id,
            "client_name": f"{p.ref_client.client_first_name or ''} {p.ref_client.client_last_name or ''}".strip(),
            "passport_no": p.passport_no,
            "passport_expiry_date": p.passport_expiry_date,
            "remaining_days": remaining_days,
        })

    return Response({
        "success": True,
        "message": "" if data else "No passports found for the given criteria",
        "data": data,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)


@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def travel_insurance_expiry(request):
    """
    Travel Insurance Expiry Report API
    - Filters: today, 1Week, 2Weeks, 3Weeks, 1Month, 3Months, 6Months, 1Year, custom
    - Extra filters: expired, valid
    """

    menu_id = 8
    perms = get_menu_permissions(request.user, menu_id)

    # --- Input filters (accept camelCase & snake_case) ---
    time_period = request.data.get("timePeriod") or request.data.get("time_period")
    start_date = request.data.get("startDate") or request.data.get("report_from_date")
    end_date = request.data.get("endDate") or request.data.get("report_to_date")

    today = timezone.now().date()

    qs = ClientTravelInsurance.objects.exclude(insurance_to_date=None).select_related("ref_client")

    # --- Apply filters ---
    if time_period == "today":
        qs = qs.filter(insurance_to_date=today)

    elif time_period == "1Week":
        qs = qs.filter(insurance_to_date__range=[today, today + timedelta(days=7)])

    elif time_period == "2Weeks":
        qs = qs.filter(insurance_to_date__range=[today, today + timedelta(days=15)])

    elif time_period == "3Weeks":
        qs = qs.filter(insurance_to_date__range=[today, today + timedelta(days=21)])

    elif time_period == "1Month":
        qs = qs.filter(insurance_to_date__range=[today, today + timedelta(days=30)])

    elif time_period == "3Months":
        qs = qs.filter(insurance_to_date__range=[today, today + timedelta(days=90)])

    elif time_period == "6Months":
        qs = qs.filter(insurance_to_date__range=[today, today + timedelta(days=180)])

    elif time_period == "1Year":
        qs = qs.filter(insurance_to_date__range=[today, today + timedelta(days=365)])

    elif time_period == "custom" and start_date and end_date:
        qs = qs.filter(insurance_to_date__range=[start_date, end_date])

    # elif time_period == "expired":
    #     qs = qs.filter(insurance_to_date__lt=today)

    # elif time_period == "valid":
    #     qs = qs.filter(insurance_to_date__gte=today)

    # --- Build response ---
    data = []
    for ins in qs.order_by("insurance_to_date"):
        if not ins.ref_client:
            continue

        remaining_days = (ins.insurance_to_date - today).days if ins.insurance_to_date else None

        data.append({
            "id": ins.id,
            "client_id": ins.ref_client.client_id,
            "client_name": f"{ins.ref_client.client_first_name or ''} {ins.ref_client.client_last_name or ''}".strip(),
            "insurance_document": ins.insurance_document.url if ins.insurance_document else None,
            "insurance_from_date": ins.insurance_from_date,
            "insurance_to_date": ins.insurance_to_date,
            "remaining_days": remaining_days
        })

    return Response({
        "success": True,
        "message": "" if data else "No travel insurance records found for the given criteria",
        "data": data,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)


def parse_date(value):
    """Convert string (DD-MM-YYYY or YYYY-MM-DD) to date safely"""
    if not value:
        return None
    for fmt in ("%d-%m", "%m-%d"):
        try:
            return datetime.strptime(value, fmt).date()
        except ValueError:
            continue
    return None

'''
@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def birth_report(request):
    menu_id = 9
    perms = get_menu_permissions(request.user, menu_id)

    time_period = request.data.get("time_period") or request.query_params.get("time_period")
    report_from_date = parse_date(request.data.get("report_from_date") or request.query_params.get("report_from_date"))
    report_to_date = parse_date(request.data.get("report_to_date") or request.query_params.get("report_to_date"))

    today = timezone.now().date()
    qs = Client.objects.exclude(dob=None)

    # Normalize to upcoming birthday (ignore year)
    data = []
    for c in qs:
        if not c.dob:
            continue
        # Construct birthday for this year
        birthday_this_year = date(today.year, c.dob.month, c.dob.day)
        if birthday_this_year < today:
            birthday_this_year = date(today.year + 1, c.dob.month, c.dob.day)

        remaining_days = (birthday_this_year - today).days

        include = False
        if time_period:
            tp = str(time_period).strip().lower()
            if tp == "1week" and remaining_days <= 7:
                include = True
            elif tp == "2weeks" and remaining_days <= 14:
                include = True
            elif tp == "3weeks" and remaining_days <= 21:
                include = True
            elif tp == "1month" and remaining_days <= 30:
                include = True
            elif tp == "3months" and remaining_days <= 90:
                include = True
            elif tp == "6months" and remaining_days <= 180:
                include = True
            elif tp == "1year" and remaining_days <= 365:
                include = True
            elif tp == "custom" and report_from_date and report_to_date:
                include = report_from_date <= birthday_this_year <= report_to_date

        if include:
            data.append({
                "client_id": c.client_id,
                "client_name": f"{c.client_first_name or ''} {c.client_last_name or ''}".strip(),
                "dob": c.dob,
                "upcoming_birthday": birthday_this_year,
                "remaining_days": remaining_days,
                "email": c.email,
                "contact_no": c.contact_no,
            })

    return Response({
        "success": True,
        "message": "" if data else "No birthdays found for the given criteria",
        "data": sorted(data, key=lambda x: x["remaining_days"]),
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)
'''

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def birth_report(request):
    menu_id = 9
    perms = get_menu_permissions(request.user, menu_id)

    # --- Input ---
    time_period = (
        request.data.get("timePeriod")
        or request.data.get("time_period")
        or request.query_params.get("time_period")
    )
    start_date = (
        request.data.get("startDate")
        or request.data.get("report_from_date")
        or request.query_params.get("report_from_date")
    )
    end_date = (
        request.data.get("endDate")
        or request.data.get("report_to_date")
        or request.query_params.get("report_to_date")
    )

    today = timezone.now().date()
    qs = Client.objects.exclude(dob__isnull=True)

    # --- Initialize filters ---
    start_filter = today
    end_filter = today + timedelta(days=365)  # default: next 1 year
    custom_range = False
    start_tuple = None
    end_tuple = None

    try:
        if time_period:
            tp = str(time_period).strip().lower().replace(" ", "_")

            if tp == "today":
                end_filter = today

            elif tp == "1week":
                end_filter = today + timedelta(days=7)
            elif tp == "2weeks":
                end_filter = today + timedelta(days=14)
            elif tp == "3weeks":
                end_filter = today + timedelta(days=21)
            elif tp == "1month":
                end_filter = today + timedelta(days=30)
            elif tp == "3months":
                end_filter = today + timedelta(days=90)
            elif tp == "6months":
                end_filter = today + timedelta(days=180)
            elif tp == "1year":
                end_filter = today + timedelta(days=365)

            elif tp == "custom" and start_date and end_date:
                try:
        # Parse as full date format
                    start_dt = datetime.strptime(start_date, "%Y-%m-%d").date()
                    end_dt = datetime.strptime(end_date, "%Y-%m-%d").date()

                    # Now create a date in the current year (or next year if already passed)
                    # start_filter = start_dt.replace(year=today.year)
                    # if start_filter < today:
                    #     start_filter = start_filter.replace(year=today.year + 1)

                    # start_filter = (start_dt.month, start_dt.day)
                    # end_filter = (end_dt.month, end_dt.day)

                    
                    start_tuple = (start_dt.month, start_dt.day)
                    end_tuple = (end_dt.month, end_dt.day)
                    custom_range = True

                except ValueError:
                # fallback if date parsing fails
                    start_tuple = (today.month, today.day)
                    end_tuple = ((today + timedelta(days=365)).month, (today + timedelta(days=365)).day)
                    custom_range = False    
        else:
            custom_range = False


    except Exception:
        # fallback safety
        start_filter = today
        end_filter = today + timedelta(days=365)

    # --- Build response ---
    data = []
    for c in qs:
        
        birth_month = c.dob.month
        birth_day = c.dob.day
        dob_md = (birth_month, birth_day)


        if custom_range and start_tuple  and end_tuple :
            if start_tuple <= end_tuple:
                # Normal range (e.g., Mar 1 to Sep 30)
                in_range = start_tuple <= dob_md <= end_tuple
            else:
                # Wrap-around range (e.g., Nov 1 to Feb 15)
                in_range = dob_md >= start_tuple or dob_md <= end_tuple
        else:
            # Default mode: look ahead for next birthday
            dob_next = date(today.year, birth_month, birth_day)
            if dob_next < today:
                dob_next = date(today.year + 1, birth_month, birth_day)
            in_range = start_filter  <= dob_next <= end_filter

        if in_range:
            # Calculate upcoming birthday for remaining_days
            dob_upcoming = date(today.year, birth_month, birth_day)
            if dob_upcoming < today:
                dob_upcoming = date(today.year + 1, birth_month, birth_day)

            remaining_days = (dob_upcoming - today).days
            data.append({
                "client_id": c.client_id,
                "client_name": f"{c.client_first_name or ''} {c.client_last_name or ''}".strip(),
                "dob": c.dob,
                "email": c.email,
                "contact_no": c.contact_no,
                "remaining_days": remaining_days,
            })

    return Response({
        "success": True,
        "message": "" if data else "No upcoming birthdays found for the given criteria",
        "data": data,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)


'''
@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def anniversary_report(request):
    menu_id = 10
    perms = get_menu_permissions(request.user, menu_id)

    time_period = request.data.get("time_period") or request.query_params.get("time_period")
    report_from_date = parse_date(request.data.get("report_from_date") or request.query_params.get("report_from_date"))
    report_to_date = parse_date(request.data.get("report_to_date") or request.query_params.get("report_to_date"))

    today = timezone.now().date()
    qs = Client.objects.exclude(anniversary_date=None)

    data = []
    for c in qs:
        if not c.anniversary_date:
            continue

        anniversary_this_year = date(today.year, c.anniversary_date.month, c.anniversary_date.day)
        if anniversary_this_year < today:
            anniversary_this_year = date(today.year + 1, c.anniversary_date.month, c.anniversary_date.day)

        remaining_days = (anniversary_this_year - today).days

        include = False
        if time_period:
            tp = str(time_period).strip().lower()
            if tp == "today" and remaining_days == 0:
                include = True
            elif tp == "week" and remaining_days <= 7:
                include = True
            elif tp == "month" and anniversary_this_year.month == today.month:
                include = True
            elif tp == "3months" and remaining_days <= 90:
                include = True
            elif tp == "custom" and report_from_date and report_to_date:
                include = report_from_date <= anniversary_this_year <= report_to_date

        if include:
            data.append({
                "client_id": c.client_id,
                "client_name": f"{c.client_first_name or ''} {c.client_last_name or ''}".strip(),
                "anniversary_date": c.anniversary_date,
                "upcoming_anniversary": anniversary_this_year,
                "remaining_days": remaining_days,
            })

    return Response({
        "success": True,
        "message": "" if data else "No anniversaries found for the given criteria",
        "data": sorted(data, key=lambda x: x["remaining_days"]),
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)
'''


@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def anniversary_report(request):
    menu_id = 10
    perms = get_menu_permissions(request.user, menu_id)

    # --- Input ---
    time_period = (
        request.data.get("timePeriod")
        or request.data.get("time_period")
        or request.query_params.get("time_period")
    )
    start_date = (
        request.data.get("startDate")
        or request.data.get("report_from_date")
        or request.query_params.get("report_from_date")
    )
    end_date = (
        request.data.get("endDate")
        or request.data.get("report_to_date")
        or request.query_params.get("report_to_date")
    )

    today = timezone.now().date()
    qs = Client.objects.exclude(anniversary_date__isnull=True)


    # --- Initialize filters ---
    start_filter = today
    end_filter = today + timedelta(days=365)  # default: next 1 year
    custom_range = False
    start_tuple = None
    end_tuple = None

    try:
        if time_period:
            tp = str(time_period).strip().lower().replace(" ", "_")

            if tp == "today":
                end_filter = today

            elif tp == "1week":
                end_filter = today + timedelta(days=7)
            elif tp == "2weeks":
                end_filter = today + timedelta(days=14)
            elif tp == "3weeks":
                end_filter = today + timedelta(days=21)
            elif tp == "1month":
                end_filter = today + timedelta(days=30)
            elif tp == "3months":
                end_filter = today + timedelta(days=90)
            elif tp == "6months":
                end_filter = today + timedelta(days=180)
            elif tp == "1year":
                end_filter = today + timedelta(days=365)

            elif tp == "custom" and start_date and end_date:
                try:
        # Parse as full date format
                    start_dt = datetime.strptime(start_date, "%Y-%m-%d").date()
                    end_dt = datetime.strptime(end_date, "%Y-%m-%d").date()

                    # Now create a date in the current year (or next year if already passed)
                    # start_filter = start_dt.replace(year=today.year)
                    # if start_filter < today:
                    #     start_filter = start_filter.replace(year=today.year + 1)

                    # start_filter = (start_dt.month, start_dt.day)
                    # end_filter = (end_dt.month, end_dt.day)

                    
                    start_tuple = (start_dt.month, start_dt.day)
                    end_tuple = (end_dt.month, end_dt.day)
                    custom_range = True

                except ValueError:
                # fallback if date parsing fails
                    start_tuple = (today.month, today.day)
                    end_tuple = ((today + timedelta(days=365)).month, (today + timedelta(days=365)).day)
                    custom_range = False    
        else:
            custom_range = False


    except Exception:
        # fallback safety
        start_filter = today
        end_filter = today + timedelta(days=365)

    # --- Build response ---
    data = []
    for c in qs:
        if not c.anniversary_date:
            continue  # Skip if anniversary_date is None (extra safety)

        anni_month = c.anniversary_date.month
        anni_day = c.anniversary_date.day
        anni_md = (anni_month, anni_day)

        in_range = False


        if custom_range and start_tuple  and end_tuple :
            if start_tuple <= end_tuple:
                # Normal range (e.g., Mar 1 to Sep 30)
                in_range = start_tuple <= anni_md <= end_tuple
            else:
                # Wrap-around range (e.g., Nov 1 to Feb 15)
                in_range = anni_md >= start_tuple or anni_md <= end_tuple
        else:
            # Default mode: look ahead for next birthday
            dob_next = date(today.year, anni_month, anni_day)
            if dob_next < today:
                dob_next = date(today.year + 1, anni_month, anni_day)
            in_range = start_filter <= dob_next <= end_filter

        if in_range:
            # Calculate upcoming birthday for remaining_days
            dob_upcoming = date(today.year, anni_month, anni_day)
            if dob_upcoming < today:
                dob_upcoming = date(today.year + 1, anni_month, anni_day)

            remaining_days = (dob_upcoming - today).days
            data.append({
                "client_id": c.client_id,
                "client_name": f"{c.client_first_name or ''} {c.client_last_name or ''}".strip(),
                "anniversary_date": c.anniversary_date,
                "email": c.email,
                "contact_no": c.contact_no,
                "remaining_days": remaining_days,
            })

    return Response({
        "success": True,
        "message": "" if data else "No upcoming anniversaries found for the given criteria",
        "data": data,
        "permissions": {
            "can_view": perms.can_view,
            "can_add": perms.can_add,
            "can_edit": perms.can_edit,
            "can_delete": perms.can_delete,
            "can_export": perms.can_export,
        } if perms else {}
    }, status=status.HTTP_200_OK)


# @login_required
# @api_view(['POST'])
'''
@permission_classes([IsAuthenticated])
def report_client(request):
    company_data = Company.objects.all()
    reference_data = Client.objects.values("client_id", "client_first_name", "client_last_name")
    country_data = Countries.objects.all()

    return render(request, "home/reports/client/index.html", {
        "company_data": company_data,
        "reference_data": reference_data,
        "country_data": country_data,
    })
'''


from datetime import datetime

@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def report_client(request):
    menu_id = 11
    perms = get_menu_permissions(request.user, menu_id)

    params = request.query_params if request.method == "GET" else request.data

    # Normalize ("" → None)
    def normalize(val):
        return val if val not in [None, "", "null", "undefined"] else None
    
    def safe_parse_date(val):
        try:
            if not val or val.strip().lower() in ["", "null", "undefined"]:
                return None
            return datetime.strptime(val.strip(), "%Y-%m-%d").date()
        except Exception as e:
            print(f"❌ Date parse failed for value '{val}': {e}")
            return None

    start_date = normalize(params.get("start_date"))
    end_date = normalize(params.get("end_date"))
    company_id = normalize(params.get("company_id"))
    reference_from = normalize(params.get("reference_from"))
    reference_id = normalize(params.get("reference_id")) 
    reference_remark = normalize(params.get("reference_remark")) 
    status_filter = normalize(params.get("status_filter")) 
    country_id = normalize(params.get("country_id"))   
    state_id = normalize(params.get("state_id"))       
    city_id = normalize(params.get("city_id"))         


    start_date = safe_parse_date(start_date) if start_date else None
    end_date = safe_parse_date(end_date) if end_date else None

    queryset = Client.objects.all()
    
    

    if start_date and end_date:
        queryset = queryset.filter(created_at__range=(datetime.combine(start_date, time.min), datetime.combine(end_date, time.min)))
  
    elif start_date:
        queryset = queryset.filter(created_at__gte=datetime.combine(start_date, time.min))
        
    elif end_date:
        queryset = queryset.filter(created_at__lte=datetime.combine(end_date, time.min))
        
    
    if company_id:
        queryset = queryset.filter(client_companies__ref_company_id=company_id)


    if reference_from:
        queryset = queryset.filter(reference_from__icontains=reference_from)

    if reference_id:
        queryset = queryset.filter(ref_client_id=reference_id)

    if reference_remark:
        queryset = queryset.filter(reference_remark__icontains=reference_remark)

    if status_filter:
        queryset = queryset.filter(client_status=status_filter)

    if country_id:
        queryset = queryset.filter(residential_country_id=country_id)

    if state_id:
        queryset = queryset.filter(residential_state_id=state_id)

    if city_id:
        queryset = queryset.filter(residential_city_id=city_id)

    queryset = queryset.distinct()

    # Serialize results
    data = []
    for client in queryset:
        full_name = " ".join(filter(None, [
            client.client_first_name,
            client.client_middle_name,
            client.client_last_name
        ]))
        companies = [
            {
                "company_name": assoc.ref_company.company_name if assoc.ref_company else "",
                "designation": assoc.designation,
                "primary_company": assoc.primary_company,
            } for assoc in client.client_companies.all()
        ]
        data.append({
            "id": client.client_id,
            "client_name": full_name,
            "company": ", ".join([c["company_name"] for c in companies]),
            "reference": " ".join(filter(None, [
                client.ref_client.client_first_name if client.ref_client else "",
                client.ref_client.client_middle_name if client.ref_client else "",
                client.ref_client.client_last_name if client.ref_client else ""
            ])),
            "reference_from": reference_from,
            "reference_remark": reference_remark,
            "status": client.client_status,
            "country": str(client.residential_country) if client.residential_country else "",
            "state": str(client.residential_state) if client.residential_state else "",
            "city": str(client.residential_city) if client.residential_city else "",
            "created_at": client.created_at,
        })

    return Response({
        "success": True,
        "message": "",
        "data": data,
        "permissions": {
            "can_view": getattr(perms, "can_view", False),
            "can_add": getattr(perms, "can_add", False),
            "can_edit": getattr(perms, "can_edit", False),
            "can_delete": getattr(perms, "can_delete", False),
            "can_export": getattr(perms, "can_export", False),
        } if perms else {}
    }, status=status.HTTP_200_OK)




@api_view(["GET"])
@permission_classes([IsAuthenticated])
def list_email_setups(request):
    setups = EmailSetup.objects.prefetch_related("emails").all()
    serializer = EmailSetupSerializer(setups, many=True)
    return Response({"success": True, "data": serializer.data})


@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def email_setup(request):
    report_type = request.query_params.get("report_type") if request.method == "GET" else request.data.get("report_type")

    if not report_type:
        return Response(
            {"success": False, "message": "report_type is required"},
            status=status.HTTP_400_BAD_REQUEST
        )

    setup, created = EmailSetup.objects.get_or_create(report_type=report_type)

    if request.method == "GET":
        return Response({
            "success": True,
            "report_type": setup.report_type,
            "email": setup.email
        }, status=status.HTTP_200_OK)

    elif request.method == "POST":
        email = request.data.get("email")
        if not email:
            return Response(
                {"success": False, "message": "Email is required"},
                status=status.HTTP_400_BAD_REQUEST
            )

        setup.email = email
        setup.save()

        return Response({
            "success": True,
            "report_type": setup.report_type,
            "email": setup.email
        }, status=status.HTTP_201_CREATED)
    

'''
@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def email_setup(request):
    report_type = request.query_params.get("report_type") if request.method == "GET" else request.data.get("report_type")

    if not report_type:
        return Response({"success": False, "message": "report_type is required"}, status=status.HTTP_400_BAD_REQUEST)

    try:
        setup = EmailSetup.objects.get(report_type=report_type)
    except EmailSetup.DoesNotExist:
        setup = EmailSetup.objects.create(report_type=report_type)

    # GET - fetch single email
    if request.method == "GET":
        email_obj = setup.emails.first()
        return Response({
            "success": True,
            "email": email_obj.email if email_obj else None
        })

    # POST - insert or replace one email
    elif request.method == "POST":
        email = request.data.get("email")
        if not email:
            return Response({"success": False, "message": "Email is required"}, status=status.HTTP_400_BAD_REQUEST)

        # If already has email, update it
        email_obj = setup.emails.first()
        if email_obj:
            email_obj.email = email
            email_obj.save()
        else:
            email_obj = ReportEmail.objects.create(setup=setup, email=email)

        return Response({
            "success": True,
            "email": email_obj.email
        }, status=status.HTTP_201_CREATED)
'''
    
'''
@api_view(["PUT"])
@permission_classes([IsAuthenticated])
def email_setup_update(request, setup_id):
    """
    PUT: Update emails for an EmailSetup using its ID.
    URL: /email-setups/update/<int:setup_id>/
    Payload:
    {
        "emails": ["new1@example.com", "new2@example.com"]   # replace all emails
    }
    """
    emails = request.data.get("emails")

    if not emails or not isinstance(emails, list):
        return Response({"success": False, "message": "emails list is required"}, status=400)

    # Fetch existing setup by ID
    try:
        setup = EmailSetup.objects.get(id=setup_id)
    except EmailSetup.DoesNotExist:
        return Response({"success": False, "message": "EmailSetup not found"}, status=404)

    # Delete all existing emails
    ReportEmail.objects.filter(setup=setup).delete()

    # Add new emails
    for email in emails:
        ReportEmail.objects.create(setup=setup, email=email)

    # Return full setup with updated emails
    serializer = EmailSetupSerializer(setup)
    return Response({"success": True, "data": serializer.data})
'''

'''
@api_view(["DELETE"])
@permission_classes([IsAuthenticated])
def delete_email_from_setup(request, email_id):
    try:
        email = ReportEmail.objects.get(id=email_id)
    except ReportEmail.DoesNotExist:
        return Response({"success": False, "message": "Email not found"}, status=404)

    email.delete()
    return Response({"success": True, "message": "Email deleted"})
'''


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def trigger_anniversary_report(request):
    call_command("send_anniversary_email")
    return Response({"success": True, "message": "Anniversary email triggered"}, status=status.HTTP_200_OK)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def trigger_birthday_report(request):
    """
    Trigger the birthday report email by calling the management command.
    """
    try:
        call_command("send_birthday_email")
        return Response({"success": True, "message": "Birthday email triggered"}, status=status.HTTP_200_OK)
    except Exception as e:
        return Response({"success": False, "message": f"Failed to trigger birthday email: {e}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
    

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def trigger_passport_expiry_report(request):
    """
    Trigger the passport expiry email report via management command
    """
    try:
        call_command("send_passport_expiry_email")  # Name of your management command
        return Response({
            "success": True,
            "message": "Passport expiry email report triggered successfully."
        }, status=status.HTTP_200_OK)
    except Exception as e:
        return Response({
            "success": False,
            "message": f"Failed to trigger passport expiry report: {str(e)}"
        }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
    

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def trigger_travel_insurance_report(request):
    """
    Trigger the travel insurance expiry email report via management command
    """
    try:
        # Call the management command
        call_command("send_travel_insurance_expiry_email")  # Name of your command file
        return Response({
            "success": True,
            "message": "Travel insurance expiry email report triggered successfully."
        }, status=status.HTTP_200_OK)
    except Exception as e:
        return Response({
            "success": False,
            "message": f"Failed to trigger travel insurance expiry report: {str(e)}"
        }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
    

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def trigger_visa_expiry_report(request):
    try:
        call_command("send_visa_expiry_email")  # name of your command file
        return Response({
            "success": True,
            "message": "Visa expiry email report triggered successfully."
        }, status=status.HTTP_200_OK)
    except Exception as e:
        return Response({
            "success": False,
            "message": f"Failed to trigger visa expiry report: {str(e)}"
        }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


def error_404_view(request):
    return render(request, 'Views/errors/html/error_404.html', status=404)

def error_400_view(request):
    return render(request, 'Views/errors/html/error_400.html', status=400)