from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db.models import Count, Q, Sum
from customers.models import Customer, CustomerFieldMeta, CustomerRegField, CustomerTypeField
from datetime import datetime, date
from ledgers.models import SystemTransactions, OrganisationSubAccount
from savings.models import SavingAccount
from loans.models import LoanApplication

from organisations.serializers import *
from organisations.models import * 
from users.models import * 
from users.helper import * 
from rest_framework.filters import SearchFilter, OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.response import Response
from ledgers.ledgers_helper import populate_system_generalted_charts_of_accounts, update_branch_interbranch_legder
from questbanker_api.utils import get_current_user
from rest_framework import status
from customers.helper import populate_system_customer_fields
import json
from django.shortcuts import get_object_or_404
from integrations.helper import flexi_pay_sacco_onboarding
import threading
from django.db.models import Case, When, Value, IntegerField, F, DecimalField
from django.core.cache import cache
from django.utils import timezone


from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import CreateModelMixin,RetrieveModelMixin, ListModelMixin
from rest_framework.decorators import action
# from rest_framework.mixins import RetrieveModelMixin, ListModelMixin
# from rest_framework.response import Response
# from rest_framework import status


class OrganisationView(viewsets.ModelViewSet):
    queryset = Organisation.objects.all().order_by('-id')
    serializer_class = OrganisationSerializer
    filter_backends = (SearchFilter, OrderingFilter, DjangoFilterBackend, )
    filterset_fields = ['name', 'organisation_type', 'admin_organisation' ]
    search_fields = ('name','registration_no' )
    ordering_fields = ['name', ]

    @staticmethod
    def _coerce_bool(value):
        if isinstance(value, str):
            return value.strip().lower() in ('1', 'true', 'yes', 'on')
        return bool(value)

    def perform_create(self, serializer):
        organisation_type_id = self.request.data.get('organisation_type')
        #Save Organisation.
        organisation = serializer.save(organisation_added_by=self.request.user.id, organisation_type_id=organisation_type_id)
        fields_to_update = []
        if 'is_crb_active' in self.request.data:
            organisation.is_crb_active = self._coerce_bool(self.request.data.get('is_crb_active'))
            fields_to_update.append('is_crb_active')
        if 'is_mobile_app_active' in self.request.data:
            organisation.is_mobile_app_active = self._coerce_bool(self.request.data.get('is_mobile_app_active'))
            fields_to_update.append('is_mobile_app_active')
        if fields_to_update:
            organisation.save(update_fields=fields_to_update)
        #Create Organisation admin role
        user_role_field = {"role_name":"System Administrator","role_desc":"System Administrator","role_added_by":self.request.user.id,"role_org":organisation,"is_active":True}
        user_role       = UserRole.objects.create(**user_role_field)
        # Assign new organisation components
        components = OrgTypeComponent.objects.filter(comp_org_type__id=organisation_type_id).all()
        for component in components:
            if component.is_active:
                organisation_component_field = {"component_org":organisation,"system_component":component.system_component,"is_active":True}
                organisation_component       = OrganisationComponent.objects.create(**organisation_component_field)
                if organisation_component and user_role:
                    #Assign previlleges to admin role
                    role_component_field={"user_role":user_role,"role_component_added_by":self.request.user.id,"org_component":organisation_component,"is_active":True}
                    RoleComponent.objects.create(**role_component_field)
        #Create Head Office Branch for Organisation.
        branch = OrganisationBranch(branch_organisation=organisation, name="Head Office", short_name="HQ", status="active", added_by=self.request.user.id)
        branch.save()

        # Save organisational sms unit cost
        unit_sms_cost_field = {
           "setting_key":"unit_sms_cost",
           "setting_value":self.request.data.get('unit_sms_cost'),
           "org_setting":organisation,
           "setting_added_by":self.request.user.id
        }
        OrganisationSetting.objects.create(**unit_sms_cost_field)
        OrganisationSetting.objects.create(
            setting_key="aml_deposit_threshold",
            setting_value=self.request.data.get('aml_deposit_threshold') or 0,
            org_setting=organisation,
            setting_added_by=self.request.user.id,
        )

        #Initialise system generated COAs.
        populate_system_generalted_charts_of_accounts(organisation, self.request.user.id)

        # populate organisation default customer type and fields
        populate_system_customer_fields(branch, self.request.user)
        
    def perform_update(self, serializer):
        org_type_id = self.request.data.get('organisation_type')
        if org_type_id:
            try:
                org_type = OrganisationType.objects.get(id=org_type_id)
                serializer.save(organisation_type=org_type)
            except OrganisationType.DoesNotExist:
                serializer.save()
        else:
            serializer.save()
        saved_organisastion = serializer.instance
        fields_to_update = []
        if 'is_crb_active' in self.request.data:
            saved_organisastion.is_crb_active = self._coerce_bool(self.request.data.get('is_crb_active'))
            fields_to_update.append('is_crb_active')
        if 'is_mobile_app_active' in self.request.data:
            saved_organisastion.is_mobile_app_active = self._coerce_bool(self.request.data.get('is_mobile_app_active'))
            fields_to_update.append('is_mobile_app_active')
        if fields_to_update:
            saved_organisastion.save(update_fields=fields_to_update)
        # Update organisational sms unit cost
        unit_sms_cost_obj = OrganisationSetting.objects.filter(org_setting=saved_organisastion,setting_key='unit_sms_cost').first()
        if unit_sms_cost_obj:
            unit_sms_cost_obj.setting_value = self.request.data.get('unit_sms_cost')
            unit_sms_cost_obj.save()
        else:
            unit_sms_cost_field = {
                "setting_key":"unit_sms_cost",
                "setting_value":self.request.data.get('unit_sms_cost'),
                "org_setting":saved_organisastion,
                "setting_added_by":self.request.user.id
            }
            OrganisationSetting.objects.create(**unit_sms_cost_field)
        is_trained = self.request.data.get('is_trained', False)
        organisation_type = getattr(saved_organisastion.organisation_type, 'org_type', '').lower()
        if is_trained and 'finwise' in organisation_type:
            from customers.models import Customer  # adjust path to your actual model
            Customer.objects.filter(organisation=saved_organisastion).update(
                is_trained=True,
                training_date=saved_organisastion.training_date or timezone.now().date()
            )

    @action(detail=True, methods=['post'])
    def set_crb_active(self, request, pk=None):
        """Activate or deactivate CRB for an organisation.

        Frontend contract:
        - Endpoint: POST /organisations/{id}/set_crb_active/
        - Body: { "is_crb_active": true } or { "is_crb_active": false }
        - Requires same permissions as updating organisation details.
        """
        organisation = self.get_object()
        # enforce same object permissions as update
        self.check_object_permissions(request, organisation)

        val = request.data.get('is_crb_active')
        if isinstance(val, str):
            val = val.lower() in ('1', 'true', 'yes')

        organisation.is_crb_active = bool(val)
        organisation.save()

        return Response({
            'id': organisation.id,
            'is_crb_active': organisation.is_crb_active,
            'message': 'CRB status updated'
        }, status=status.HTTP_200_OK)

    @action(detail=True, methods=['post'])
    def set_mobile_app_active(self, request, pk=None):
        """Activate or deactivate mobile app access for an organisation.

        Frontend contract:
        - Endpoint: POST /organisations/{id}/set_mobile_app_active/
        - Body: { "is_mobile_app_active": true } or { "is_mobile_app_active": false }
        - Requires same permissions as updating organisation details.
        """
        organisation = self.get_object()
        # enforce same object permissions as update
        self.check_object_permissions(request, organisation)

        val = request.data.get('is_mobile_app_active')
        if isinstance(val, str):
            val = val.lower() in ('1', 'true', 'yes')

        organisation.is_mobile_app_active = bool(val)
        organisation.save()

        return Response({
            'id': organisation.id,
            'is_mobile_app_active': organisation.is_mobile_app_active,
            'message': 'Mobile app status updated'
        }, status=status.HTTP_200_OK)

    # ✅ Optional manual trigger endpoint for later use (frontend can call this)
    @action(detail=True, methods=['post'])
    def mark_members_trained(self, request, pk=None):
        org = self.get_object()
        from customers.models import Customer  # adjust import
        updated = Customer.objects.filter(organisation=org).update(
            is_trained=True,
            training_date=timezone.now().date()
        )
        return Response(
            {"message": f"{updated} members marked as trained"},
            status=status.HTTP_200_OK
        )    


class SystemComponentView(viewsets.ModelViewSet):
    serializer_class = SystemComponentSerializer
    pagination_class = None
    queryset = SystemComponent.objects.all()

class OrganisationComponentView(APIView):
    def get(self, request, format=None):
        data = []
        organisationid = request.GET.get('organisationid', '1')
        modules = OrganisationComponent.objects.filter(component_org=organisationid).all()
        for module in modules:
            data.append(
                {"id":module.id, "system_component":module.system_component.id,
                 "component_org":module.component_org.id,
                 "is_active":module.is_active})
        return Response(data) 
        
    def post(self, request):
        '''
        Add / update orgamisation feature
        '''
        request_data = request.data
        system_component        = SystemComponent.objects.get(id=request_data['componentid'])
        organisation            = Organisation.objects.get(id=request_data['organisationid'])
        organisation_component  = OrganisationComponent.objects.filter(component_org=organisation,system_component=system_component).first()
        if organisation_component:
            organisation_component.is_active = request_data['is_active']
            organisation_component.save()

            data = self.get_children_component_ids(organisation_component.system_component.id)

            if(len(data) > 0):
                for id in data:
                    result  = OrganisationComponent.objects.filter(component_org=organisation,system_component=SystemComponent.objects.get(id=id))
                    if result:
                        child_component = result[0]
                        child_component.is_active = organisation_component.is_active
                        child_component.save()
                    else:
                        OrganisationComponent.objects.create(
                            component_org    = organisation,
                            system_component = SystemComponent.objects.get(id=id),
                            is_active        = organisation_component.is_active,
                        )
                          
            self.saveParentComponent(organisation_component)
            return Response({"message":"updated successfully"}, status=status.HTTP_200_OK)
        else:
            organisation_component_field={"component_org":organisation,"system_component":system_component,"is_active":True}
            organisation_component = OrganisationComponent.objects.create(**organisation_component_field)
            if organisation_component:
                data = self.get_children_component_ids(organisation_component.system_component.id)

                if(len(data) > 0):
                    for id in data:
                        result  = OrganisationComponent.objects.filter(component_org=organisation,system_component=SystemComponent.objects.get(id=id))
                        if result:
                            child_component = result[0]
                            child_component.is_active = organisation_component.is_active
                            child_component.save()
                        else:
                            OrganisationComponent.objects.create(
                                component_org    = organisation,
                                system_component = SystemComponent.objects.get(id=id),
                                is_active        = organisation_component.is_active,
                            )
                          
                self.saveParentComponent(organisation_component)
                return Response({"message":"Added successfully"}, status=status.HTTP_200_OK)
        return Response({"message":"error occurred"}, status=status.HTTP_400_BAD_REQUEST)
    
    def saveParentComponent(self,organisation_component):
        if organisation_component.system_component.parent_component > 0:
            if organisation_component.is_active:
                result  = OrganisationComponent.objects.filter(component_org=organisation_component.component_org,system_component=organisation_component.system_component.parent_component)
                if result:
                    parent_component = result[0]
                    parent_component.is_active = True
                    parent_component.save()
                    if parent_component:
                        self.saveParentComponent(parent_component)
                else:
                    organisation_component_field={"component_org":organisation_component.component_org,"system_component":SystemComponent.objects.get(id=organisation_component.system_component.parent_component),"is_active":True}
                    parent_component = OrganisationComponent.objects.create(**organisation_component_field)
                    if parent_component:
                        self.saveParentComponent(parent_component)
            if not organisation_component.is_active:
                    result  = OrganisationComponent.objects.filter(component_org=organisation_component.component_org,system_component=organisation_component.system_component.parent_component)
                    if result:
                        parent_component = result[0]
                        children  = OrganisationComponent.objects.filter(component_org=organisation_component.component_org,system_component__parent_component=parent_component.system_component.id,is_active=True).all()
                        if len(children) < 1:
                            parent_component.is_active = False
                            parent_component.save()
                            if parent_component:
                                self.saveParentComponent(parent_component)

    def get_children_component_ids(self,parent_id):
        data = []
        modules = SystemComponent.objects.filter(parent_component=parent_id).all()
        if(len(modules) > 0):
            for module in modules:
                data.append(module.id)
                children_modules = self.get_children_component_ids(module.id)
                if(len(children_modules) > 0):
                    for child_module_id in children_modules:
                        data.append(child_module_id)
                        children_modules2 = self.get_children_component_ids(child_module_id)
                        if(len(children_modules2) > 0):
                            for child_module_id2 in children_modules2:
                                data.append(child_module_id2)
        return data   

class OrgTypeComponentView(APIView):
    def get(self, request, format=None):
        data = []
        org_type_id = request.GET.get('comp_org_type', '1')
        modules = OrgTypeComponent.objects.filter(comp_org_type__id=org_type_id).all()
        for module in modules:
            data.append(
                {"id":module.id, "system_component":module.system_component.id,
                 "comp_org_type":module.comp_org_type.id,
                 "is_active":module.is_active})
        return Response(data) 
    def post(self, request):
        '''
        Add / update orgamisation feature
        '''
        request_data = request.data
        system_component = SystemComponent.objects.get(id=request_data['componentid'])
        org_type         = OrganisationType.objects.get(id=request_data['comp_org_type'])
        result           = OrgTypeComponent.objects.filter(comp_org_type=org_type,system_component=system_component)
        if result:
            org_type_comp = result[0]
            org_type_comp.is_active = request_data['is_active']
            org_type_comp.save()
            if org_type_comp:
                data = self.get_children_component_ids(org_type_comp.system_component.id)
                if(len(data) > 0):
                    for id in data:
                        result  = OrgTypeComponent.objects.filter(comp_org_type=org_type_comp.comp_org_type,system_component=SystemComponent.objects.get(id=id))
                        if result:
                            child_component = result[0]
                            child_component.is_active = org_type_comp.is_active
                            child_component.save()
                        else:
                            org_type_comp_field={"comp_org_type":org_type,"system_component":SystemComponent.objects.get(id=id),"is_active": org_type_comp.is_active}
                            OrgTypeComponent.objects.create(**org_type_comp_field)
                self.saveParentComponent(org_type_comp)
                return Response({"message":"updated successfully"}, status=status.HTTP_200_OK)
        else:
            org_type_comp_field={"comp_org_type":org_type,"system_component":system_component,"is_active":True}
            org_type_comp  = OrgTypeComponent.objects.create(**org_type_comp_field)
            if org_type_comp:
                data = self.get_children_component_ids(org_type_comp.system_component.id)
                if(len(data) > 0):
                    for id in data:
                        result  = OrgTypeComponent.objects.filter(comp_org_type=org_type_comp.comp_org_type,system_component=SystemComponent.objects.get(id=id))
                        if result:
                            child_component = result[0]
                            child_component.is_active = org_type_comp.is_active
                            child_component.save()
                        else:
                            org_type_comp_field={"comp_org_type":org_type,"system_component":SystemComponent.objects.get(id=id),"is_active": org_type_comp.is_active}
                            OrgTypeComponent.objects.create(**org_type_comp_field)
                self.saveParentComponent(org_type_comp)
                return Response({"message":"Added successfully"}, status=status.HTTP_200_OK)
        return Response({"message":"error occurred"}, status=status.HTTP_400_BAD_REQUEST)
    
    def saveParentComponent(self,org_type_comp):
        if org_type_comp.system_component.parent_component > 0:
            if org_type_comp.is_active:
                result  = OrgTypeComponent.objects.filter(comp_org_type=org_type_comp.comp_org_type,system_component=org_type_comp.system_component.parent_component)
                if result:
                    parent_component = result[0]
                    parent_component.is_active = True
                    parent_component.save()
                    if parent_component:
                        self.saveParentComponent(parent_component)
                else:
                    org_type_comp_field={"comp_org_type":org_type_comp.comp_org_type,"system_component":SystemComponent.objects.get(id=org_type_comp.system_component.parent_component),"is_active":True}
                    parent_component = OrgTypeComponent.objects.create(**org_type_comp_field)
                    if parent_component:
                        self.saveParentComponent(parent_component)
            if not org_type_comp.is_active:
                result  = OrgTypeComponent.objects.filter(comp_org_type=org_type_comp.comp_org_type,system_component=org_type_comp.system_component.parent_component)
                if result:
                    parent_component = result[0]
                    children  = OrgTypeComponent.objects.filter(system_component__parent_component=parent_component.system_component.id,is_active=True).all()
                    if len(children) < 1:
                        parent_component.is_active = False
                        parent_component.save()
                        if parent_component:
                            self.saveParentComponent(parent_component)

    def get_children_component_ids(self,parent_id):
        data = []
        modules = SystemComponent.objects.filter(parent_component=parent_id).all()
        if(len(modules) > 0):
            for module in modules:
                data.append(module.id)
                children_modules = self.get_children_component_ids(module.id)
                if(len(children_modules) > 0):
                    for child_module in children_modules:
                        data.append(child_module)
                        children_modules2 = self.get_children_component_ids(child_module)
                        if(len(children_modules2) > 0):
                            for child_module2 in children_modules2:
                                data.append(child_module2)
                           
        return data   

class SystemFeatureView(APIView):
    def get(self,request, format=None):
        data = []
        modules = SystemComponent.objects.filter(parent_component=0).all()
        for module in modules:
            children = self.get_children_components(module.id)
            data.append(
                        {"id":module.id, "name":module.component,
                        "key":module.key,
                        "desc":module.component_desc,"children":children,
                        "is_active":module.is_active,
                        "parent_component":module.parent_component,"type":module.type})
           
        return Response(data) 

    def post(self, request):
        '''
        add/update system feature
        '''
        request_data = request.data
        if request_data['action'] == 'ADD':
            component_field={
            "component":request_data['name'],
            "component_desc":request_data['desc'],
            "parent_component":request_data['parent'],
            "key":request_data['key'],
            "type":request_data['type'],
            "is_active":True}
            component = SystemComponent.objects.create(**component_field)
            if component:
                return Response({"message":"Added successfully"}, status=status.HTTP_200_OK)
        if request_data['action'] == 'EDIT':
            system_component           = SystemComponent.objects.get(id=request_data['id'])
            system_component.type      = request_data['type']
            system_component.component = request_data['name']
            system_component.component_desc   = request_data['desc']
            system_component.parent_component = request_data['parent']
            system_component.save()
            if system_component:
                return Response({"message":"updated successfully"}, status=status.HTTP_200_OK)
        if request_data['action'] == 'DELETE':
            system_component = SystemComponent.objects.get(id=request_data['id'])
            if system_component:
               system_component.is_active = False
               system_component.save()
            return Response({"message":"Feature Deleted successfully"}, status=status.HTTP_200_OK)
        if request_data['action'] == 'ACTIVATE':
            system_component = SystemComponent.objects.get(id=request_data['id'])
            if system_component:
                system_component.is_active = request_data['status']
                system_component.save()
                if request_data['status']:
                    return Response({"message":"Feature activated successfully"}, status=status.HTTP_200_OK)
                else:
                    return Response({"message":"Feature deactivated successfully"}, status=status.HTTP_200_OK)
        return Response({"message":"error occurred"}, status=status.HTTP_400_BAD_REQUEST)

    def get_children_components(self,parent_id):
        data = []
        modules = SystemComponent.objects.filter(parent_component=parent_id).all()
        if(len(modules) > 0):
            for module in modules:
                children = self.get_children_components(module.id)
                if(len(children) > 0):
                    children2 = self.get_children_components(module.id)
                    data.append(
                        {"id":module.id, "name":module.component,
                         "key":module.key,
                        "desc":module.component_desc,"children":children2,
                        "is_active":module.is_active,
                        "parent_component":module.parent_component,"type":module.type})
                if(len(children) < 1):
                    data.append(
                        {"id":module.id, "name":module.component,
                         "key":module.key,
                        "desc":module.component_desc,"children":[],
                        "is_active":module.is_active,
                        "parent_component":module.parent_component,"type":module.type})
        return data

class OrganisationFeatureView(APIView):
    def get(self,request):
        data = []
        organisation_id = get_current_user(self.request, 'organisation_id', 1)
        modules = OrganisationFeature.objects.filter(org_id = organisation_id,parent=0,is_active = True,is_feature_active = True).all()
        for module in modules:
            children = self.get_children_components(module.component_id,organisation_id)
            data.append(
                        {"id":module.id, "name":module.component,
                        "key":module.key,
                        "desc":module.desc,"children":children,
                        "parent_component":module.parent,"type":module.type})
        return Response(data) 

    def get_children_components(self,parent_id,organisation_id):
        data = []
        modules = OrganisationFeature.objects.filter(org_id = organisation_id,parent=parent_id,is_active = True,is_feature_active = True).all()
        if(len(modules) > 0):
            for module in modules:
                children = self.get_children_components(module.component_id,organisation_id)
                if(len(children) > 0):
                    children2 = self.get_children_components(module.component_id,organisation_id)
                    data.append(
                        {"id":module.id, "name":module.component,
                         "key":module.key,
                        "desc":module.desc,"children":children2,
                        "parent_component":module.parent,"type":module.type})
                if(len(children) < 1):
                    data.append(
                        {"id":module.id, "name":module.component,
                         "key":module.key,
                        "desc":module.desc,"children":[],
                        "parent_component":module.parent,"type":module.type})
        return data

class OrganisationTypesView(viewsets.ModelViewSet):
    serializer_class = OrganisationTypeSerializer
    queryset = OrganisationType.objects.all()

class UserRoleView(viewsets.ModelViewSet):
    serializer_class = UserRoleSerializer
    def get_queryset(self):
        #role_id = self.request.query_params.get('roleid')
        organisationid = get_current_user(self.request, 'organisation_id', None)
        queryset = UserRole.objects.filter(role_org=organisationid).order_by('-id')
        return queryset

    def perform_create(self, serializer):
        organisationid = get_current_user(self.request, 'organisation_id', None)
        action       = self.request.data.get('action') 
        if action == 'DELETE':
            old_role_id   = self.request.data.get('id') 
            old_role      = UserRole.objects.get(id=old_role_id)
            assignedusers = self.request.data.get('users') 
            for assigneduser in assignedusers:
                        userAssignedRole = UserAssignedRole.objects.filter(assigned_role__id=old_role_id,user_id__id=assigneduser['userId']).first()
                        if userAssignedRole:
                           userAssignedRole.assigned_role = UserRole.objects.get(id=assigneduser['roleId'])
                           userAssignedRole.save()
            role_users = UserAssignedRole.objects.filter(assigned_role__id=old_role_id)
            if not role_users:
                old_role.delete()
        else:
            #Save role.
            features  = self.request.data.get('features') 
            role_org  = Organisation.objects.get(id=organisationid)
            user_role = serializer.save(role_added_by=self.request.user.id, role_org=role_org,is_active =True)
            #Save role features
            if user_role:
                if len(features) > 0:
                    for feature_id in features:
                        feature = OrganisationComponent.objects.get(id=feature_id)
                        role_component_field={"user_role":user_role,"role_component_added_by":self.request.user.id,"org_component":feature,"is_active":True}
                        RoleComponent.objects.create(**role_component_field)
        
class RoleComponentView(viewsets.ModelViewSet):
    serializer_class = RoleComponentSerializer
    def get_queryset(self):
        role_id  = self.request.query_params.get('roleid')
        queryset = RoleComponent.objects.filter(user_role=role_id)
        return queryset
        
    def perform_create(self, serializer):
        roleid    = self.request.data.get('roleid') 
        featureid = self.request.data.get('featureid')
        is_active = self.request.data.get('is_active')
        feature   = OrganisationComponent.objects.get(id=featureid)
        user_role = UserRole.objects.get(id=roleid)
        role_component = RoleComponent.objects.filter(user_role=user_role,org_component=feature).first()
        
        if role_component:
            role_component.is_active = is_active
            role_component.role_component_added_by = self.request.user.id
            role_component.save()
        else:
            role_component = serializer.save(user_role=user_role,org_component=feature,role_component_added_by=self.request.user.id,is_active = is_active)
        

        if role_component:
            data = self.get_children_component_ids(role_component.org_component.system_component.id)

            if(len(data) > 0):
                for id in data:
                    org_results  = OrganisationComponent.objects.filter(component_org=role_component.org_component.component_org,system_component=SystemComponent.objects.get(id=id))
                    if org_results:
                        for org_result in org_results:
                            comp_results    = RoleComponent.objects.filter(user_role=roleid,org_component=org_result)
                            if comp_results:
                                for comp_result in comp_results:
                                    comp_result.is_active = is_active
                                    comp_result.save()
                            else:
                                RoleComponent.objects.create(user_role=user_role,org_component=org_result,is_active=is_active,role_component_added_by = self.request.user.id)
            self.saveParentRole(role_component) 
                     
        
       
    def saveParentRole(self,role_component):
        if role_component.org_component.system_component.parent_component > 0:
            if role_component.is_active:
                component_org    = role_component.org_component.component_org
                parent_component = role_component.org_component.system_component.parent_component
                org_comps  = OrganisationComponent.objects.filter(component_org=component_org,system_component=parent_component)
                if org_comps:
                    org_comp = org_comps[0]
                    result    = RoleComponent.objects.filter(user_role=role_component.user_role,org_component=org_comp)
                    if result:
                        parent_role = result[0]
                        parent_role.is_active = True
                        parent_role.save()
                        if parent_role:
                            self.saveParentRole(parent_role)
                    else:
                        role_component_field={"user_role":role_component.user_role,"role_component_added_by":self.request.user.id,"org_component":org_comp,"is_active":True}
                        parent_role = RoleComponent.objects.create(**role_component_field)
                        if parent_role:
                            self.saveParentRole(parent_role)

            if not role_component.is_active:
                component_org    = role_component.org_component.component_org
                parent_component = role_component.org_component.system_component.parent_component
                org_comps  = OrganisationComponent.objects.filter(component_org=component_org,system_component=parent_component)
                if org_comps:
                    org_comp = org_comps[0]
                    result   = RoleComponent.objects.filter(user_role=role_component.user_role,org_component=org_comp)
                    if result:
                        parent_role = result[0]
                        children  = RoleComponent.objects.filter(user_role=parent_role.user_role,org_component__system_component__parent_component=parent_role.org_component.system_component.id,is_active=True).all()
                        if len(children) < 1:
                            parent_role.is_active = False
                            parent_role.save()
                            if parent_role:
                                self.saveParentRole(parent_role)

    def get_children_component_ids(self,parent_id):
        data = []
        modules = SystemComponent.objects.filter(parent_component=parent_id).all()
        if(len(modules) > 0):
            for module in modules:
                data.append(module.id)
                children_modules = self.get_children_component_ids(module.id)
                if(len(children_modules) > 0):
                    for child_module in children_modules:
                        data.append(child_module)
                        children_modules2 = self.get_children_component_ids(child_module)
                        if(len(children_modules2) > 0):
                            for child_module2 in children_modules2:
                                data.append(child_module2)
        return data   

class OrganisationBranchView(viewsets.ModelViewSet):
    serializer_class = OrganisationBranchSerializer
    
    def get_queryset(self):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        return OrganisationBranch.objects.filter(branch_organisation_id=organisation_id)

    def perform_create(self, serializer):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        organisation = Organisation.objects.get(pk=organisation_id)

        #Create Head Office Branch for Organisation.
        branch = serializer.save(branch_organisation=organisation, added_by=self.request.user.id)
        
        # Create interbranch chart
        update_branch_interbranch_legder(branch, self.request.user.id)

    def perform_update(self, serializer):
        branch = serializer.save()

        # Update Chart details
        chart_name = branch.name + ": InterBranch"

        # Create if not present:
        update_branch_interbranch_legder(branch, self.request.user.id)
        
class OrganisationSettingView(viewsets.ModelViewSet):
    serializer_class = OrganisationSettingSerializer
   
    queryset = OrganisationSetting.objects.all()
    
    def get_queryset(self):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        setting         = self.request.query_params.get('setting',None)
        if setting:
            return OrganisationSetting.objects.filter(org_setting__id=organisation_id,setting_key=setting)
        
        return OrganisationSetting.objects.filter(org_setting__id=organisation_id)
    
    def perform_create(self, serializer):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        organisation = Organisation.objects.get(pk=organisation_id)
        serializer.save(org_setting=organisation, setting_added_by=self.request.user.id)


class CustomCOAsView(APIView):
    def put(self, request, format=None):
        '''
        Manually populate system generated COAs.
        '''
        organisation_id = request.data.get('organisation_id')
        if organisation_id and request.user.is_superuser:
            organisation = Organisation.objects.filter(id=organisation_id)
            if len(organisation) > 0:
                #Initialise system generated COAs.
                populate_system_generalted_charts_of_accounts(organisation[0], self.request.user.id)
                return Response({"status": "ok","message":"Charts successfully updated."}, status=status.HTTP_200_OK)
            
            return Response({"status": "failed","message":"Organisation not found."}, status=status.HTTP_404_NOT_FOUND)
        
        return Response({"status": "failed","message":"Permission not granted."}, status=status.HTTP_403_FORBIDDEN)
    
class GeneralSettingsView(APIView):

    def get(self, request, format=None):
        settings = {}
        general_settings_keys = [
            'use_old_mem_no','withholding_tax','restrict_schedule','restrict_account_opening','enable_group_savings','restrict_member_withdraw','group_savings_customers','savings_product','enable_refine_schedule', 'enable_loan_disbursement_from_any_branch', 'enable_interest_on_loan_rate_and_prepayment', 'aml_deposit_threshold', 'enable_agriculture'
        ]
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        for general_settings_key in general_settings_keys:
            general_setting   = OrganisationSetting.objects.filter(org_setting__id=organisation_id,setting_key=general_settings_key).first()
            if general_setting:
                settings[general_settings_key] = general_setting.setting_value
            else:
                if general_settings_key == 'use_old_mem_no':
                    settings[general_settings_key] = "off"
                if general_settings_key == 'enable_group_savings':
                    settings[general_settings_key] = "off"
                if general_settings_key == 'restrict_member_withdraw':
                    settings[general_settings_key] = "off"
                if general_settings_key == 'restrict_schedule':
                    settings[general_settings_key] = "off"
                if general_settings_key == 'restrict_account_opening':
                    settings[general_settings_key] = "off"
                if general_settings_key == 'withholding_tax':
                    settings[general_settings_key] = 6
                if general_settings_key == 'aml_deposit_threshold':
                    settings[general_settings_key] = 0
                if general_settings_key == 'enable_refine_schedule':
                    settings[general_settings_key] = "on"
                
                if general_settings_key == 'enable_loan_disbursement_from_any_branch':
                    settings[general_settings_key] = "off"
                if general_settings_key == 'enable_interest_on_loan_rate_and_prepayment':
                    settings[general_settings_key] = "off"
                if general_settings_key == 'enable_agriculture':
                    settings[general_settings_key] = "off"

        return Response(settings)
    
    def post(self, request, format=None):
            organisation_id = get_current_user(self.request, 'organisation_id', None)
            all_keys = [
                'use_old_mem_no', 'withholding_tax', 'restrict_schedule',
                'restrict_account_opening', 'enable_group_savings',
                'restrict_member_withdraw', 'group_savings_customers',
                'savings_product', 'enable_refine_schedule',
                'enable_loan_disbursement_from_any_branch',
                'enable_interest_on_loan_rate_and_prepayment',
                'aml_deposit_threshold', 'enable_agriculture',
            ]
            # Only process keys that were actually sent in the request
            settings_data = []
            for key in all_keys:
                if key in request.data:
                    value = request.data.get(key)
                    if key == 'aml_deposit_threshold' and not value:
                        value = 0
                    settings_data.append({"setting_key": key, "setting_value": value})

            for data in settings_data:
                general_setting   = OrganisationSetting.objects.filter(org_setting__id=organisation_id,setting_key=data["setting_key"]).first()
                if general_setting:
                    general_setting.setting_key=data["setting_key"]
                    general_setting.setting_added_by=self.request.user.id
                    if general_setting.setting_key == 'group_savings_customers':
                        customer_type_ids = data["setting_value"]
                        if not isinstance(customer_type_ids, list):
                             customer_type_ids = list(customer_type_ids.split(","))
                        general_setting.setting_value =  json.dumps(customer_type_ids)
                    else:
                        general_setting.setting_value = data["setting_value"]
                    
                    general_setting.save()
                else:
                    OrganisationSetting.objects.create(setting_key=data["setting_key"],setting_value=data["setting_value"],org_setting=Organisation.objects.get(pk=organisation_id),setting_added_by=self.request.user.id)
            
            return Response({"message":"Success"})
    
class PasswordSettingsView(APIView):
    
    def get(self, request, format=None):
        settings = {}
        password_settings_keys = [
            'password_expiry_days','password_expiry_status',
        ]
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        if organisation_id:
            organisation = Organisation.objects.get(pk=organisation_id)
        for password_settings_key in password_settings_keys:
            password_setting   = OrganisationSetting.objects.filter(org_setting__id=organisation_id,setting_key=password_settings_key).first()
            if password_setting:
                settings[password_settings_key] = password_setting.setting_value
            else:
                if password_settings_key == 'password_expiry_days':
                    settings[password_settings_key] = ""
                if password_settings_key == 'password_expiry_status':
                    settings[password_settings_key] = ""
            if organisation:
                account_name = organisation.name
                settings['account_name'] = account_name     
        return Response(settings)
    
    def post(self, request, format=None):
            organisation_id = get_current_user(self.request, 'organisation_id', None)

            password_settings_data   = [
                {"setting_key":"password_expiry_days","setting_value":request.data.get('password_expiry_days')},
                {"setting_key":"password_expiry_status","setting_value":request.data.get('password_expiry_status')},
            ]
            for data in password_settings_data:
                password_setting   = OrganisationSetting.objects.filter(org_setting__id=organisation_id,setting_key=data["setting_key"]).first()
                if password_setting:
                    password_setting.setting_key=data["setting_key"]
                    password_setting.setting_value = data["setting_value"]
                    password_setting.save()
                else:
                    OrganisationSetting.objects.create(setting_key=data["setting_key"],setting_value=data["setting_value"],org_setting=Organisation.objects.get(pk=organisation_id),setting_added_by=self.request.user.id)
            
            return Response({"message":"Success"})
    

class UpdateAggregatorAssignementView(APIView):

    def post(self, request, format=None):
            client_id   = request.data.get('client_id')
            mm_provider = request.data.get('mm_provider',None)
            mm_status   = request.data.get('mm_status',None)
            account_number = request.data.get('account_number',None)
            mm_provider_setting = None

            if mm_provider:
                mm_provider_setting   = OrganisationSetting.objects.filter(org_setting__id=client_id,setting_key='mm_provider').first()
                if mm_provider_setting:
                    mm_provider_setting.setting_value    = mm_provider
                    mm_provider_setting.setting_added_by = self.request.user.id
                    mm_provider_setting.save()
                else:
                    mm_provider_setting = OrganisationSetting.objects.create(setting_key="mm_provider",setting_value=mm_provider,org_setting=Organisation.objects.get(pk=client_id),setting_added_by=self.request.user.id)

                # update account number
                if account_number:
                    mm_provider_setting   = OrganisationSetting.objects.filter(org_setting__id=client_id,setting_key='mm_provider_account_number').first()
                    if mm_provider_setting:
                        mm_provider_setting.setting_value = account_number
                        mm_provider_setting.save()
                    else:
                        mm_provider_setting = OrganisationSetting.objects.create(setting_key="mm_provider_account_number",setting_value=account_number,org_setting=Organisation.objects.get(pk=client_id),setting_added_by=self.request.user.id)

            if mm_status:
                mm_status_setting   = OrganisationSetting.objects.filter(org_setting__id=client_id,setting_key='mm_status').first()
                if mm_status_setting:
                    mm_status_setting.setting_value    = mm_status
                    mm_status_setting.setting_added_by = self.request.user.id
                    mm_status_setting.save()
                else:
                    OrganisationSetting.objects.create(setting_key="mm_status",setting_value=mm_status,org_setting=Organisation.objects.get(pk=client_id),setting_added_by=self.request.user.id)

            # onboard sacco to stanbic if flexi_pay
            if mm_provider and mm_provider_setting:
                mm_provider = MMServiceProvider.objects.filter(unique_identifier='flexi_pay', id=mm_provider).first()
                if mm_provider:
                    saving_thread = threading.Thread(target=flexi_pay_sacco_onboarding, args=(mm_provider_setting.org_setting, account_number, request.user))
                    saving_thread.start()

            return Response({"message":"Success"})


class LicenseSettingView(APIView):

    def get(self, request, format=None):
            settings = {}
            license_settings_keys = [
               'license_reminder'
            ]

            organisation_id = get_current_user(self.request, 'organisation_id', None)
            for license_settings_key in license_settings_keys:
                license_setting   = OrganisationSetting.objects.filter(org_setting__id=organisation_id,setting_key=license_settings_key).first()
                if license_setting:
                    settings[license_settings_key] = license_setting.setting_value
            return Response(settings)
        
            
    def post(self, request, format=None):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        settings_data   = [
            {"setting_key":"license_reminder","setting_value":request.data.get('license_reminder')},
        ]
        for data in settings_data:
            license_setting   = OrganisationSetting.objects.filter(org_setting__id=organisation_id,setting_key=data["setting_key"]).first()
            if license_setting:
                license_setting.setting_key=data["setting_key"]
                license_setting.setting_value = data["setting_value"]
                license_setting.setting_added_by=self.request.user.id
                license_setting.save()
            else:
                OrganisationSetting.objects.create(setting_key=data["setting_key"],setting_value=data["setting_value"],org_setting=Organisation.objects.get(pk=organisation_id),setting_added_by=self.request.user.id)
        
        return Response({"message":"Success"})

class WorkingHoursView(APIView):
    def get(self,request,format=None):
        organisation_branch_id = get_current_user(self.request, 'organisation_branch_id', None)
        day_order = {
            'Monday': 1,
            'Tuesday': 2,
            'Wednesday': 3,
            'Thursday': 4,
            'Friday': 5,
            'Saturday': 6,
            'Sunday': 7,
        }
        
        # Query and sort the working hours
        data = WorkingHours.objects.filter(
            organ_branch__id=organisation_branch_id
        ).annotate(
            day_order=Case(
                *[When(week_days=day, then=Value(order)) for day, order in day_order.items()],
                output_field=IntegerField()
            )
        ).order_by('day_order')
        serializer = WorkingHoursSerializer(data, many=True)
        
        return Response(serializer.data)

    def post(self, request, format=None):
        data = request.data
        branch_id = get_current_user(self.request, 'organisation_branch_id', None)
        branch = OrganisationBranch.objects.get(pk=branch_id)
        added_by = User.objects.get(pk=request.user.id)
        
        if isinstance(data, list):
            for item in data:
                day = item.get('day')
                start_time = item.get('start_time')
                end_time = item.get('end_time')
                is_checked = item.get('is_checked')
                working_hour_id = item.get('id', None)
                working_hour = None

                if working_hour_id:
                    working_hour = WorkingHours.objects.filter(id=working_hour_id, organ_branch=branch).first()

                # Check if the record exists
                if working_hour:
                        # Update the existing record
                        working_hour.week_days = day
                        working_hour.start_time = start_time
                        working_hour.end_time = end_time
                        working_hour.is_checked = is_checked
                        working_hour.save()
                else:
                    # If no ID is provided, create a new record
                    working_hour = WorkingHours.objects.create(
                        organ_branch=branch,
                        week_days=day,
                        start_time=start_time,
                        end_time=end_time,
                        is_checked=is_checked,
                        added_by=added_by
                    )

        return Response({"message": "Success"})
class StaffWorkingHoursView(APIView):
    def get(self,request,format=None):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        user_id = request.GET.get('user')
        print("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1. ")
        if user_id:
            data = StaffWorkingHours.objects.filter(organ_branch__branch_organisation=organisation_id,).all()
        data = StaffWorkingHours.objects.filter(organ_branch__branch_organisation=organisation_id).all()
        serializer = StaffWorkingHoursSerializer(data, many=True)
        
        return Response(serializer.data)
    
    def post(self,request,format=None):
        print("???????????????????????????????????????????????????????? 1. ")
        data = request.data
        branch_id = get_current_user(self.request,'organisation_branch_id',None)
        print("???????????????????????????????????????????????????????? 2. ")
        branch = OrganisationBranch.objects.get(pk=branch_id)
        print("???????????????????????????????????????????????????????? 3. ")
        added_by = User.objects.get(pk=request.user.id)
        print("???????????????????????????????????????????????????????? 4. ")
        
        if isinstance(data, list):
            for item in data:
                start_time = item.get('start_time')
                end_time = item.get('end_time')
                role_id=item.get('role_id')
                print(end_time)
                print("end_time")
                print(role_id)
                print("role_id")
                staff_role = UserRole.objects.get(pk=role_id)
                StaffWorkingHours.objects.filter(organ_branch=branch, user_role=staff_role).delete()
                all=StaffWorkingHours.objects.create(organ_branch=branch,start_time=start_time,end_time=end_time,user_role=staff_role,added_by=added_by)
        return Response({"message":"Success"})
    

# class VoucherConfigViewSet(CreateModelMixin, GenericViewSet):
class VoucherConfigViewSet(viewsets.ModelViewSet):
    """
    ViewSet for VoucherConfig that supports creating configurations.
    """
    queryset = VoucherConfig.objects.all()
    # serializer_class = None  # Define a serializer if needed
    serializer_class = VoucherConfigSerializer

    def create(self, request, *args, **kwargs):
        organisation_id = get_current_user(request, 'organisation_id', None)
        branch_id     = get_current_user(self.request, 'organisation_branch_id', None) 
        added_by = request.user

        # Extracting fields from the request
        name = request.data.get('name')
        start_no = request.data.get('startNo')
        increment_no = request.data.get('incrementNo')
        current_no = request.data.get('currentNo')

        # Validating required fields
        if not all([name, start_no, increment_no]):
            return Response(
                {"error": "Missing required fields."}, 
                status=status.HTTP_400_BAD_REQUEST
            )

        # Check if a record with the same name exists
        try:
            voucher_config = VoucherConfig.objects.filter(
                name=name, organisation_id=organisation_id
            ).first()

            if voucher_config:
                # Update the existing record
                voucher_config.start_no = start_no
                voucher_config.current_value = current_no
                voucher_config.increment_no = increment_no
                voucher_config.added_by = added_by.id  # Assuming `added_by` is an ID
                voucher_config.save()

                return Response(
                    {"message": "VoucherConfig updated successfully.", "id": voucher_config.id},
                    status=status.HTTP_200_OK
                )
            else:
                # Create a new record if one doesn't exist
                voucher_config = VoucherConfig.objects.create(
                    name=name,
                    start_no=start_no,
                    current_value=current_no,
                    increment_no=increment_no,
                    added_by=added_by.id,  # Assuming `added_by` is an ID
                    organisation_id=organisation_id,
                    branch_id=branch_id
                )
                return Response(
                    {"message": "VoucherConfig created successfully.", "id": voucher_config.id},
                    status=status.HTTP_201_CREATED
                )
        except Exception as e:
            return Response(
                {"error": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


    def list(self, request, *args, **kwargs):
        """
        GET method to list all active VoucherConfigs for an organisation.
        """
        voucher_type = request.query_params.get('type', None)
        organisation_id = get_current_user(request, 'organisation_id', None)
        branch_id     = get_current_user(self.request, 'organisation_branch_id', None) 

        # Base queryset filtered by organisation_id and status
        queryset = self.queryset.filter(organisation_id=organisation_id, status="Active", branch_id=branch_id)

        # Add the 'name' filter only if voucher_type is not None
        if voucher_type:
            queryset = queryset.filter(name=voucher_type)

        serializer = self.serializer_class(queryset, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    @action(detail=False, methods=['get'], url_path='next-voucher-number')
    def get_next_voucher_number(self, request, *args, **kwargs):
        """
        GET method to preview the next voucher number WITHOUT incrementing.
        Use this when opening forms to display what the next number will be.
        """
        organisation_id = get_current_user(request, 'organisation_id', None)
        branch_id = get_current_user(request, 'organisation_branch_id', None)
        name = request.query_params.get('name', None)
        
        if not name:
            return Response(
                {"error": "Voucher type 'name' is required."},
                status=status.HTTP_400_BAD_REQUEST
            )
        
        try:
            voucher_config = self.queryset.get(
                organisation_id=organisation_id,
                status="Active",
                name=name,
                branch_id=branch_id
            )
            
            # Calculate next number without saving
            next_number = voucher_config.current_value + voucher_config.increment_no
            
            return Response(
                {
                    "current_value": voucher_config.current_value,
                    "next_number": next_number,
                    "increment_no": voucher_config.increment_no,
                    "name": voucher_config.name
                },
                status=status.HTTP_200_OK
            )
        except VoucherConfig.DoesNotExist:
            return Response(
                {"error": "Active VoucherConfig not found."},
                status=status.HTTP_404_NOT_FOUND
            )
        except Exception as e:
            return Response(
                {"error": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

        
    def update(self, request, pk=None, *args, **kwargs):
        """
        PUT method to update an existing VoucherConfig.
        """
        try:
            voucher_config = self.queryset.get(pk=pk)

            # Update fields
            serializer = self.serializer_class(voucher_config, data=request.data, partial=True)
            if serializer.is_valid():
                serializer.save()
                return Response(
                    {"message": "VoucherConfig updated successfully.", "data": serializer.data},
                    status=status.HTTP_200_OK
                )
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        except VoucherConfig.DoesNotExist:
            return Response(
                {"error": "VoucherConfig not found."},
                status=status.HTTP_404_NOT_FOUND
            )
        except Exception as e:
            return Response(
                {"error": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

    @action(detail=False, methods=['post'], url_path='increment-current-value')
    def increment_current_value(self, request, *args, **kwargs):
        """
        POST method to increment the current_value of an active VoucherConfig by 1.
        """
        organisation_id = get_current_user(request, 'organisation_id', None)
        branch_id     = get_current_user(self.request, 'organisation_branch_id', None) 

        name = request.data.get('name', None)
        try:
            # Retrieve the active VoucherConfig for the organisation
            # voucher_config = self.queryset.get(organisation_id=organisation_id, status="Active")
            voucher_config = self.queryset.get(organisation_id=organisation_id, status="Active", name=name, branch_id=branch_id)
        
            
            # Increment the current_value
            voucher_config.current_value += voucher_config.increment_no
            voucher_config.save()

            # Serialize the updated entity
            serializer = self.serializer_class(voucher_config)
            return Response(
                {"message": "VoucherConfig current_value updated successfully.", "data": serializer.data},
                status=status.HTTP_200_OK
            )
        except VoucherConfig.DoesNotExist:
            return Response(
                {"error": "Active VoucherConfig not found for the given organisation."},
                status=status.HTTP_404_NOT_FOUND
            )
        except Exception as e:
            return Response(
                {"error": str(e)},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


class FinwiseSummaryView(APIView):
    """
    Summary for all organisations under the parent organisation 'Finwise'.
    """

    def calculate_age(self, dob_str):
        """Compute age safely."""
        try:
            dob = datetime.strptime(dob_str, "%Y-%m-%d").date()
            today = date.today()
            return today.year - dob.year - ((today.month, today.day) < (dob.month, dob.day))
        except:
            return None

    def get(self, request):
        include_details = request.query_params.get('include_details', 'false').lower() == 'true'
        gender_filter = request.query_params.get('gender')

        # Locate Finwise parent organisation
        finwise = Organisation.objects.filter(
            Q(name__iexact='Finwise') | Q(short_name__iexact='Finwise')
        ).first()

        if not finwise:
            return Response({
                "total_organizations": 0,
                "total_trained_organizations": 0,
                "total_customers": 0,
                "total_trained_customers": 0,
                "average_trained_customers_per_organization": 0.0,
                "customers_by_gender": {"Male": 0, "Female": 0, "Other": 0},
                "details": {
                    "organizations": [],
                    "customers": [],
                    "trained_customers": []
                } if include_details else None,
            })

        # --- Child Organisations ---
        child_orgs_qs = Organisation.objects.filter(admin_organisation=finwise)
        child_orgs_list = list(child_orgs_qs.values(
            "id", "name", "short_name", "is_trained", "training_date", "region", "district",
            "income_source", "date_added", "status", "logo_url",
            "organisation_type_id", "phone_number"
        ))
        total_orgs = len(child_orgs_list)

        trained_orgs_list = [org for org in child_orgs_list if org['is_trained']]
        trained_org_ids = [org['id'] for org in trained_orgs_list]

        # --- Customers ---
        child_org_ids = [org['id'] for org in child_orgs_list]
        customers_qs = Customer.objects.filter(
            customer_branch__branch_organisation_id__in=child_org_ids,
            is_deleted=False
        )

        customers_list = list(customers_qs.values(
            "id",
            "name",
            "gender",
            "telephone",
            "nationality",
            "customer_branch__branch_organisation__id",
            "customer_branch__branch_organisation__name"
        ))

        dob_field = CustomerRegField.objects.filter(field_label__iexact='Date of Birth').first()
        age_field = CustomerRegField.objects.filter(field_label__iexact='Age').first()
        
        dob_map = {}
        age_map = {}

        customer_ids = [c['id'] for c in customers_list]

        if dob_field:
            dob_metas = CustomerFieldMeta.objects.filter(
                customer_field__customer_reg_field=dob_field,
                customer_id__in=customer_ids
            ).select_related('customer')

            for meta in dob_metas:
                dob_map[meta.customer.id] = meta.value

        if age_field:
            age_metas = CustomerFieldMeta.objects.filter(
                customer_field__customer_reg_field=age_field,
                customer_id__in=customer_ids
            ).select_related('customer')

            for meta in age_metas:
                age_map[meta.customer.id] = meta.value

        for c in customers_list:
            dob = dob_map.get(c['id'])
            c['date_of_birth'] = dob

            if dob:
                age = self.calculate_age(dob)
                c['age'] = age if age is not None else "N/A"
            else:
                age = age_map.get(c['id'])
                c['age'] = age if age else "N/A"
            
            c['age_custom_field'] = age_map.get(c['id'])

        if gender_filter:
            gender_map = {"Male": "M", "Female": "F", "Other": None}
            g = gender_map.get(gender_filter)
            if g:
                customers_list = [c for c in customers_list if c['gender'] == g]
            else:
                customers_list = [c for c in customers_list if c['gender'] not in ['M', 'F']]

        total_customers = len(customers_list)

        trained_customers_list = [
            c for c in customers_list if c['customer_branch__branch_organisation__id'] in trained_org_ids
        ]
        total_trained_customers = len(trained_customers_list)

        avg_trained_per_org = round(
            total_trained_customers / len(trained_orgs_list) if trained_orgs_list else 0.0, 2
        )

        customers_by_gender = {"Male": 0, "Female": 0, "Other": 0}
        for c in customers_list:
            if c['gender'] == 'M':
                customers_by_gender['Male'] += 1
            elif c['gender'] == 'F':
                customers_by_gender['Female'] += 1
            else:
                customers_by_gender['Other'] += 1

        details = None
        if include_details:
            details = {
                "organizations": child_orgs_list,
                "customers": customers_list,
                "trained_customers": trained_customers_list
            }

        return Response({
            "total_organizations": total_orgs,
            "total_trained_organizations": len(trained_orgs_list),
            "total_customers": total_customers,
            "total_trained_customers": total_trained_customers,
            "average_trained_customers_per_organization": avg_trained_per_org,
            "customers_by_gender": customers_by_gender,
            "details": details,
        })


class QdfBmcStatsView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        from loans.models import LoanApplication, LoanRepaymentSchedule, LoanMainTransactions
        from savings.models import SavingAccountTransactions
        from shares.models import SharesTransaction
        from users.models import Staff
        from rest_framework.exceptions import PermissionDenied

        org = request.user.user_organisation_branch.branch_organisation
        if not org or org.short_name != 'Quest Banker':
            raise PermissionDenied('Access restricted to Quest Banker organisation.')

        cached = cache.get('qdf_bmc_stats_v7')
        if cached:
            return Response(cached)

        # Organisations
        saccos = Organisation.objects.filter(organisation_type__org_type='SACCO').count()
        mses   = Organisation.objects.filter(organisation_type__org_type='MSE').count()

        regions_qs = (
            Organisation.objects
            .exclude(region__isnull=True)
            .exclude(region='')
            .values('region')
            .annotate(count=Count('id'))
            .order_by('-count')
        )
        regions = [{'name': r['region'], 'count': r['count']} for r in regions_qs]

        # Customers
        total_customers = Customer.objects.filter(is_deleted=False).count()
        members_saccos  = Customer.objects.filter(customer_branch__branch_organisation__organisation_type__org_type='SACCO', is_deleted=False).count()
        members_mses    = Customer.objects.filter(customer_branch__branch_organisation__organisation_type__org_type='MSE', is_deleted=False).count()

        # Savings accounts
        savings_active   = SavingAccount.objects.filter(status='active').count()
        savings_dormant  = SavingAccount.objects.filter(status='dormant').count()
        savings_inactive = SavingAccount.objects.filter(status='inactive').count()

        # Transactions — total system transactions
        total_transactions = SystemTransactions.objects.filter(deleted=False).count()

        # Savings transactions — count + amount via related SystemTransactions.amount
        savings_dep_qs  = SavingAccountTransactions.objects.filter(transaction_type='deposit', deleted=False)
        savings_with_qs = SavingAccountTransactions.objects.filter(transaction_type='withdrawal', deleted=False)
        savings_trf_qs  = SavingAccountTransactions.objects.filter(transaction_type='transfer', deleted=False)

        transactions_deposits    = savings_dep_qs.count()
        transactions_withdrawals = savings_with_qs.count()
        transactions_transfers   = savings_trf_qs.count()

        txn_savings_deposits         = transactions_deposits
        txn_savings_deposits_amount  = savings_dep_qs.aggregate(total=Sum('transaction__amount'))['total'] or 0
        txn_savings_withdrawals       = transactions_withdrawals
        txn_savings_withdrawals_amount = savings_with_qs.aggregate(total=Sum('transaction__amount'))['total'] or 0

        # Loan transactions — count + amount via LoanMainTransactions.amount

        # Repayments: loans fully cleared off (not individual payment transactions)

        # Shares transactions — count + amount via related SystemTransactions.amount
        shares_purch_qs = SharesTransaction.objects.filter(transaction_type='purchase', deleted=False)
        shares_trf_qs   = SharesTransaction.objects.filter(transaction_type='transfer-out', deleted=False)

        txn_shares_purchases        = shares_purch_qs.count()
        txn_shares_purchases_amount = shares_purch_qs.aggregate(total=Sum('system_transaction__amount'))['total'] or 0
        txn_shares_transfers        = shares_trf_qs.count()
        txn_shares_transfers_amount = shares_trf_qs.aggregate(total=Sum('system_transaction__amount'))['total'] or 0

        # Staff
        staff_male   = Staff.objects.filter(gender='M', is_active=True).count()
        staff_female = Staff.objects.filter(gender='F', is_active=True).count()
        staff_other  = Staff.objects.filter(is_active=True).exclude(gender__in=['M', 'F']).count()

        # All loan applications ever received (any status)
        all_loans_qs = LoanApplication.objects.filter(is_deleted=False)

        def loan_count(status):
            return all_loans_qs.filter(status=status).count()

        def loan_amount(status):
            return all_loans_qs.filter(status=status).aggregate(total=Sum('loan_amount'))['total'] or 0

        # Disbursements: all loans that ever reached disbursed stage
        disbursed_statuses = ['disbursed', 'cleared_off', 'written_off']
        txn_loans_disbursements        = all_loans_qs.filter(status__in=disbursed_statuses).count()
        txn_loans_disbursements_amount = all_loans_qs.filter(status__in=disbursed_statuses).aggregate(total=Sum('loan_amount'))['total'] or 0

        # Loans approved = all that reached approved stage or beyond
        approved_statuses = ['approved', 'disbursed', 'cleared_off', 'written_off']
        loans_approved        = all_loans_qs.filter(status__in=approved_statuses).count()
        loans_approved_amount = all_loans_qs.filter(status__in=approved_statuses).aggregate(total=Sum('loan_amount'))['total'] or 0

        loans_disbursed        = loan_count('disbursed')
        loans_disbursed_amount = loan_amount('disbursed')

        # Loans overdue: disbursed loans with at least one unpaid past-due schedule installment
        today = timezone.now().date()
        overdue_loan_ids = LoanRepaymentSchedule.objects.filter(
            expected_date__date__lt=today,
            status='active',
            deleted=False,
            loan_application__status='disbursed',
            loan_application__is_deleted=False,
        ).values_list('loan_application_id', flat=True).distinct()
        loans_overdue        = overdue_loan_ids.count()
        loans_overdue_amount = all_loans_qs.filter(id__in=overdue_loan_ids).aggregate(total=Sum('loan_amount'))['total'] or 0

        # Loans repaid = cleared_off (disbursed loans that were fully repaid)
        loans_repaid        = loan_count('cleared_off')
        loans_repaid_amount = loan_amount('cleared_off')

        # Borrower gender on active disbursed loans
        loans_borrowers_male   = all_loans_qs.filter(status='disbursed', customer__gender='M').count()
        loans_borrowers_female = all_loans_qs.filter(status='disbursed', customer__gender='F').count()
        loans_borrowers_other  = all_loans_qs.filter(status='disbursed').exclude(customer__gender__in=['M', 'F']).count()

        data = {
            'saccos':                    saccos,
            'mses':                      mses,
            'total_organisations':       saccos + mses,
            'total_customers':           total_customers,
            'members_saccos':            members_saccos,
            'members_mses':              members_mses,
            'savings_active':            savings_active,
            'savings_dormant':           savings_dormant,
            'savings_inactive':          savings_inactive,
            'total_transactions':               total_transactions,
            'transactions_deposits':              transactions_deposits,
            'transactions_withdrawals':           transactions_withdrawals,
            'transactions_transfers':             transactions_transfers,
            'staff_male':                staff_male,
            'staff_female':              staff_female,
            'staff_other':               staff_other,
            'loans_applications':        all_loans_qs.count(),
            'loans_approved':            loans_approved,
            'loans_disbursed':           loans_disbursed,
            'loans_overdue':             loans_overdue,
            'loans_repaid':              loans_repaid,
            'loans_applications_amount': all_loans_qs.aggregate(total=Sum('loan_amount'))['total'] or 0,
            'loans_approved_amount':     loans_approved_amount,
            'loans_disbursed_amount':    loans_disbursed_amount,
            'loans_overdue_amount':      loans_overdue_amount,
            'loans_repaid_amount':       loans_repaid_amount,
            'loans_borrowers_male':      loans_borrowers_male,
            'loans_borrowers_female':    loans_borrowers_female,
            'loans_borrowers_other':     loans_borrowers_other,
            'txn_savings_deposits':              txn_savings_deposits,
            'txn_savings_deposits_amount':        txn_savings_deposits_amount,
            'txn_savings_withdrawals':            txn_savings_withdrawals,
            'txn_savings_withdrawals_amount':     txn_savings_withdrawals_amount,
            'txn_loans_disbursements':            txn_loans_disbursements,
            'txn_loans_disbursements_amount':     txn_loans_disbursements_amount,
            'txn_loans_repayments':               loans_repaid,
            'txn_loans_repayments_amount':        loans_repaid_amount,
            'txn_shares_purchases':               txn_shares_purchases,
            'txn_shares_purchases_amount':        txn_shares_purchases_amount,
            'txn_shares_transfers':               txn_shares_transfers,
            'txn_shares_transfers_amount':        txn_shares_transfers_amount,
            'regions':                   regions,
        }

        cache.set('qdf_bmc_stats_v7', data, 60 * 5)
        return Response(data)


class OrganisationProfileView(APIView):
    def get(self, request, org_id):
        org = Organisation.objects.filter(id=org_id).first()
        if not org:
            return Response({"error": "Not found"}, status=404)

        members_count = Customer.objects.filter(
            customer_branch__branch_organisation_id=org_id,
            is_deleted=False
        ).count()

        data = {
            "id": org.id,
            "name": org.name,
            "region": org.region,
            "district": org.district,
            "county": org.county,
            "sub_county": org.sub_county,
            "parish": org.parish,
            "village": org.village,
            "income_source": org.income_source,
            "members_count": members_count,
        }
        return Response(data)


class PlatformSummaryReportView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        from ledgers.models import SystemTransactions
        from customers.models import Customer
        from savings.models import SavingAccountTransactions
        from loans.models import LoanMainTransactions
        from shares.models import SharesTransaction

        start_date = request.data.get('start_date')
        end_date = request.data.get('end_date')

        orgs = Organisation.objects.exclude(
            Q(name__iexact='Quest Banker') | Q(short_name__iexact='Quest Banker')
        ).order_by('name')

        rows = []
        total_orgs = 0
        total_members = 0
        total_transactions = 0
        total_unique_customers = 0

        for org in orgs:
            members = Customer.objects.filter(
                customer_branch__branch_organisation=org,
                is_deleted=False
            ).count()

            txn_qs = SystemTransactions.objects.filter(
                branch__branch_organisation=org,
                deleted=False
            )
            if start_date:
                txn_qs = txn_qs.filter(record_date__date__gte=start_date)
            if end_date:
                txn_qs = txn_qs.filter(record_date__date__lte=end_date)

            txn_count = txn_qs.count()

            # Distinct customers from savings transactions
            savings_txn_qs = SavingAccountTransactions.objects.filter(
                customer_account__account_customer__customer_branch__branch_organisation=org,
                deleted=False
            )
            if start_date:
                savings_txn_qs = savings_txn_qs.filter(transaction__record_date__date__gte=start_date)
            if end_date:
                savings_txn_qs = savings_txn_qs.filter(transaction__record_date__date__lte=end_date)
            savings_customer_ids = set(
                savings_txn_qs.values_list('customer_account__account_customer_id', flat=True).distinct()
            )

            # Distinct customers from loan transactions
            loan_txn_qs = LoanMainTransactions.objects.filter(
                loan_application__customer__customer_branch__branch_organisation=org,
                deleted=False
            )
            if start_date:
                loan_txn_qs = loan_txn_qs.filter(system_transaction__record_date__date__gte=start_date)
            if end_date:
                loan_txn_qs = loan_txn_qs.filter(system_transaction__record_date__date__lte=end_date)
            loan_customer_ids = set(
                loan_txn_qs.values_list('loan_application__customer_id', flat=True).distinct()
            )

            customers = len(savings_customer_ids | loan_customer_ids)

            # Savings transaction count
            savings_txn_count = savings_txn_qs.count()

            # Loan transaction count
            loan_txn_count = loan_txn_qs.count()

            # Shares transaction count
            shares_txn_qs = SharesTransaction.objects.filter(
                organisation_branch__branch_organisation=org,
                deleted=False
            )
            if start_date:
                shares_txn_qs = shares_txn_qs.filter(system_transaction__record_date__date__gte=start_date)
            if end_date:
                shares_txn_qs = shares_txn_qs.filter(system_transaction__record_date__date__lte=end_date)
            shares_txn_count = shares_txn_qs.count()

            other_txn_count = max(0, txn_count - savings_txn_count - loan_txn_count - shares_txn_count)

            total_orgs += 1
            total_members += members
            total_transactions += txn_count
            total_unique_customers += customers

            rows.append({
                'id': org.id,
                'name': org.name,
                'org_type': org.organisation_type.org_type if org.organisation_type else '',
                'region': org.region or '',
                'district': org.district or '',
                'members': members,
                'transactions': txn_count,
                'customers': customers,
                'loan_transactions': loan_txn_count,
                'savings_transactions': savings_txn_count,
                'shares_transactions': shares_txn_count,
                'other_transactions': other_txn_count,
            })

        return Response({
            'summary': {
                'total_orgs': total_orgs,
                'total_members': total_members,
                'total_transactions': total_transactions,
                'total_unique_customers': total_unique_customers,
            },
            'organisations': rows,
        })


class OrganisationFinanceSummary(APIView):
    """
    Financial summary for an organisation:
    - Total Income
    - Total Expenses
    - Total Assets
    - Total Liabilities
    - Total Transactions
    - Income/Expense Chart Data
    
    Optimized to use single aggregation query instead of multiple queries
    """

    def get(self, request, org_id):
        cache_key = f"org_finance_summary_{org_id}"
        cached_data = cache.get(cache_key)
        if cached_data:
            return Response(cached_data)

        org = Organisation.objects.filter(id=org_id).first()
        if not org:
            return Response({"error": "Organisation not found"}, status=404)

        # Get all charts owned by this organisation
        chart_ids = OrganisationSubAccount.objects.filter(
            account_organisation_id=org_id,
            deleted=False
        ).values_list("id", flat=True)

        if not chart_ids:
            # No charts, return zeros
            response_data = {
                "organisation": org.name,
                "total_income": 0,
                "total_expenses": 0,
                "assets": 0,
                "liabilities": 0,
                "transactions": 0,
                "income_chart": {"labels": ["Income"], "datasets": [{"data": [0]}]},
                "expense_chart": {"labels": ["Expenses"], "datasets": [{"data": [0]}]},
            }
            cache.set(cache_key, response_data, 300)  # Cache for 5 minutes
            return Response(response_data)

        aggregation = SystemTransactions.objects.filter(
            Q(credit_chart_id__in=chart_ids) | Q(debit_chart_id__in=chart_ids),
            deleted=False
        ).aggregate(
            # Income: credit to income account line
            income_total=Sum(
                Case(
                    When(
                        credit_chart__account_line="income",
                        credit_chart__account_organisation_id=org_id,
                        then=F('amount')
                    ),
                    default=Value(0),
                    output_field=DecimalField(max_digits=20, decimal_places=2)
                )
            ),
            # Expenses: debit from expenses account line
            expenses_total=Sum(
                Case(
                    When(
                        debit_chart__account_line="expenses",
                        debit_chart__account_organisation_id=org_id,
                        then=F('amount')
                    ),
                    default=Value(0),
                    output_field=DecimalField(max_digits=20, decimal_places=2)
                )
            ),
            # Assets: debit minus credit
            asset_debits=Sum(
                Case(
                    When(
                        debit_chart__account_line="assets",
                        debit_chart__account_organisation_id=org_id,
                        then=F('amount')
                    ),
                    default=Value(0),
                    output_field=DecimalField(max_digits=20, decimal_places=2)
                )
            ),
            asset_credits=Sum(
                Case(
                    When(
                        credit_chart__account_line="assets",
                        credit_chart__account_organisation_id=org_id,
                        then=F('amount')
                    ),
                    default=Value(0),
                    output_field=DecimalField(max_digits=20, decimal_places=2)
                )
            ),
            # Liabilities: credit minus debit
            liability_debits=Sum(
                Case(
                    When(
                        debit_chart__account_line="liabilities",
                        debit_chart__account_organisation_id=org_id,
                        then=F('amount')
                    ),
                    default=Value(0),
                    output_field=DecimalField(max_digits=20, decimal_places=2)
                )
            ),
            liability_credits=Sum(
                Case(
                    When(
                        credit_chart__account_line="liabilities",
                        credit_chart__account_organisation_id=org_id,
                        then=F('amount')
                    ),
                    default=Value(0),
                    output_field=DecimalField(max_digits=20, decimal_places=2)
                )
            ),
            # Transaction count
            txn_count=Sum(
                Case(
                    When(deleted=False, then=Value(1)),
                    default=Value(0),
                    output_field=IntegerField()
                )
            ),
        )

        # Extract and calculate totals
        income_total = aggregation['income_total'] or 0
        expenses_total = aggregation['expenses_total'] or 0
        asset_total = (aggregation['asset_debits'] or 0) - (aggregation['asset_credits'] or 0)
        liability_total = (aggregation['liability_credits'] or 0) - (aggregation['liability_debits'] or 0)
        transaction_count = aggregation['txn_count'] or 0

        response_data = {
            "organisation": org.name,
            "total_income": income_total,
            "total_expenses": expenses_total,
            "assets": asset_total,
            "liabilities": liability_total,
            "transactions": transaction_count,
            "income_chart": {
                "labels": ["Income"],
                "datasets": [{"data": [income_total]}]
            },
            "expense_chart": {
                "labels": ["Expenses"],
                "datasets": [{"data": [expenses_total]}]
            },
        }

        cache.set(cache_key, response_data, 300)

        return Response(response_data)


class LocationSettingsView(APIView):

    def get(self, request, format=None):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        setting = OrganisationSetting.objects.filter(
            org_setting__id=organisation_id, setting_key='location_enabled'
        ).first()
        return Response({
            'location_enabled': setting.setting_value if setting else 'off'
        })

    def post(self, request, format=None):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        value = request.data.get('location_enabled', 'off')
        setting = OrganisationSetting.objects.filter(
            org_setting__id=organisation_id, setting_key='location_enabled'
        ).first()
        if setting:
            setting.setting_value = value
            setting.setting_added_by = self.request.user.id
            setting.save()
        else:
            OrganisationSetting.objects.create(
                setting_key='location_enabled',
                setting_value=value,
                org_setting=Organisation.objects.get(pk=organisation_id),
                setting_added_by=self.request.user.id
            )
        return Response({'message': 'Success'})


class OrganisationLocationViewSet(viewsets.ModelViewSet):
    serializer_class = OrganisationLocationSerializer

    def get_queryset(self):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        return OrganisationLocation.objects.filter(organisation__id=organisation_id).order_by('-date_added')

    def perform_create(self, serializer):
        organisation_id = get_current_user(self.request, 'organisation_id', None)
        serializer.save(
            organisation=Organisation.objects.get(pk=organisation_id),
            added_by=self.request.user.id
        )

    @action(detail=False, methods=['post'], url_path='bulk-upload')
    def bulk_upload(self, request):
        import csv
        import io
        file = request.FILES.get('file')
        if not file:
            return Response({'error': 'No file provided'}, status=status.HTTP_400_BAD_REQUEST)
        organisation_id = get_current_user(request, 'organisation_id', None)
        organisation = Organisation.objects.get(pk=organisation_id)
        try:
            decoded = file.read().decode('utf-8')
            reader = csv.DictReader(io.StringIO(decoded))
            created = 0
            for row in reader:
                name = (row.get('name*') or row.get('name') or '').strip()
                if not name:
                    continue
                OrganisationLocation.objects.create(
                    name=name,
                    location_code=(row.get('location_code') or '').strip(),
                    organisation=organisation,
                    added_by=request.user.id
                )
                created += 1
            return Response({'message': f'{created} locations uploaded successfully'}, status=status.HTTP_201_CREATED)
        except Exception as e:
            return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)
