# import json
# from datetime import timedelta
# from decimal import Decimal
# from django.contrib.auth import authenticate
# from django.core.mail import send_mail
# from django.db import transaction
# from django.db.models import Count, Sum, Q
# from django.db.models import Sum, Max, Avg, Count
# from django.contrib.auth.hashers import make_password, check_password
# from django.utils import timezone
# from rest_framework import status
# from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
# from rest_framework.permissions import AllowAny, IsAuthenticated
# from rest_framework.response import Response
# from rest_framework.views import APIView
# from rest_framework_simplejwt.tokens import RefreshToken
# from openpyxl import Workbook
# from django.http import HttpResponse
# from io import BytesIO

# from .models import (
#     User, Business, BusinessBankDetail, BusinessDetailField, BusinessDocument,
#     Investor, InvestorDetailField, InvestorDocument, AccountantDetailField, AccountantDocument, Scheme, KuberBusinessInvestment,
#     Investment, BalanceSheet, DailyFinancialStatement, LedgerEntry, Payout, Certificate, CertificateAttachment,
#     AuditLog, RecurringContribution, PasswordResetOTP, AccountantPermission
# )
# from .permissions import IsAdmin
# from .serializers import (
#     UserCreateSerializer, UserOutputSerializer, BusinessSerializer,
#     BusinessDocumentSerializer, InvestorSerializer, InvestorDocumentSerializer,
#     InvestorDetailFieldSerializer, AccountantDocumentSerializer, SchemeSerializer, KuberBusinessInvestmentSerializer,
#     InvestmentSerializer, BalanceSheetSerializer, DailyFinancialStatementSerializer, LedgerEntrySerializer, PayoutSerializer,
#     CertificateSerializer, CertificateAttachmentSerializer, AuditLogSerializer,
#     RecurringContributionSerializer, AccountantPermissionSerializer,InvestorProfileSerializer,
#     InvestorInvestmentSerializer
# )
# from .services import (
#     audit, create_investment_with_snapshot, generate_daily_ledger,
#     maturity_value, premature_value, build_default_certificate_html,
#     diamond_interest_till_date
# )


# def page_response(request, queryset, serializer_class):
#     from rest_framework.pagination import PageNumberPagination
#     p = PageNumberPagination()
#     p.page_size = 20
#     p.page_size_query_param = "page_size"
#     p.max_page_size = 500
#     page = p.paginate_queryset(queryset, request)
#     return p.get_paginated_response(serializer_class(page, many=True, context={"request": request}).data)


# class AdminOnlyAPIView(APIView):
#     permission_classes = [IsAdmin]


# class LoginAPIView(APIView):
#     permission_classes = [AllowAny]

#     def post(self, request):
#         username = request.data.get("username")
#         password = request.data.get("password")
#         user = authenticate(username=username, password=password)
#         if not user or not user.is_active:
#             return Response({"detail": "Invalid credentials."}, status=status.HTTP_401_UNAUTHORIZED)
#         refresh = RefreshToken.for_user(user)
#         return Response({
#             "access": str(refresh.access_token),
#             "refresh": str(refresh),
#             "user": UserOutputSerializer(user, context={"request": request}).data,
#         })




# class IsBusinessUser(APIView):
#     permission_classes = []

#     def initial(self, request, *args, **kwargs):
#         super().initial(request, *args, **kwargs)
#         user = request.user
#         if not user or not user.is_authenticated or not user.is_active or user.role != User.Roles.BUSINESS:
#             from rest_framework.exceptions import PermissionDenied
#             raise PermissionDenied("You do not have permission to perform this action.")


# class BusinessPortalBaseAPIView(IsBusinessUser):
#     pass


# class BusinessPortalMeAPIView(BusinessPortalBaseAPIView):
#     def get(self, request):
#         business = Business.objects.filter(
#             user=request.user, deleted_at__isnull=True
#         ).prefetch_related("bank_detail_fields", "detail_fields", "uploaded_documents").first()
#         if not business:
#             return Response({"detail": "Business account is not linked to a Business record."}, status=404)
#         return Response(BusinessSerializer(business, context={"request": request}).data)


# class BusinessPortalDashboardAPIView(BusinessPortalBaseAPIView):
#     def get(self, request):
#         business = Business.objects.filter(user=request.user, deleted_at__isnull=True).first()
#         if not business:
#             return Response({"detail": "Business account is not linked to a Business record."}, status=404)

#         investment_qs = Investment.objects.filter(business=business, deleted_at__isnull=True)
#         ledger_qs = LedgerEntry.objects.filter(business=business)
#         investors_count = investment_qs.values("investor_id").distinct().count()
#         total_investment = investment_qs.filter(amount__isnull=False).aggregate(v=Sum("amount"))["v"] or Decimal("0")
#         platinum_total = RecurringContribution.objects.filter(
#             investment__business=business,
#             investment__deleted_at__isnull=True,
#             deleted_at__isnull=True,
#         ).aggregate(v=Sum("amount"))["v"] or Decimal("0")
#         ledger_qs = ledger_qs.exclude(investment__scheme__category=Scheme.Categories.DIAMOND)
#         credit = ledger_qs.filter(entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT).aggregate(v=Sum("amount"))["v"] or Decimal("0")
#         debit = ledger_qs.filter(entry_type=LedgerEntry.EntryTypes.DEBIT).aggregate(v=Sum("amount"))["v"] or Decimal("0")
#         return Response({
#             "business": BusinessSerializer(business, context={"request": request}).data,
#             "kpis": {
#                 "investors": investors_count,
#                 "total_investment": total_investment + platinum_total,
#                 "credit_profit": credit,
#                 "debit": debit,
#                 "accumulated_funding": credit - debit,
#             },
#         })


# class BusinessPortalInvestorsAPIView(BusinessPortalBaseAPIView):
#     def get(self, request):
#         business = Business.objects.filter(user=request.user, deleted_at__isnull=True).first()
#         if not business:
#             return Response({"detail": "Business account is not linked to a Business record."}, status=404)
#         ids = Investment.objects.filter(business=business, deleted_at__isnull=True).values_list("investor_id", flat=True).distinct()
#         qs = Investor.objects.filter(id__in=ids, deleted_at__isnull=True).order_by("name")
#         return page_response(request, qs, InvestorSerializer)


# class BusinessPortalInvestmentsAPIView(BusinessPortalBaseAPIView):
#     def get(self, request):
#         business = Business.objects.filter(user=request.user, deleted_at__isnull=True).first()
#         if not business:
#             return Response({"detail": "Business account is not linked to a Business record."}, status=404)
#         qs = Investment.objects.filter(business=business, deleted_at__isnull=True).select_related("investor", "business", "scheme").prefetch_related("payouts")
#         return page_response(request, qs, InvestmentSerializer)

# from decimal import Decimal

# from django.db.models import Q, Sum, DecimalField, Value
# from django.db.models.functions import Coalesce
# class BusinessPortalKuberInvestmentsAPIView(BusinessPortalBaseAPIView):
#     """
#     Read-only Kuber -> Business investments for the logged-in
#     Business account.

#     Interest is calculated from LedgerEntry CREDIT_PROFIT records
#     belonging to each KuberBusinessInvestment.
#     """

#     def get(self, request):
#         business = Business.objects.filter(
#             user=request.user,
#             deleted_at__isnull=True
#         ).first()

#         if not business:
#             return Response(
#                 {
#                     "detail": (
#                         "Business account is not linked "
#                         "to a Business record."
#                     )
#                 },
#                 status=404
#             )

#         qs = (
#             KuberBusinessInvestment.objects
#             .filter(
#                 business=business,
#                 deleted_at__isnull=True
#             )
#             .select_related("business")
#             .annotate(
#                 interest=Coalesce(
#                     Sum(
#                         "ledger_entries__amount",
#                         filter=Q(
#                             ledger_entries__entry_type=(
#                                 LedgerEntry.EntryTypes.CREDIT_PROFIT
#                             )
#                         )
#                     ),
#                     Value(Decimal("0.00")),
#                     output_field=DecimalField(
#                         max_digits=14,
#                         decimal_places=2
#                     )
#                 )
#             )
#             .order_by(
#                 "-investment_date",
#                 "-id"
#             )
#         )

#         return page_response(
#             request,
#             qs,
#             KuberBusinessInvestmentSerializer
#         )


# class BusinessPortalBalanceSheetsAPIView(BusinessPortalBaseAPIView):
#     """
#     Business balance-sheet endpoint.

#     The Business UI intentionally submits one combined form containing:
#       1. BalanceSheet fields
#       2. DailyFinancialStatement fields

#     Both records are persisted together for the same business + statement date.
#     """

#     def get(self, request):
#         business = Business.objects.filter(
#             user=request.user, deleted_at__isnull=True
#         ).first()
#         if not business:
#             return Response({"detail": "Business account is not linked to a Business record."}, status=404)

#         qs = BalanceSheet.objects.filter(
#             business=business, deleted_at__isnull=True
#         ).select_related("business", "entered_by").order_by("-statement_date", "-id")
#         return page_response(request, qs, BalanceSheetSerializer)

#     @transaction.atomic
#     def post(self, request):
#         business = Business.objects.filter(
#             user=request.user, deleted_at__isnull=True
#         ).first()
#         if not business:
#             return Response({"detail": "Business account is not linked to a Business record."}, status=404)

#         payload = request.data.copy()
#         payload["business"] = business.pk

#         balance_fields = {
#             "business", "statement_date",
#             "cash_in_hand", "bank_balance", "accounts_receivable", "inventory",
#             "prepaid_expenses", "other_current_assets", "fixed_assets",
#             "accumulated_depreciation", "other_non_current_assets",
#             "accounts_payable", "short_term_borrowings", "accrued_expenses",
#             "taxes_payable", "other_current_liabilities", "long_term_loans",
#             "other_non_current_liabilities", "owner_capital", "reserves_surplus",
#             "retained_earnings", "drawings", "remarks",
#         }
#         financial_fields = {
#             "business", "statement_date", "revenue", "direct_cost",
#             "operating_expenses", "other_income", "finance_cost", "tax",
#             "operating_cash_inflow", "operating_cash_outflow",
#             "investing_cash_inflow", "investing_cash_outflow",
#             "financing_cash_inflow", "financing_cash_outflow", "remarks",
#         }

#         balance_payload = {k: payload.get(k) for k in balance_fields if k in payload}
#         balance_payload["business"] = business.pk
#         balance_serializer = BalanceSheetSerializer(data=balance_payload, context={"request": request})
#         balance_serializer.is_valid(raise_exception=True)

#         statement_date = balance_serializer.validated_data["statement_date"]
#         if BalanceSheet.objects.filter(
#             business=business, statement_date=statement_date, deleted_at__isnull=True
#         ).exists():
#             return Response({"detail": "A balance sheet already exists for this business and date."}, status=400)

#         financial_payload = {k: payload.get(k) for k in financial_fields if k in payload}
#         financial_payload["business"] = business.pk
#         financial_payload["statement_date"] = statement_date
#         financial_serializer = DailyFinancialStatementSerializer(
#             data=financial_payload, context={"request": request}
#         )
#         financial_serializer.is_valid(raise_exception=True)
#         if DailyFinancialStatement.objects.filter(
#             business=business, statement_date=statement_date, deleted_at__isnull=True
#         ).exists():
#             return Response({"detail": "A daily financial statement already exists for this business and date."}, status=400)

#         balance = balance_serializer.save(entered_by=request.user)
#         financial = financial_serializer.save(
#             business=business, submitted_by=request.user,
#             status=DailyFinancialStatement.Status.SUBMITTED
#         )

#         audit(request.user, "CREATE", "BALANCE_SHEET", balance, "Balance sheet uploaded by business user.")
#         audit(request.user, "CREATE", "DAILY_FINANCIAL_STATEMENT", financial, "Daily financial statement uploaded with balance sheet.")

#         return Response({
#             "balance_sheet": BalanceSheetSerializer(balance, context={"request": request}).data,
#             "daily_financial_statement": DailyFinancialStatementSerializer(financial, context={"request": request}).data,
#         }, status=201)


# class BusinessPortalLedgerAPIView(BusinessPortalBaseAPIView):
#     def get(self, request):
#         business = Business.objects.filter(user=request.user, deleted_at__isnull=True).first()
#         if not business:
#             return Response({"detail": "Business account is not linked to a Business record."}, status=404)
#         qs = LedgerEntry.objects.filter(business=business).exclude(
#             investment__scheme__category=Scheme.Categories.DIAMOND
#         ).select_related("business", "investor", "investment", "kuber_investment")
#         if request.query_params.get("entry_type"):
#             qs = qs.filter(entry_type=request.query_params["entry_type"])
#         if request.query_params.get("date"):
#             qs = qs.filter(entry_date=request.query_params["date"])
#         return page_response(request, qs, LedgerEntrySerializer)


# class ForgotPasswordAPIView(APIView):
#     permission_classes = [AllowAny]

#     def post(self, request):
#         email = request.data.get("email")
#         user = User.objects.filter(email__iexact=email, is_active=True).first()
#         # Do not reveal whether an email exists.
#         if user:
#             import secrets
#             otp = f"{secrets.randbelow(1000000):06d}"
#             PasswordResetOTP.objects.filter(user=user, used=False).update(used=True)
#             PasswordResetOTP.objects.create(
#                 user=user,
#                 otp_hash=make_password(otp),
#                 expires_at=timezone.now() + timedelta(minutes=10),
#             )
#             send_mail(
#                 "Kuber Trobe Password Reset OTP",
#                 f"Your OTP is {otp}. It expires in 10 minutes.",
#                 None,
#                 [user.email],
#             )
#         return Response({"detail": "If the email is registered, an OTP has been sent."})


# class ResetPasswordAPIView(APIView):
#     permission_classes = [AllowAny]

#     def post(self, request):
#         email = request.data.get("email")
#         otp = request.data.get("otp")
#         new_password = request.data.get("new_password")
#         user = User.objects.filter(email__iexact=email, is_active=True).first()
#         if not user or not otp or not new_password:
#             return Response({"detail": "Invalid reset request."}, status=400)

#         reset = PasswordResetOTP.objects.filter(
#             user=user, used=False, expires_at__gt=timezone.now()
#         ).first()
#         if not reset or reset.attempts >= 5 or not check_password(otp, reset.otp_hash):
#             if reset:
#                 reset.attempts += 1
#                 reset.save(update_fields=["attempts"])
#             return Response({"detail": "Invalid or expired OTP."}, status=400)

#         user.set_password(new_password)
#         user.save(update_fields=["password"])
#         reset.used = True
#         reset.save(update_fields=["used"])
#         return Response({"detail": "Password reset successfully."})


# class DashboardAPIView(AdminOnlyAPIView):
#     def get(self, request):
#         businesses = Business.objects.filter(deleted_at__isnull=True)
#         investors = Investor.objects.filter(deleted_at__isnull=True)
#         investments = Investment.objects.filter(deleted_at__isnull=True)
#         kuber_investments = KuberBusinessInvestment.objects.filter(deleted_at__isnull=True)
#         one_time_total = investments.filter(amount__isnull=False).aggregate(v=Sum("amount"))["v"] or Decimal("0")
#         platinum_total = RecurringContribution.objects.filter(
#             deleted_at__isnull=True,
#             investment__deleted_at__isnull=True,
#             investment__scheme__category=Scheme.Categories.PLATINUM,
#         ).aggregate(v=Sum("amount"))["v"] or Decimal("0")
#         total_investment = one_time_total + platinum_total
#         payable = LedgerEntry.objects.filter(
#             entry_type=LedgerEntry.EntryTypes.SOFT_DEBIT
#         ).exclude(
#             investment__scheme__category=Scheme.Categories.DIAMOND
#         ).aggregate(total=Sum("amount"))["total"] or Decimal("0")
#         credits = LedgerEntry.objects.filter(
#             entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT
#         ).aggregate(total=Sum("amount"))["total"] or Decimal("0")
#         debits = LedgerEntry.objects.filter(
#             entry_type=LedgerEntry.EntryTypes.DEBIT
#         ).aggregate(total=Sum("amount"))["total"] or Decimal("0")
#         soft_debits = payable
#         return Response({
#             "kpis": {
#                 "total_businesses": businesses.count(),
#                 "total_investors": investors.count(),
#                 "total_investment": total_investment,
#                 "total_kuber_business_investment": kuber_investments.aggregate(v=Sum("amount"))["v"] or 0,
#                 "interest_payable_to_investors": payable,
#                 "accumulated_funding": credits - debits,
#                 "actual_funds": credits - soft_debits - debits,
#             },
#             "note": "Charts can be built from the same date-grouped aggregates exposed by reporting endpoints.",
#         })


# class BusinessListCreateAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser, JSONParser]

#     def get(self, request):
#         qs = Business.objects.filter(deleted_at__isnull=True).annotate(
#             investor_count=Count("investments", filter=Q(investments__deleted_at__isnull=True), distinct=True),
#         )
#         return page_response(request, qs, BusinessSerializer)

#     @transaction.atomic
#     def post(self, request):
#         serializer = BusinessSerializer(data=request.data, context={"request": request})
#         serializer.is_valid(raise_exception=True)
#         business = serializer.save()
#         audit(request.user, "CREATE", "BUSINESS", business, "Business created.")
#         return Response(BusinessSerializer(business, context={"request": request}).data, status=201)


# class BusinessDetailAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser, JSONParser]

#     def get_object(self, pk):
#         return Business.objects.filter(pk=pk, deleted_at__isnull=True).first()

#     def get(self, request, pk):
#         obj = self.get_object(pk)
#         if not obj:
#             return Response({"detail": "Business not found."}, status=404)
#         return Response(BusinessSerializer(obj, context={"request": request}).data)

#     def put(self, request, pk):
#         obj = self.get_object(pk)
#         if not obj:
#             return Response({"detail": "Business not found."}, status=404)
#         serializer = BusinessSerializer(obj, data=request.data, partial=True, context={"request": request})
#         serializer.is_valid(raise_exception=True)
#         obj = serializer.save()
#         audit(request.user, "UPDATE", "BUSINESS", obj, "Business updated.")
#         return Response(BusinessSerializer(obj, context={"request": request}).data)

#     def delete(self, request, pk):
#         obj = self.get_object(pk)
#         if not obj:
#             return Response({"detail": "Business not found."}, status=404)
#         obj.soft_delete()
#         audit(request.user, "DELETE", "BUSINESS", obj, "Business moved to restore bin.")
#         return Response(status=204)


# class BusinessDocumentCreateAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser]

#     def post(self, request, business_id):
#         business = Business.objects.filter(pk=business_id, deleted_at__isnull=True).first()
#         if not business:
#             return Response({"detail": "Business not found."}, status=404)
#         serializer = BusinessDocumentSerializer(data=request.data)
#         serializer.is_valid(raise_exception=True)
#         doc = serializer.save(business=business, uploaded_by=request.user)
#         audit(request.user, "CREATE", "BUSINESS_DOCUMENT", doc, "Business document uploaded.")
#         return Response(BusinessDocumentSerializer(doc).data, status=201)


# class InvestorDocumentListAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser]
#     def get(self, request, investor_id):
#         qs = InvestorDocument.objects.filter(investor_id=investor_id)
#         return page_response(request, qs, InvestorDocumentSerializer)

#     def post(self, request, investor_id):
#         investor = Investor.objects.filter(pk=investor_id, deleted_at__isnull=True).first()
#         if not investor:
#             return Response({"detail": "Investor not found."}, status=404)
#         files = request.FILES.getlist("files")
#         section = request.data.get("section", InvestorDocument.Sections.OTHER)
#         titles = request.data.getlist("titles") if hasattr(request.data, "getlist") else []
#         created = []
#         for index, file in enumerate(files):
#             title = titles[index] if index < len(titles) else file.name
#             created.append(InvestorDocument.objects.create(
#                 investor=investor, section=section, title=title or file.name,
#                 file=file, uploaded_by=request.user
#             ))
#         return Response(InvestorDocumentSerializer(created, many=True).data, status=201)


# class BusinessInvestorsAPIView(AdminOnlyAPIView):
#     def get(self, request, business_id):
#         business = Business.objects.filter(pk=business_id, deleted_at__isnull=True).first()
#         if not business:
#             return Response({"detail": "Business not found."}, status=404)
#         qs = Investor.objects.filter(
#             investments__business=business,
#             investments__deleted_at__isnull=True,
#             deleted_at__isnull=True,
#         ).distinct()
#         return page_response(request, qs, InvestorSerializer)


# class InvestorListCreateAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser, JSONParser]
#     def get(self, request):
#         qs = Investor.objects.filter(deleted_at__isnull=True).annotate(
#             business_count=Count("investments__business", filter=Q(investments__deleted_at__isnull=True), distinct=True),
#         )
#         return page_response(request, qs, InvestorSerializer)

#     @transaction.atomic
#     def post(self, request):
#         serializer = InvestorSerializer(data=request.data, context={"request": request})
#         serializer.is_valid(raise_exception=True)
#         investor = serializer.save()
#         audit(request.user, "CREATE", "INVESTOR", investor, "Investor created.")
#         return Response(InvestorSerializer(investor, context={"request": request}).data, status=201)


# class InvestorDetailAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser, JSONParser]
#     def get(self, request, pk):
#         investor = Investor.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not investor:
#             return Response({"detail": "Investor not found."}, status=404)
#         investor.business_count = investor.investments.filter(deleted_at__isnull=True).values("business").distinct().count()
#         return Response(InvestorSerializer(investor, context={"request": request}).data)

#     def put(self, request, pk):
#         investor = Investor.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not investor:
#             return Response({"detail": "Investor not found."}, status=404)
#         serializer = InvestorSerializer(investor, data=request.data, partial=True, context={"request": request})
#         serializer.is_valid(raise_exception=True)
#         investor = serializer.save()
#         audit(request.user, "UPDATE", "INVESTOR", investor, "Investor updated.")
#         return Response(InvestorSerializer(investor, context={"request": request}).data)

#     def delete(self, request, pk):
#         investor = Investor.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not investor:
#             return Response({"detail": "Investor not found."}, status=404)
#         investor.soft_delete()
#         audit(request.user, "DELETE", "INVESTOR", investor, "Investor moved to restore bin.")
#         return Response(status=204)


# class InvestorPortfolioAPIView(AdminOnlyAPIView):
#     def get(self, request, pk):
#         investor = Investor.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not investor:
#             return Response({"detail": "Investor not found."}, status=404)
#         qs = Investment.objects.filter(investor=investor, deleted_at__isnull=True).select_related("business", "scheme")
#         return page_response(request, qs, InvestmentSerializer)


# class SchemeListCreateAPIView(AdminOnlyAPIView):
#     def get(self, request):
#         qs = Scheme.objects.filter(deleted_at__isnull=True)
#         category = request.query_params.get("category")
#         if category:
#             qs = qs.filter(category=category.upper())
#         return page_response(request, qs, SchemeSerializer)

#     def post(self, request):
#         serializer = SchemeSerializer(data=request.data)
#         serializer.is_valid(raise_exception=True)
#         scheme = serializer.save(created_by=request.user)
#         audit(request.user, "CREATE", "SCHEME", scheme, "Scheme created.")
#         return Response(SchemeSerializer(scheme).data, status=201)


# class SchemeDetailAPIView(AdminOnlyAPIView):
#     def put(self, request, pk):
#         scheme = Scheme.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not scheme:
#             return Response({"detail": "Scheme not found."}, status=404)
#         serializer = SchemeSerializer(scheme, data=request.data, partial=True)
#         serializer.is_valid(raise_exception=True)
#         scheme = serializer.save()
#         audit(request.user, "UPDATE", "SCHEME", scheme, "Scheme updated.")
#         return Response(SchemeSerializer(scheme).data)

#     def delete(self, request, pk):
#         scheme = Scheme.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not scheme:
#             return Response({"detail": "Scheme not found."}, status=404)
#         scheme.soft_delete()
#         audit(request.user, "DELETE", "SCHEME", scheme, "Scheme moved to restore bin.")
#         return Response(status=204)


# class KuberBusinessInvestmentListCreateAPIView(AdminOnlyAPIView):
#     def get(self, request):
#         qs = KuberBusinessInvestment.objects.filter(deleted_at__isnull=True).select_related("business")
#         return page_response(request, qs, KuberBusinessInvestmentSerializer)

#     def post(self, request):
#         serializer = KuberBusinessInvestmentSerializer(data=request.data)
#         serializer.is_valid(raise_exception=True)
#         obj = serializer.save(created_by=request.user)
#         audit(request.user, "CREATE", "KUBER_BUSINESS_INVESTMENT", obj, "Kuber investment into business created.")
#         return Response(KuberBusinessInvestmentSerializer(obj).data, status=201)


# class KuberBusinessInvestmentDetailAPIView(AdminOnlyAPIView):
#     def put(self, request, pk):
#         obj = KuberBusinessInvestment.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not obj:
#             return Response({"detail": "Investment not found."}, status=404)
#         serializer = KuberBusinessInvestmentSerializer(obj, data=request.data, partial=True)
#         serializer.is_valid(raise_exception=True)
#         obj = serializer.save()
#         audit(request.user, "UPDATE", "KUBER_BUSINESS_INVESTMENT", obj, "Kuber investment updated.")
#         return Response(KuberBusinessInvestmentSerializer(obj).data)

#     def delete(self, request, pk):
#         obj = KuberBusinessInvestment.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not obj:
#             return Response({"detail": "Investment not found."}, status=404)
#         obj.soft_delete()
#         audit(request.user, "DELETE", "KUBER_BUSINESS_INVESTMENT", obj, "Kuber investment moved to restore bin.")
#         return Response(status=204)


# class BusinessCreditProfitAPIView(AdminOnlyAPIView):
#     def get(self, request):
#         qs = Business.objects.filter(deleted_at__isnull=True).order_by("name")
#         business_id = request.query_params.get("business")
#         if business_id:
#             qs = qs.filter(id=business_id)

#         rows = []
#         for business in qs:
#             credit_qs = LedgerEntry.objects.filter(
#                 business=business,
#                 kuber_investment__isnull=False,
#                 entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT,
#             )
#             payout_qs = LedgerEntry.objects.filter(
#                 business=business,
#                 entry_type=LedgerEntry.EntryTypes.DEBIT,
#                 reference__startswith="KUBER-PAYMENT-",
#             )
#             credit_data = credit_qs.aggregate(total=Sum("amount"), last_date=Max("entry_date"))
#             payout_data = payout_qs.aggregate(total=Sum("amount"), last_date=Max("entry_date"))
#             credit = credit_data["total"] or Decimal("0.00")
#             payments = payout_data["total"] or Decimal("0.00")
#             rows.append({
#                 "business": business.id,
#                 "business_name": business.name,
#                 "accumulated_credit_profit": credit,
#                 "total_payouts": payments,
#                 "available_for_kuber_payout": max(Decimal("0.00"), credit - payments),
#                 "last_payout_date": payout_data["last_date"],
#                 "last_credit_profit_date": credit_data["last_date"],
#             })
#         return Response({"results": rows})


# class KuberBusinessPayoutAPIView(AdminOnlyAPIView):
#     """Record a payment received by Kuber from a business in the ledger."""

#     @transaction.atomic
#     def post(self, request):
#         business_id = request.data.get("business")
#         amount = request.data.get("amount")
#         payout_date = request.data.get("payout_date") or timezone.localdate()
#         notes = str(request.data.get("notes") or "").strip()

#         business = Business.objects.filter(
#             id=business_id, deleted_at__isnull=True
#         ).first()
#         if not business:
#             return Response({"detail": "Business not found."}, status=404)

#         try:
#             from datetime import datetime
#             if isinstance(payout_date, str):
#                 payout_date = datetime.strptime(payout_date, "%Y-%m-%d").date()
#             amount = Decimal(str(amount))
#         except (TypeError, ValueError, ArithmeticError):
#             return Response({"detail": "Valid amount and payout_date are required."}, status=400)

#         if amount <= 0:
#             return Response({"detail": "Kuber payment amount must be greater than zero."}, status=400)

#         credit = (
#             LedgerEntry.objects.filter(
#                 business=business,
#                 kuber_investment__isnull=False,
#                 entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT,
#             ).aggregate(v=Sum("amount"))["v"]
#             or Decimal("0.00")
#         )
#         paid = (
#             LedgerEntry.objects.filter(
#                 business=business,
#                 entry_type=LedgerEntry.EntryTypes.DEBIT,
#                 reference__startswith="KUBER-PAYMENT-",
#             ).aggregate(v=Sum("amount"))["v"]
#             or Decimal("0.00")
#         )
#         available = max(Decimal("0.00"), credit - paid)

#         if amount > available:
#             return Response(
#                 {
#                     "detail": "Kuber payment cannot exceed the available Credit Profit.",
#                     "available_for_kuber_payout": available,
#                 },
#                 status=400,
#             )

#         entry = LedgerEntry.objects.create(
#             business=business,
#             entry_date=payout_date,
#             entry_type=LedgerEntry.EntryTypes.DEBIT,
#             amount=amount,
#             description=notes or "Kuber Payment received from business.",
#             reference=f"KUBER-PAYMENT-{business.pk}-{timezone.now().strftime('%Y%m%d%H%M%S%f')}",
#         )

#         audit(
#             request.user,
#             "KUBER_PAYMENT",
#             "LEDGER",
#             entry,
#             f"Kuber payment of {amount} recorded for {business.name}.",
#         )

#         return Response({
#             "id": entry.id,
#             "business": business.id,
#             "business_name": business.name,
#             "amount": entry.amount,
#             "payout_date": entry.entry_date,
#             "entry_type": entry.entry_type,
#             "description": entry.description,
#             "reference": entry.reference,
#             "available_for_kuber_payout": available - amount,
#         }, status=201)


# class InvestmentListCreateAPIView(AdminOnlyAPIView):
#     def get(self, request):
#         qs = Investment.objects.filter(
#             deleted_at__isnull=True
#         ).select_related("investor", "business", "scheme").prefetch_related("recurring_contributions", "payouts")
#         investor_id = request.query_params.get("investor")
#         business_id = request.query_params.get("business")
#         scheme_id = request.query_params.get("scheme")
#         if investor_id:
#             qs = qs.filter(investor_id=investor_id)
#         if business_id:
#             qs = qs.filter(business_id=business_id)
#         if scheme_id:
#             qs = qs.filter(scheme_id=scheme_id)
#         return page_response(request, qs, InvestmentSerializer)

#     @transaction.atomic
#     def post(self, request):
#         serializer = InvestmentSerializer(data=request.data, context={"request": request})
#         serializer.is_valid(raise_exception=True)
#         obj = serializer.save()
#         audit(request.user, "CREATE", "INVESTMENT", obj, "Investor investment created.")
#         return Response(
#             InvestmentSerializer(obj, context={"request": request}).data,
#             status=201,
#         )


# class InvestmentDetailAPIView(AdminOnlyAPIView):
#     def get(self, request, pk):
#         obj = Investment.objects.filter(pk=pk, deleted_at__isnull=True).select_related("investor","business","scheme").first()
#         if not obj:
#             return Response({"detail": "Investment not found."}, status=404)
#         return Response(InvestmentSerializer(obj).data)

#     def put(self, request, pk):
#         obj = Investment.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not obj:
#             return Response({"detail": "Investment not found."}, status=404)
#         # Investment terms should normally be changed only by an explicit business rule.
#         serializer = InvestmentSerializer(obj, data=request.data, partial=True)
#         serializer.is_valid(raise_exception=True)
#         obj = serializer.save()
#         audit(request.user, "UPDATE", "INVESTMENT", obj, "Investor investment updated.")
#         return Response(InvestmentSerializer(obj).data)

#     def delete(self, request, pk):
#         obj = Investment.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not obj:
#             return Response({"detail": "Investment not found."}, status=404)
#         obj.soft_delete()
#         audit(request.user, "DELETE", "INVESTMENT", obj, "Investor investment moved to restore bin.")
#         return Response(status=204)


# class BalanceSheetListCreateAPIView(AdminOnlyAPIView):
#     """Admin/Accountant combined Balance Sheet + Daily Financial persistence."""

#     def get(self, request):
#         qs = BalanceSheet.objects.filter(
#             deleted_at__isnull=True
#         ).select_related("business", "entered_by")
#         business_id = request.query_params.get("business")
#         if business_id:
#             qs = qs.filter(business_id=business_id)
#         return page_response(request, qs, BalanceSheetSerializer)

#     @transaction.atomic
#     def post(self, request):
#         payload = request.data.copy()
#         balance_fields = {
#             "business", "statement_date", "cash_in_hand", "bank_balance",
#             "accounts_receivable", "inventory", "prepaid_expenses",
#             "other_current_assets", "fixed_assets", "accumulated_depreciation",
#             "other_non_current_assets", "accounts_payable", "short_term_borrowings",
#             "accrued_expenses", "taxes_payable", "other_current_liabilities",
#             "long_term_loans", "other_non_current_liabilities", "owner_capital",
#             "reserves_surplus", "retained_earnings", "drawings", "remarks",
#         }
#         financial_fields = {
#             "business", "statement_date", "revenue", "direct_cost",
#             "operating_expenses", "other_income", "finance_cost", "tax",
#             "operating_cash_inflow", "operating_cash_outflow",
#             "investing_cash_inflow", "investing_cash_outflow",
#             "financing_cash_inflow", "financing_cash_outflow", "remarks",
#         }
#         balance_payload = {k: payload.get(k) for k in balance_fields if k in payload}
#         balance_serializer = BalanceSheetSerializer(data=balance_payload, context={"request": request})
#         balance_serializer.is_valid(raise_exception=True)
#         business = balance_serializer.validated_data["business"]
#         statement_date = balance_serializer.validated_data["statement_date"]

#         if BalanceSheet.objects.filter(business=business, statement_date=statement_date, deleted_at__isnull=True).exists():
#             return Response({"detail": "A balance sheet already exists for this business and date."}, status=400)

#         financial_payload = {k: payload.get(k) for k in financial_fields if k in payload}
#         financial_payload["business"] = business.pk
#         financial_payload["statement_date"] = statement_date
#         financial_serializer = DailyFinancialStatementSerializer(data=financial_payload, context={"request": request})
#         financial_serializer.is_valid(raise_exception=True)
#         if DailyFinancialStatement.objects.filter(business=business, statement_date=statement_date, deleted_at__isnull=True).exists():
#             return Response({"detail": "A daily financial statement already exists for this business and date."}, status=400)

#         balance = balance_serializer.save(entered_by=request.user)
#         financial = financial_serializer.save(
#             business=business, submitted_by=request.user,
#             status=payload.get("status", DailyFinancialStatement.Status.SUBMITTED)
#         )
#         audit(request.user, "CREATE", "BALANCE_SHEET", balance, "Structured balance sheet created.")
#         audit(request.user, "CREATE", "DAILY_FINANCIAL_STATEMENT", financial, "Daily financial statement created with balance sheet.")
#         return Response({
#             "balance_sheet": BalanceSheetSerializer(balance, context={"request": request}).data,
#             "daily_financial_statement": DailyFinancialStatementSerializer(financial, context={"request": request}).data,
#         }, status=201)


# class BalanceSheetDetailAPIView(AdminOnlyAPIView):
#     @transaction.atomic
#     def put(self, request, pk):
#         obj = BalanceSheet.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not obj:
#             return Response({"detail": "Balance sheet not found."}, status=404)

#         payload = request.data.copy()
#         balance_fields = {
#             "business", "statement_date", "cash_in_hand", "bank_balance",
#             "accounts_receivable", "inventory", "prepaid_expenses", "other_current_assets",
#             "fixed_assets", "accumulated_depreciation", "other_non_current_assets",
#             "accounts_payable", "short_term_borrowings", "accrued_expenses", "taxes_payable",
#             "other_current_liabilities", "long_term_loans", "other_non_current_liabilities",
#             "owner_capital", "reserves_surplus", "retained_earnings", "drawings", "remarks",
#         }
#         financial_fields = {
#             "business", "statement_date", "revenue", "direct_cost", "operating_expenses",
#             "other_income", "finance_cost", "tax", "operating_cash_inflow",
#             "operating_cash_outflow", "investing_cash_inflow", "investing_cash_outflow",
#             "financing_cash_inflow", "financing_cash_outflow", "remarks",
#         }
#         balance_payload = {k: payload.get(k) for k in balance_fields if k in payload}
#         serializer = BalanceSheetSerializer(obj, data=balance_payload, partial=True, context={"request": request})
#         serializer.is_valid(raise_exception=True)
#         obj = serializer.save()

#         business = obj.business
#         statement_date = obj.statement_date
#         financial_payload = {k: payload.get(k) for k in financial_fields if k in payload}
#         financial_payload["business"] = business.pk
#         financial_payload["statement_date"] = statement_date
#         financial = DailyFinancialStatement.objects.filter(
#             business=business, statement_date=statement_date, deleted_at__isnull=True
#         ).first()
#         if financial:
#             fs = DailyFinancialStatementSerializer(financial, data=financial_payload, partial=True, context={"request": request})
#         else:
#             fs = DailyFinancialStatementSerializer(data=financial_payload, context={"request": request})
#         fs.is_valid(raise_exception=True)
#         financial = fs.save(
#             business=business, submitted_by=(financial.submitted_by if financial else request.user),
#             status=(financial.status if financial else DailyFinancialStatement.Status.SUBMITTED),
#         )

#         audit(request.user, "UPDATE", "BALANCE_SHEET", obj, "Structured balance sheet updated.")
#         audit(request.user, "UPDATE" if financial else "CREATE", "DAILY_FINANCIAL_STATEMENT", financial, "Daily financial statement saved with balance sheet.")
#         return Response({
#             "balance_sheet": BalanceSheetSerializer(obj, context={"request": request}).data,
#             "daily_financial_statement": DailyFinancialStatementSerializer(financial, context={"request": request}).data,
#         })

#     def patch(self, request, pk):
#         return self.put(request, pk)

#     def delete(self, request, pk):
#         obj = BalanceSheet.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not obj:
#             return Response({"detail": "Balance sheet not found."}, status=404)

#         obj.soft_delete()
#         audit(
#             request.user,
#             "DELETE",
#             "BALANCE_SHEET",
#             obj,
#             "Balance sheet moved to restore bin.",
#         )
#         return Response(status=204)


# class LedgerListAPIView(AdminOnlyAPIView):
#     def get(self, request):
#         qs = LedgerEntry.objects.all().exclude(
#             investment__scheme__category=Scheme.Categories.DIAMOND
#         ).select_related("business", "investor", "investment", "kuber_investment")
#         if request.query_params.get("business"):
#             qs = qs.filter(business_id=request.query_params["business"])
#         if request.query_params.get("investor"):
#             qs = qs.filter(investor_id=request.query_params["investor"])
#         if request.query_params.get("investment"):
#             qs = qs.filter(investment_id=request.query_params["investment"])
#         if request.query_params.get("entry_type"):
#             qs = qs.filter(entry_type=request.query_params["entry_type"])
#         if request.query_params.get("date"):
#             qs = qs.filter(entry_date=request.query_params["date"])
#         return page_response(request, qs, LedgerEntrySerializer)


# class GenerateDailyLedgerAPIView(AdminOnlyAPIView):
#     @transaction.atomic
#     def post(self, request):
#         entry_date = request.data.get("date")
#         if entry_date:
#             from datetime import datetime
#             try:
#                 entry_date = datetime.strptime(entry_date, "%Y-%m-%d").date()
#             except ValueError:
#                 return Response({"detail": "date must be YYYY-MM-DD."}, status=400)
#         else:
#             entry_date = timezone.localdate()

#         result = generate_daily_ledger(entry_date)
#         return Response(result, status=200)


# class PayoutListCreateAPIView(AdminOnlyAPIView):
#     def get(self, request):
#         qs = Payout.objects.all().select_related("investment", "investor")
#         return page_response(request, qs, PayoutSerializer)

#     @transaction.atomic
#     def post(self, request):
#         serializer = PayoutSerializer(data=request.data)
#         serializer.is_valid(raise_exception=True)
#         payout = serializer.save(created_by=request.user)
#         investment = payout.investment
#         # Diamond payouts are company <-> investor transactions and must not
#         # appear in the Business/Admin Ledger. The Payout record itself is
#         # retained for the Diamond personal payout history.
#         if investment.scheme.category != Scheme.Categories.DIAMOND:
#             LedgerEntry.objects.create(
#                 business=investment.business,
#                 investment=investment,
#                 investor=investment.investor,
#                 entry_date=payout.payout_date,
#                 entry_type=LedgerEntry.EntryTypes.DEBIT,
#                 amount=payout.amount,
#                 description="Investor payout.",
#                 reference=f"PAYOUT-{payout.pk}",
#             )
#         audit(request.user, "PAYOUT", "LEDGER", payout, "Investor payout recorded.")
#         return Response(PayoutSerializer(payout).data, status=201)


# class CertificateListCreateAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser, JSONParser]

#     def get(self, request):
#         qs = Certificate.objects.filter(deleted_at__isnull=True).select_related(
#             "investment", "investment__investor", "investment__business", "investment__scheme"
#         ).prefetch_related("attachments")
#         return page_response(request, qs, CertificateSerializer)

#     @transaction.atomic
#     def post(self, request):
#         investment = Investment.objects.filter(
#             pk=request.data.get("investment"), deleted_at__isnull=True
#         ).select_related("investor", "business", "scheme").first()
#         if not investment:
#             return Response({"detail": "Investment not found."}, status=404)

#         # The standard certificate design is generated below after attachments are saved.
#         certificate = Certificate.objects.filter(investment=investment).first()
#         if certificate:
#             certificate.deleted_at = None
#             certificate.version += 1
#             certificate.created_by = request.user
#             certificate.save(update_fields=["deleted_at", "html_content", "version", "created_by", "updated_at"])
#             operation = "UPDATE"
#         else:
#             certificate = Certificate.objects.create(
#                 investment=investment, html_content="", created_by=request.user
#             )
#             operation = "CREATE"

#         files = request.FILES.getlist("attachments")
#         titles = request.data.getlist("attachment_titles") if hasattr(request.data, "getlist") else []
#         for index, file in enumerate(files):
#             title = titles[index] if index < len(titles) else file.name
#             CertificateAttachment.objects.create(
#                 certificate=certificate,
#                 title=title or file.name,
#                 file=file,
#                 sort_order=index,
#             )

#         certificate.html_content = build_default_certificate_html(
#             investment,
#             certificate.attachments.filter(deleted_at__isnull=True),
#             request=request,
#         )
#         certificate.save(update_fields=["html_content", "updated_at"])

#         audit(
#             request.user, operation, "CERTIFICATE", certificate,
#             "Investment certificate generated with attachments."
#         )
#         return Response(
#             CertificateSerializer(certificate, context={"request": request}).data,
#             status=201 if operation == "CREATE" else 200,
#         )


# class CertificateDetailAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser, JSONParser]

#     def get(self, request, pk):
#         obj = Certificate.objects.filter(pk=pk, deleted_at__isnull=True).prefetch_related("attachments").first()
#         if not obj:
#             return Response({"detail": "Certificate not found."}, status=404)
#         return Response(CertificateSerializer(obj, context={"request": request}).data)

#     def put(self, request, pk):
#         obj = Certificate.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not obj:
#             return Response({"detail": "Certificate not found."}, status=404)

#         obj.version += 1
#         obj.created_by = request.user
#         obj.save(update_fields=["version", "created_by", "updated_at"])

#         files = request.FILES.getlist("attachments")
#         titles = request.data.getlist("attachment_titles") if hasattr(request.data, "getlist") else []
#         start_order = obj.attachments.count()
#         for index, file in enumerate(files):
#             title = titles[index] if index < len(titles) else file.name
#             CertificateAttachment.objects.create(
#                 certificate=obj, title=title or file.name, file=file, sort_order=start_order + index
#             )

#         obj.html_content = build_default_certificate_html(
#             obj.investment,
#             obj.attachments.filter(deleted_at__isnull=True),
#             request=request,
#         )
#         obj.save(update_fields=["html_content", "updated_at"])

#         audit(request.user, "UPDATE", "CERTIFICATE", obj, "Certificate updated.")
#         return Response(CertificateSerializer(obj, context={"request": request}).data)

#     def delete(self, request, pk):
#         obj = Certificate.objects.filter(pk=pk, deleted_at__isnull=True).first()
#         if not obj:
#             return Response({"detail": "Certificate not found."}, status=404)
#         obj.soft_delete()
#         audit(request.user, "DELETE", "CERTIFICATE", obj, "Certificate moved to restore bin.")
#         return Response(status=204)


# class CertificateAttachmentDeleteAPIView(AdminOnlyAPIView):
#     def delete(self, request, pk, attachment_id):
#         attachment = CertificateAttachment.objects.filter(
#             pk=attachment_id, certificate_id=pk
#         ).first()
#         if not attachment:
#             return Response({"detail": "Attachment not found."}, status=404)
#         attachment.soft_delete()
#         audit(request.user, "DELETE", "CERTIFICATE_ATTACHMENT", attachment, "Certificate attachment moved to restore bin.")
#         return Response(status=204)


# ACCOUNTANT_PERMISSION_DEFAULTS = {
#     "business_add": True, "business_view": True, "business_edit": True, "business_delete": True,
#     "investor_add": True, "investor_view": True, "investor_edit": True, "investor_delete": True,
#     "scheme_add": True, "scheme_view": True, "scheme_edit": True, "scheme_delete": True,
#     "kuber_investment_add": True, "kuber_investment_view": True, "kuber_investment_edit": True,
#     "kuber_investment_delete": True,
#     "investment_add": True, "investment_view": True, "investment_edit": True, "investment_delete": True,
#     "balance_sheet_view": True, "balance_sheet_upload": True, "balance_sheet_edit": True,
#     "ledger_view": True, "ledger_generate": True,
#     "payout_view": True, "payout_create": True,
#     "certificate_view": True, "certificate_generate": True,
#     "audit_view": True, "restore": True,
# }

# class AccountantListCreateAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser, JSONParser]

#     def get(self, request):
#         qs = User.objects.filter(
#             role=User.Roles.ACCOUNTANT
#         ).prefetch_related(
#             "accountant_detail_fields",
#             "accountant_documents"
#         ).order_by("-date_joined")
#         return page_response(request, qs, UserOutputSerializer)

#     @transaction.atomic
#     def post(self, request):
#         data = request.data.copy()
#         data["role"] = User.Roles.ACCOUNTANT

#         identity_details = request.data.get("identity_details", "[]")
#         if isinstance(identity_details, str):
#             try:
#                 identity_details = json.loads(identity_details)
#             except json.JSONDecodeError:
#                 return Response({"detail": "identity_details must be valid JSON."}, status=400)

#         serializer = UserCreateSerializer(data=data)
#         serializer.is_valid(raise_exception=True)
#         user = serializer.save()

#         image = request.FILES.get("image")
#         if image:
#             user.image = image
#             user.save(update_fields=["image"])

#         for item in identity_details:
#             if not isinstance(item, dict) or not str(item.get("key", "")).strip():
#                 return Response({"detail": "Each identity detail must contain a key."}, status=400)
#             AccountantDetailField.objects.create(
#                 accountant=user, key=str(item["key"]).strip(), value=str(item.get("value", ""))
#             )

#         files = request.FILES.getlist("identity_files")
#         titles = request.data.getlist("identity_files_titles") if hasattr(request.data, "getlist") else []
#         for index, file in enumerate(files):
#             title = titles[index] if index < len(titles) else file.name
#             AccountantDocument.objects.create(
#                 accountant=user, title=title or file.name, file=file, uploaded_by=request.user
#             )

#         permissions = request.data.get("permissions") or {}
#         if isinstance(permissions, str):
#             try:
#                 permissions = json.loads(permissions)
#             except json.JSONDecodeError:
#                 permissions = {}

#         AccountantPermission.objects.update_or_create(
#             user=user,
#             defaults={"permissions": {**ACCOUNTANT_PERMISSION_DEFAULTS, **permissions}}
#         )
#         audit(request.user, "CREATE", "ACCOUNTANT", user, "Accountant created.")

#         response_data = UserOutputSerializer(user, context={"request": request}).data
#         response_data["identity_details"] = list(user.accountant_detail_fields.values("id", "key", "value"))
#         response_data["identity_files"] = AccountantDocumentSerializer(
#             user.accountant_documents.all(), many=True, context={"request": request}
#         ).data
#         response_data["permissions"] = user.accountant_permissions.permissions
#         return Response(response_data, status=201)


# class AccountantDetailAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser, JSONParser]

#     def put(self, request, pk):
#         user = User.objects.filter(pk=pk, role=User.Roles.ACCOUNTANT).first()
#         if not user:
#             return Response({"detail": "Accountant not found."}, status=404)

#         allowed = {"email", "first_name", "last_name", "phone", "is_active"}
#         for field in allowed:
#             if field in request.data:
#                 setattr(user, field, request.data[field])
#         if request.data.get("password"):
#             user.set_password(request.data["password"])
#         if "image" in request.FILES:
#             user.image = request.FILES["image"]
#         user.save()

#         if "identity_details" in request.data:
#             identity_details = request.data.get("identity_details")
#             if isinstance(identity_details, str):
#                 try:
#                     identity_details = json.loads(identity_details)
#                 except json.JSONDecodeError:
#                     return Response({"detail": "identity_details must be valid JSON."}, status=400)
#             AccountantDetailField.objects.filter(accountant=user).delete()
#             for item in identity_details or []:
#                 if not isinstance(item, dict) or not str(item.get("key", "")).strip():
#                     return Response({"detail": "Each identity detail must contain a key."}, status=400)
#                 AccountantDetailField.objects.create(
#                     accountant=user, key=str(item["key"]).strip(), value=str(item.get("value", ""))
#                 )

#         files = request.FILES.getlist("identity_files")
#         titles = request.data.getlist("identity_files_titles") if hasattr(request.data, "getlist") else []
#         for index, file in enumerate(files):
#             title = titles[index] if index < len(titles) else file.name
#             AccountantDocument.objects.create(
#                 accountant=user, title=title or file.name, file=file, uploaded_by=request.user
#             )

#         if "permissions" in request.data:
#             permissions = request.data.get("permissions") or {}
#             if isinstance(permissions, str):
#                 try:
#                     permissions = json.loads(permissions)
#                 except json.JSONDecodeError:
#                     permissions = {}
#             AccountantPermission.objects.update_or_create(
#                 user=user, defaults={"permissions": {**ACCOUNTANT_PERMISSION_DEFAULTS, **permissions}}
#             )

#         audit(request.user, "UPDATE", "ACCOUNTANT", user, "Accountant updated.")
#         response_data = UserOutputSerializer(user, context={"request": request}).data
#         response_data["identity_details"] = list(user.accountant_detail_fields.values("id", "key", "value"))
#         response_data["identity_files"] = AccountantDocumentSerializer(
#             user.accountant_documents.all(), many=True, context={"request": request}
#         ).data
#         response_data["permissions"] = getattr(user, "accountant_permissions", None).permissions if hasattr(user, "accountant_permissions") else {}
#         return Response(response_data)

#     def delete(self, request, pk):
#         user = User.objects.filter(pk=pk, role=User.Roles.ACCOUNTANT).first()
#         if not user:
#             return Response({"detail": "Accountant not found."}, status=404)
#         user.is_active = False
#         user.save(update_fields=["is_active"])
#         audit(request.user, "DELETE", "ACCOUNTANT", user, "Accountant deactivated.")
#         return Response(status=204)

# class AccountantPermissionAPIView(AdminOnlyAPIView):
#     def get(self, request, pk):
#         user = User.objects.filter(pk=pk, role=User.Roles.ACCOUNTANT).first()
#         if not user:
#             return Response({"detail": "Accountant not found."}, status=404)
#         obj, _ = AccountantPermission.objects.get_or_create(user=user)
#         return Response(AccountantPermissionSerializer(obj).data)

#     def put(self, request, pk):
#         user = User.objects.filter(pk=pk, role=User.Roles.ACCOUNTANT).first()
#         if not user:
#             return Response({"detail": "Accountant not found."}, status=404)
#         obj, _ = AccountantPermission.objects.get_or_create(user=user)
#         serializer = AccountantPermissionSerializer(obj, data=request.data, partial=True)
#         serializer.is_valid(raise_exception=True)
#         obj = serializer.save()
#         audit(request.user, "UPDATE", "ACCOUNTANT_PERMISSION", obj, "Accountant permissions updated.")
#         return Response(serializer.data)


# class BusinessDocumentListAPIView(AdminOnlyAPIView):
#     def get(self, request, business_id):
#         qs = BusinessDocument.objects.filter(business_id=business_id).select_related("uploaded_by")
#         return page_response(request, qs, BusinessDocumentSerializer)


# class RecurringContributionListCreateAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser, JSONParser]

#     def get(self, request, investment_id):
#         qs = RecurringContribution.objects.filter(investment_id=investment_id, deleted_at__isnull=True)
#         return page_response(request, qs, RecurringContributionSerializer)

#     @transaction.atomic
#     def post(self, request, investment_id):
#         investment = Investment.objects.filter(
#             pk=investment_id, deleted_at__isnull=True
#         ).select_related("scheme").first()
#         if not investment or investment.scheme.category != Scheme.Categories.PLATINUM:
#             return Response({"detail": "A valid Platinum investment is required."}, status=400)
#         data = request.data.copy()
#         data["investment"] = investment.pk
#         serializer = RecurringContributionSerializer(
#             data=data, context={"investment": investment}
#         )
#         serializer.is_valid(raise_exception=True)
#         obj = serializer.save(created_by=request.user)
#         audit(request.user, "CREATE", "RECURRING_CONTRIBUTION", obj, "Platinum monthly contribution recorded.")
#         return Response(RecurringContributionSerializer(obj).data, status=201)


# class RecurringContributionDetailAPIView(AdminOnlyAPIView):
#     parser_classes = [MultiPartParser, FormParser, JSONParser]

#     def put(self, request, investment_id, pk):
#         obj = RecurringContribution.objects.filter(
#             pk=pk, investment_id=investment_id, deleted_at__isnull=True
#         ).first()
#         if not obj:
#             return Response({"detail": "Contribution not found."}, status=404)
#         serializer = RecurringContributionSerializer(obj, data=request.data, partial=True)
#         serializer.is_valid(raise_exception=True)
#         obj = serializer.save()
#         audit(request.user, "UPDATE", "RECURRING_CONTRIBUTION", obj, "Platinum monthly contribution updated.")
#         return Response(RecurringContributionSerializer(obj).data)

#     def delete(self, request, investment_id, pk):
#         obj = RecurringContribution.objects.filter(
#             pk=pk, investment_id=investment_id, deleted_at__isnull=True
#         ).first()
#         if not obj:
#             return Response({"detail": "Contribution not found."}, status=404)
#         obj.soft_delete()
#         audit(request.user, "DELETE", "RECURRING_CONTRIBUTION", obj, "Platinum monthly contribution moved to restore bin.")
#         return Response(status=204)


# class AuditLogListAPIView(AdminOnlyAPIView):
#     def get(self, request):
#         qs = AuditLog.objects.all().select_related("actor")
#         if request.query_params.get("operation"):
#             qs = qs.filter(operation=request.query_params["operation"])
#         if request.query_params.get("date"):
#             qs = qs.filter(created_at__date=request.query_params["date"])
#         if request.query_params.get("module"):
#             qs = qs.filter(module=request.query_params["module"])
#         return page_response(request, qs, AuditLogSerializer)


# class RestoreBinAPIView(AdminOnlyAPIView):
#     model_map = {
#         "business": Business,
#         "investor": Investor,
#         "scheme": Scheme,
#         "kuber-investment": KuberBusinessInvestment,
#         "investment": Investment,
#         "balance-sheet": BalanceSheet,
#         "daily-financial-statement": DailyFinancialStatement,
#         "certificate": Certificate,
#         "certificate-attachment": CertificateAttachment,
#         "recurring-contribution": RecurringContribution,
#     }

#     def get(self, request):
#         result = {}
#         cutoff = timezone.now() - timedelta(days=30)
#         for key, model in self.model_map.items():
#             qs = model.objects.filter(deleted_at__isnull=False, deleted_at__gte=cutoff)
#             result[key] = [
#                 {
#                     "id": o.pk,
#                     "deleted_at": o.deleted_at,
#                     "label": str(o),
#                 } for o in qs[:100]
#             ]
#         return Response(result)


# class RestoreItemAPIView(AdminOnlyAPIView):
#     def post(self, request, resource, pk):
#         model = RestoreBinAPIView.model_map.get(resource)
#         if not model:
#             return Response({"detail": "Unsupported restore resource."}, status=400)
#         cutoff = timezone.now() - timedelta(days=30)
#         obj = model.objects.filter(pk=pk, deleted_at__isnull=False, deleted_at__gte=cutoff).first()
#         if not obj:
#             return Response({"detail": "Item not found or restore window expired."}, status=404)
#         obj.restore()
#         audit(request.user, "RESTORE", resource.upper(), obj, "Item restored.")
#         return Response({"detail": "Restored successfully.", "id": obj.pk})

# from io import BytesIO

# from django.http import HttpResponse

# from rest_framework.response import Response

# from openpyxl import Workbook
# class ExportLedgerAPIView(AdminOnlyAPIView):
#     """Export the same ledger report shown in the Admin Panel."""

#     def _queryset(self, request):
#         qs = (
#             LedgerEntry.objects
#             .all()
#             .select_related(
#                 "business",
#                 "investor",
#                 "investment",
#                 "kuber_investment",
#                 "investment__scheme",
#             )
#             .exclude(investment__scheme__category=Scheme.Categories.DIAMOND)
#             .order_by("entry_date", "id")
#         )

#         investment_id = request.query_params.get("investment")

#         if investment_id:
#             qs = qs.filter(
#                 investment_id=investment_id,
#                 entry_type__in=[
#                     LedgerEntry.EntryTypes.SOFT_DEBIT,
#                     LedgerEntry.EntryTypes.DEBIT,
#                 ],
#             )

#         business_id = request.query_params.get("business")
#         investor_id = request.query_params.get("investor")
#         entry_type = request.query_params.get("entry_type")
#         entry_date = request.query_params.get("date")

#         if business_id:
#             qs = qs.filter(business_id=business_id)

#         if investor_id:
#             qs = qs.filter(investor_id=investor_id)

#         if entry_type:
#             qs = qs.filter(entry_type=entry_type)

#         if entry_date:
#             qs = qs.filter(entry_date=entry_date)

#         return qs

#     @staticmethod
#     def _row(e):
#         return [
#             str(e.entry_date),
#             e.entry_type,
#             e.investor.name if e.investor else "—",
#             e.business.name if e.business else "—",
#             str(e.amount),
#             f"{e.annual_rate}% p.a."
#             if e.annual_rate is not None
#             else "—",
#             (
#                 e.investment.scheme.get_category_display()
#                 if e.investment
#                 else "—"
#             ),
#             e.description or "",
#             e.reference or "",
#         ]

#     def get(self, request):
#         # IMPORTANT:
#         # Use export_format instead of format so DRF does not
#         # interpret "format=pdf" as a renderer format.
#         fmt = (
#             request.query_params
#             .get("export_format", "xlsx")
#             .lower()
#             .strip()
#         )

#         qs = self._queryset(request)
#         rows = [self._row(e) for e in qs]

#         headers = [
#             "Date",
#             "Transaction Type",
#             "Investor",
#             "Business",
#             "Amount",
#             "Rate",
#             "Scheme Category",
#             "Description",
#             "Reference",
#         ]

#         try:
#             # =========================================================
#             # PDF EXPORT
#             # =========================================================
#             if fmt == "pdf":
#                 from reportlab.lib import colors
#                 from reportlab.lib.pagesizes import A4, landscape
#                 from reportlab.lib.styles import (
#                     getSampleStyleSheet,
#                     ParagraphStyle,
#                 )
#                 from reportlab.platypus import (
#                     SimpleDocTemplate,
#                     Table,
#                     TableStyle,
#                     Paragraph,
#                     Spacer,
#                 )

#                 output = BytesIO()

#                 doc = SimpleDocTemplate(
#                     output,
#                     pagesize=landscape(A4),
#                     rightMargin=18,
#                     leftMargin=18,
#                     topMargin=18,
#                     bottomMargin=18,
#                 )

#                 styles = getSampleStyleSheet()

#                 body = ParagraphStyle(
#                     "LedgerBody",
#                     parent=styles["BodyText"],
#                     fontSize=6.5,
#                     leading=8,
#                     spaceAfter=0,
#                 )

#                 head = ParagraphStyle(
#                     "LedgerHead",
#                     parent=styles["BodyText"],
#                     fontSize=7,
#                     leading=8,
#                     textColor=colors.white,
#                 )

#                 data = [
#                     [
#                         Paragraph(str(header), head)
#                         for header in headers
#                     ]
#                 ]

#                 for row in rows:
#                     data.append(
#                         [
#                             Paragraph(
#                                 str(value)
#                                 .replace("&", "&amp;")
#                                 .replace("<", "&lt;")
#                                 .replace(">", "&gt;"),
#                                 body,
#                             )
#                             for value in row
#                         ]
#                     )

#                 table = Table(
#                     data,
#                     repeatRows=1,
#                     colWidths=[
#                         48,
#                         68,
#                         80,
#                         80,
#                         58,
#                         58,
#                         68,
#                         145,
#                         105,
#                     ],
#                     hAlign="LEFT",
#                 )

#                 table.setStyle(
#                     TableStyle(
#                         [
#                             (
#                                 "BACKGROUND",
#                                 (0, 0),
#                                 (-1, 0),
#                                 colors.HexColor("#172033"),
#                             ),
#                             (
#                                 "TEXTCOLOR",
#                                 (0, 0),
#                                 (-1, 0),
#                                 colors.white,
#                             ),
#                             (
#                                 "GRID",
#                                 (0, 0),
#                                 (-1, -1),
#                                 0.35,
#                                 colors.grey,
#                             ),
#                             (
#                                 "VALIGN",
#                                 (0, 0),
#                                 (-1, -1),
#                                 "TOP",
#                             ),
#                             (
#                                 "FONTSIZE",
#                                 (0, 0),
#                                 (-1, -1),
#                                 7,
#                             ),
#                             (
#                                 "LEFTPADDING",
#                                 (0, 0),
#                                 (-1, -1),
#                                 4,
#                             ),
#                             (
#                                 "RIGHTPADDING",
#                                 (0, 0),
#                                 (-1, -1),
#                                 4,
#                             ),
#                             (
#                                 "TOPPADDING",
#                                 (0, 0),
#                                 (-1, -1),
#                                 4,
#                             ),
#                             (
#                                 "BOTTOMPADDING",
#                                 (0, 0),
#                                 (-1, -1),
#                                 4,
#                             ),
#                         ]
#                     )
#                 )

#                 elements = [
#                     Paragraph(
#                         "Kuber Trobe — Ledger Report",
#                         styles["Title"],
#                     ),
#                     Spacer(1, 8),
#                     table,
#                 ]

#                 doc.build(elements)

#                 response = HttpResponse(
#                     output.getvalue(),
#                     content_type="application/pdf",
#                 )

#                 response["Content-Disposition"] = (
#                     'attachment; filename="kuber_trobe_ledger.pdf"'
#                 )

#                 return response

#             # =========================================================
#             # INVALID FORMAT
#             # =========================================================
#             if fmt != "xlsx":
#                 return Response(
#                     {
#                         "detail": (
#                             "export_format must be xlsx or pdf."
#                         )
#                     },
#                     status=400,
#                 )

#             # =========================================================
#             # EXCEL / XLSX EXPORT
#             # =========================================================
#             output = BytesIO()

#             wb = Workbook()
#             ws = wb.active
#             ws.title = "Ledger"

#             # Header
#             ws.append(headers)

#             # Data
#             for row in rows:
#                 ws.append(row)

#             # Freeze header
#             ws.freeze_panes = "A2"

#             # Filter
#             ws.auto_filter.ref = ws.dimensions

#             # Column widths
#             widths = [
#                 14,
#                 20,
#                 24,
#                 24,
#                 16,
#                 16,
#                 18,
#                 48,
#                 34,
#             ]

#             for index, width in enumerate(widths, start=1):
#                 ws.column_dimensions[
#                     chr(64 + index)
#                 ].width = width

#             # Save workbook
#             wb.save(output)

#             response = HttpResponse(
#                 output.getvalue(),
#                 content_type=(
#                     "application/"
#                     "vnd.openxmlformats-officedocument."
#                     "spreadsheetml.sheet"
#                 ),
#             )

#             response["Content-Disposition"] = (
#                 'attachment; filename="kuber_trobe_ledger.xlsx"'
#             )

#             return response

#         # =============================================================
#         # MISSING DEPENDENCY
#         # =============================================================
#         except ImportError as exc:
#             return Response(
#                 {
#                     "detail": (
#                         f"Export dependency missing: {exc}. "
#                         "Run pip install -r requirements.txt"
#                     )
#                 },
#                 status=500,
#             )

#         # =============================================================
#         # GENERAL EXPORT ERROR
#         # =============================================================
#         except Exception as exc:
#             return Response(
#                 {
#                     "detail": f"Ledger export failed: {exc}"
#                 },
#                 status=500,
#             )


# class ExportInvestmentsAPIView(AdminOnlyAPIView):
#     def get(self, request):
#         qs = Investment.objects.filter(deleted_at__isnull=True).select_related("investor","business","scheme")
#         wb = Workbook()
#         ws = wb.active
#         ws.title = "Investments"
#         ws.append(["Investor", "Business", "Amount", "Scheme Category", "Scheme", "Rate % p.a.", "Investment Date", "Maturity Date"])
#         for i in qs:
#             ws.append([
#                 i.investor.name, i.business.name, float(i.amount or 0), i.scheme.category,
#                 i.scheme.name, float(i.investor_annual_rate), str(i.investment_date),
#                 str(i.maturity_date or ""),
#             ])
#         response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
#         response["Content-Disposition"] = 'attachment; filename="kuber_trobe_investments.xlsx"'
#         wb.save(response)
#         return response


# # =========================================================
# # INVESTOR PORTAL
# # =========================================================

# class InvestorPortalProfileAPIView(APIView):
#     permission_classes = [IsAuthenticated]

#     def get(self, request):
#         investor = Investor.objects.filter(
#             user=request.user,
#             deleted_at__isnull=True
#         ).first()

#         if not investor:
#             return Response(
#                 {"detail": "Investor account is not linked to an Investor record."},
#                 status=404
#             )

#         serializer = InvestorProfileSerializer(investor)
#         return Response(serializer.data)


# class InvestorPortalInvestmentsAPIView(APIView):
#     permission_classes = [IsAuthenticated]

#     def get(self, request):
#         investor = Investor.objects.filter(
#             user=request.user,
#             deleted_at__isnull=True
#         ).first()

#         if not investor:
#             return Response(
#                 {"detail": "Investor account is not linked to an Investor record."},
#                 status=404
#             )

#         investments = (
#             Investment.objects
#             .filter(
#                 investor=investor,
#                 deleted_at__isnull=True
#             )
#             .select_related(
#                 "investor",
#                 "business",
#                 "scheme"
#             )
#             .prefetch_related("payouts")
#             .order_by("-investment_date")
#         )

#         serializer = InvestorInvestmentSerializer(
#             investments,
#             many=True
#         )

#         return Response({
#             "count": investments.count(),
#             "results": serializer.data
#         })


# # =========================================================
# # INVESTOR DASHBOARD
# # =========================================================

# class InvestorPortalDashboardAPIView(APIView):
#     permission_classes = [IsAuthenticated]

#     def get(self, request):
#         investor = Investor.objects.filter(
#             user=request.user,
#             deleted_at__isnull=True
#         ).first()

#         if not investor:
#             return Response(
#                 {
#                     "detail": (
#                         "Investor account is not linked "
#                         "to an Investor record."
#                     )
#                 },
#                 status=404
#             )

#         investments = (
#             Investment.objects
#             .filter(
#                 investor=investor,
#                 deleted_at__isnull=True
#             )
#             .select_related(
#                 "business",
#                 "scheme"
#             )
#         )

#         investment_list = list(investments)

#         total_investment = Decimal("0")
#         total_interest = Decimal("0")
#         total_maturity = Decimal("0")
#         total_premature = Decimal("0")

#         business_map = {}
#         scheme_map = {}

#         for investment in investment_list:

#             # -----------------------------
#             # CAPITAL
#             # -----------------------------
#             if investment.scheme.category == Scheme.Categories.PLATINUM:

#                 contributed = (
#                     investment.recurring_contributions
#                     .filter(deleted_at__isnull=True)
#                     .aggregate(
#                         total=Sum("amount")
#                     )["total"]
#                     or Decimal("0")
#                 )

#             else:
#                 contributed = (
#                     investment.amount
#                     or Decimal("0")
#                 )

#             # -----------------------------
#             # INTEREST
#             # -----------------------------
#             try:
#                 interest = (
#                     diamond_interest_till_date(investment)
#                     if investment.scheme.category == Scheme.Categories.DIAMOND
#                     else interest_till_date(investment)
#                 )
#             except (ValueError, TypeError):
#                 interest = Decimal("0")

#             # -----------------------------
#             # MATURITY
#             # -----------------------------
#             try:
#                 maturity = maturity_value(investment)
#             except (ValueError, TypeError):
#                 maturity = Decimal("0")

#             # -----------------------------
#             # PREMATURE
#             # -----------------------------
#             try:
#                 premature = premature_value(investment)
#             except (ValueError, TypeError):
#                 premature = Decimal("0")

#             total_investment += Decimal(contributed)
#             total_interest += Decimal(interest)
#             total_maturity += Decimal(maturity)
#             total_premature += Decimal(premature)

#             # -----------------------------
#             # BUSINESS CHART
#             # -----------------------------
#             business_name = investment.business.name

#             if business_name not in business_map:
#                 business_map[business_name] = {
#                     "business_id": investment.business_id,
#                     "business_name": business_name,
#                     "investment": Decimal("0"),
#                     "interest": Decimal("0"),
#                 }

#             business_map[business_name]["investment"] += Decimal(
#                 contributed
#             )

#             business_map[business_name]["interest"] += Decimal(
#                 interest
#             )

#             # -----------------------------
#             # SCHEME CHART
#             # -----------------------------
#             scheme_name = investment.scheme.name
#             scheme_category = investment.scheme.get_category_display()

#             scheme_key = f"{scheme_category}-{scheme_name}"

#             if scheme_key not in scheme_map:
#                 scheme_map[scheme_key] = {
#                     "scheme_id": investment.scheme_id,
#                     "scheme_category": scheme_category,
#                     "scheme_name": scheme_name,
#                     "investment": Decimal("0"),
#                 }

#             scheme_map[scheme_key]["investment"] += Decimal(
#                 contributed
#             )

#         # -------------------------------------------------
#         # RETURN RESPONSE
#         # -------------------------------------------------

#         business_chart = []

#         for item in business_map.values():
#             business_chart.append({
#                 "business_id": item["business_id"],
#                 "business_name": item["business_name"],
#                 "investment": item["investment"],
#                 "interest": item["interest"],
#             })

#         scheme_chart = []

#         for item in scheme_map.values():
#             scheme_chart.append({
#                 "scheme_id": item["scheme_id"],
#                 "scheme_category": item["scheme_category"],
#                 "scheme_name": item["scheme_name"],
#                 "investment": item["investment"],
#             })

#         return Response({
#             "kpis": {
#                 "total_investment": total_investment,
#                 "total_interest": total_interest,
#                 "total_maturity": total_maturity,
#                 "total_premature": total_premature,
#                 "investment_count": len(investment_list),
#                 "business_count": len(business_map),
#             },

#             "charts": {
#                 "business_wise": business_chart,
#                 "scheme_wise": scheme_chart,
#                 "capital_vs_interest": {
#                     "capital": total_investment,
#                     "interest": total_interest,
#                 },
#             }
#         })


# # =========================================================
# # INVESTOR AVAILABLE BUSINESSES
# # =========================================================

# class InvestorPortalAvailableBusinessesAPIView(APIView):
#     permission_classes = [IsAuthenticated]

#     def get(self, request):

#         businesses = (
#             Business.objects
#             .filter(
#                 deleted_at__isnull=True
#             )
#             .order_by("name")
#         )

#         serializer = BusinessSerializer(
#             businesses,
#             many=True,
#             context={"request": request}
#         )

#         return Response({
#             "count": businesses.count(),
#             "results": serializer.data
#         })

# from .services import interest_till_date, premature_value, maturity_value
# # =========================================================
# # INVESTOR AVAILABLE SCHEMES
# # =========================================================

# class InvestorPortalAvailableSchemesAPIView(APIView):
#     permission_classes = [IsAuthenticated]

#     def get(self, request):

#         schemes = (
#             Scheme.objects
#             .filter(
#                 deleted_at__isnull=True,
#                 is_active=True
#             )
#             .order_by(
#                 "category",
#                 "tenure_months",
#                 "name"
#             )
#         )

#         serializer = SchemeSerializer(
#             schemes,
#             many=True
#         )

#         return Response({
#             "count": schemes.count(),
#             "results": serializer.data
#         })


# # =========================================================
# # INVESTOR EXCEL EXPORT
# # =========================================================

# class InvestorPortalExportAPIView(APIView):
#     permission_classes = [IsAuthenticated]

#     def get(self, request):

#         investor = Investor.objects.filter(
#             user=request.user,
#             deleted_at__isnull=True
#         ).first()

#         if not investor:
#             return Response(
#                 {
#                     "detail": (
#                         "Investor account is not linked "
#                         "to an Investor record."
#                     )
#                 },
#                 status=404
#             )

#         investments = (
#             Investment.objects
#             .filter(
#                 investor=investor,
#                 deleted_at__isnull=True
#             )
#             .select_related(
#                 "business",
#                 "scheme"
#             )
#             .order_by("-investment_date")
#         )

#         try:
#             from openpyxl import Workbook

#             wb = Workbook()
#             ws = wb.active
#             ws.title = "My Investments"

#             # -------------------------------------------------
#             # HEADER
#             # -------------------------------------------------

#             ws.append([
#                 "Investment ID",
#                 "Business",
#                 "Scheme Category",
#                 "Scheme Name",
#                 "Tenure (Months)",
#                 "Interest Rate % p.a.",
#                 "Investment Date",
#                 "Maturity Date",
#                 "Capital Invested",
#                 "Monthly Installment",
#                 "Total Contributed",
#                 "Interest Till Date",
#                 "Premature Amount",
#                 "Maturity Amount",
#                 "Status",
#             ])

#             # -------------------------------------------------
#             # DATA
#             # -------------------------------------------------

#             for investment in investments:

#                 if (
#                     investment.scheme.category
#                     == Scheme.Categories.PLATINUM
#                 ):
#                     total_contributed = (
#                         investment.recurring_contributions
#                         .filter(
#                             deleted_at__isnull=True
#                         )
#                         .aggregate(
#                             total=Sum("amount")
#                         )["total"]
#                         or Decimal("0")
#                     )
#                 else:
#                     total_contributed = (
#                         investment.amount
#                         or Decimal("0")
#                     )

#                 try:
#                     interest = (
#                         diamond_interest_till_date(investment)
#                         if investment.scheme.category == Scheme.Categories.DIAMOND
#                         else interest_till_date(investment)
#                     )
#                 except (ValueError, TypeError):
#                     interest = Decimal("0")

#                 try:
#                     premature = premature_value(
#                         investment
#                     )
#                 except (ValueError, TypeError):
#                     premature = None

#                 try:
#                     maturity = maturity_value(
#                         investment
#                     )
#                 except (ValueError, TypeError):
#                     maturity = None

#                 ws.append([
#                     investment.id,
#                     investment.business.name,
#                     investment.scheme.get_category_display(),
#                     investment.scheme.name,
#                     investment.scheme.tenure_months,
#                     float(
#                         investment.investor_annual_rate
#                         or 0
#                     ),
#                     str(investment.investment_date),
#                     str(investment.maturity_date or ""),
#                     float(
#                         investment.amount
#                         or 0
#                     ),
#                     float(
#                         investment.monthly_installment
#                         or 0
#                     ),
#                     float(total_contributed),
#                     float(interest),
#                     float(premature)
#                     if premature is not None
#                     else "",
#                     float(maturity)
#                     if maturity is not None
#                     else "",
#                     investment.get_status_display()
#                     if hasattr(
#                         investment,
#                         "get_status_display"
#                     )
#                     else investment.status,
#                 ])

#             # -------------------------------------------------
#             # FORMATTING
#             # -------------------------------------------------

#             ws.freeze_panes = "A2"
#             ws.auto_filter.ref = ws.dimensions

#             widths = [
#                 15,
#                 25,
#                 20,
#                 30,
#                 15,
#                 20,
#                 18,
#                 18,
#                 20,
#                 20,
#                 20,
#                 20,
#                 20,
#                 20,
#                 15,
#             ]

#             for index, width in enumerate(
#                 widths,
#                 start=1
#             ):
#                 ws.column_dimensions[
#                     chr(64 + index)
#                 ].width = width

#             # -------------------------------------------------
#             # RESPONSE
#             # -------------------------------------------------

#             response = HttpResponse(
#                 content_type=(
#                     "application/vnd.openxmlformats-"
#                     "officedocument.spreadsheetml.sheet"
#                 )
#             )

#             response[
#                 "Content-Disposition"
#             ] = (
#                 'attachment; '
#                 'filename="my_investments.xlsx"'
#             )

#             wb.save(response)

#             return response

#         except ImportError as exc:

#             return Response(
#                 {
#                     "detail": (
#                         f"Export dependency missing: {exc}. "
#                         "Run pip install openpyxl"
#                     )
#                 },
#                 status=500
#             )

#         except Exception as exc:

#             return Response(
#                 {
#                     "detail": f"Investor export failed: {exc}"
#                 },
#                 status=500
#             )


# # =========================================================
# # INVESTOR CERTIFICATES
# # =========================================================

# # =========================================================
# # INVESTOR CERTIFICATES
# # =========================================================

# class InvestorPortalCertificatesAPIView(APIView):
#     permission_classes = [IsAuthenticated]

#     def get(self, request):

#         investor = Investor.objects.filter(
#             user=request.user,
#             deleted_at__isnull=True
#         ).first()

#         if not investor:
#             return Response(
#                 {
#                     "detail": (
#                         "Investor account is not linked "
#                         "to an Investor record."
#                     )
#                 },
#                 status=404
#             )

#         certificates = (
#             Certificate.objects
#             .filter(
#                 investment__investor=investor,
#                 deleted_at__isnull=True
#             )
#             .select_related(
#                 "investment",
#                 "investment__business",
#                 "investment__scheme"
#             )
#             .prefetch_related("attachments")
#             .order_by("-created_at")
#         )

#         serializer = CertificateSerializer(
#             certificates,
#             many=True,
#             context={"request": request}
#         )

#         return Response({
#             "count": certificates.count(),
#             "results": serializer.data
#         })
# # =========================================================
# # DAILY FINANCIAL STATEMENTS
# # =========================================================

# def _accountant_financial_permission(request, action="view"):
#     """
#     Admin/superuser always has access.
#     Accountant access is controlled by AccountantPermission JSON.
#     """
#     user = request.user
#     if not user or not user.is_authenticated or not user.is_active:
#         return False
#     if getattr(user, "is_superuser", False) or getattr(user, "role", None) == User.Roles.ADMIN:
#         return True
#     if getattr(user, "role", None) != User.Roles.ACCOUNTANT:
#         return False

#     permission_key = {
#         "view": "financial_statement_view",
#         "add": "financial_statement_add",
#         "edit": "financial_statement_edit",
#         "delete": "financial_statement_delete",
#     }.get(action, "financial_statement_view")

#     permission_obj = getattr(user, "accountant_permissions", None)
#     if not permission_obj:
#         # Preserve the existing broad accountant behavior if the permission
#         # row has not yet been created.
#         return True
#     permissions = permission_obj.permissions or {}
#     return permissions.get(permission_key, True) is not False


# class BusinessPortalFinancialStatementsAPIView(BusinessPortalBaseAPIView):
#     """
#     Business users can:
#       GET  -> view their own daily financial statements
#       POST -> submit a new statement for their own business

#     Existing submitted statements are intentionally view-only for Business.
#     Admin/Accountant use the admin financial-statement endpoints for editing
#     and deletion.
#     """

#     def get(self, request):
#         business = Business.objects.filter(
#             user=request.user,
#             deleted_at__isnull=True
#         ).first()

#         if not business:
#             return Response(
#                 {"detail": "Business account is not linked to a Business record."},
#                 status=404
#             )

#         qs = (
#             DailyFinancialStatement.objects
#             .filter(
#                 business=business,
#                 deleted_at__isnull=True
#             )
#             .select_related("business", "submitted_by", "reviewed_by")
#             .order_by("-statement_date", "-id")
#         )

#         return page_response(
#             request,
#             qs,
#             DailyFinancialStatementSerializer
#         )

#     @transaction.atomic
#     def post(self, request):
#         business = Business.objects.filter(
#             user=request.user,
#             deleted_at__isnull=True
#         ).first()

#         if not business:
#             return Response(
#                 {"detail": "Business account is not linked to a Business record."},
#                 status=404
#             )

#         payload = request.data.copy()
#         payload["business"] = business.pk

#         serializer = DailyFinancialStatementSerializer(
#             data=payload,
#             context={"request": request}
#         )
#         serializer.is_valid(raise_exception=True)

#         # One live statement per business/date.
#         statement_date = serializer.validated_data.get("statement_date")
#         if DailyFinancialStatement.objects.filter(
#             business=business,
#             statement_date=statement_date,
#             deleted_at__isnull=True
#         ).exists():
#             return Response(
#                 {
#                     "detail": (
#                         "A daily financial statement already exists "
#                         "for this business and date."
#                     )
#                 },
#                 status=400
#             )

#         obj = serializer.save(
#             business=business,
#             submitted_by=request.user,
#             status=DailyFinancialStatement.Status.SUBMITTED
#         )

#         audit(
#             request.user,
#             "CREATE",
#             "DAILY_FINANCIAL_STATEMENT",
#             obj,
#             "Daily financial statement submitted by business user."
#         )

#         return Response(
#             DailyFinancialStatementSerializer(
#                 obj,
#                 context={"request": request}
#             ).data,
#             status=201
#         )


# # ============================================================
# # DAILY FINANCIAL STATEMENTS
# # ============================================================

# def _accountant_financial_permission(request, action="view"):
#     """
#     Admin / Superuser:
#         Full access.

#     Accountant:
#         Access controlled through AccountantPermission.

#     Supported actions:
#         view
#         add
#         edit
#         delete
#     """

#     user = request.user

#     if not user or not user.is_authenticated or not user.is_active:
#         return False

#     # Admin / Superuser
#     if (
#         getattr(user, "is_superuser", False)
#         or getattr(user, "role", None) == User.Roles.ADMIN
#     ):
#         return True

#     # Only Accountant reaches here
#     if getattr(user, "role", None) != User.Roles.ACCOUNTANT:
#         return False

#     permission_key = {
#         "view": "financial_statement_view",
#         "add": "financial_statement_add",
#         "edit": "financial_statement_edit",
#         "delete": "financial_statement_delete",
#     }.get(action, "financial_statement_view")

#     permission_obj = getattr(
#         user,
#         "accountant_permissions",
#         None
#     )

#     # Existing behaviour:
#     # if permission object doesn't exist, allow Accountant access.
#     if not permission_obj:
#         return True

#     permissions = permission_obj.permissions or {}

#     return permissions.get(permission_key, True) is not False


# class DailyFinancialStatementListCreateAPIView(APIView):
#     """
#     Admin / Accountant Daily Financial Statement API.

#     GET:
#         /daily-financial-statements/

#     Optional filters:
#         ?business=1
#         ?date=2026-09-27
#         ?status=SUBMITTED

#     POST:
#         Create a DailyFinancialStatement.
#     """

#     permission_classes = [IsAuthenticated]

#     def initial(self, request, *args, **kwargs):
#         super().initial(request, *args, **kwargs)

#         if not _accountant_financial_permission(request, "view"):
#             from rest_framework.exceptions import PermissionDenied

#             raise PermissionDenied(
#                 "You do not have permission to view daily financial statements."
#             )

#     # ========================================================
#     # GET
#     # ========================================================

#     def get(self, request):

#         qs = (
#             DailyFinancialStatement.objects
#             .filter(
#                 deleted_at__isnull=True
#             )
#             .select_related(
#                 "business",
#                 "submitted_by",
#                 "reviewed_by",
#             )
#             .order_by(
#                 "-statement_date",
#                 "-id",
#             )
#         )

#         # ----------------------------------------------------
#         # BUSINESS FILTER
#         # ----------------------------------------------------

#         business_id = request.query_params.get("business")

#         if business_id:
#             qs = qs.filter(
#                 business_id=business_id
#             )

#         # ----------------------------------------------------
#         # DATE FILTER
#         # ----------------------------------------------------

#         statement_date = request.query_params.get("date")

#         if statement_date:
#             qs = qs.filter(
#                 statement_date=statement_date
#             )

#         # ----------------------------------------------------
#         # STATUS FILTER
#         # ----------------------------------------------------

#         status_filter = request.query_params.get("status")

#         if status_filter:
#             qs = qs.filter(
#                 status=status_filter.upper()
#             )

#         return page_response(
#             request,
#             qs,
#             DailyFinancialStatementSerializer,
#         )

#     # ========================================================
#     # POST
#     # ========================================================

#     @transaction.atomic
#     def post(self, request):

#         if not _accountant_financial_permission(
#             request,
#             "add"
#         ):
#             from rest_framework.exceptions import PermissionDenied

#             raise PermissionDenied(
#                 "You do not have permission to add daily financial statements."
#             )

#         serializer = DailyFinancialStatementSerializer(
#             data=request.data,
#             context={
#                 "request": request
#             }
#         )

#         serializer.is_valid(
#             raise_exception=True
#         )

#         business = serializer.validated_data.get(
#             "business"
#         )

#         statement_date = serializer.validated_data.get(
#             "statement_date"
#         )

#         if not business:

#             return Response(
#                 {
#                     "detail": "business is required."
#                 },
#                 status=400
#             )

#         # ----------------------------------------------------
#         # DUPLICATE CHECK
#         # ----------------------------------------------------

#         exists = DailyFinancialStatement.objects.filter(
#             business=business,
#             statement_date=statement_date,
#             deleted_at__isnull=True,
#         ).exists()

#         if exists:

#             return Response(
#                 {
#                     "detail": (
#                         "A daily financial statement already "
#                         "exists for this business and date."
#                     )
#                 },
#                 status=400
#             )

#         # ----------------------------------------------------
#         # CREATE
#         # ----------------------------------------------------

#         obj = serializer.save(
#             submitted_by=request.user,
#             status=request.data.get(
#                 "status",
#                 DailyFinancialStatement.Status.SUBMITTED,
#             ),
#         )

#         audit(
#             request.user,
#             "CREATE",
#             "DAILY_FINANCIAL_STATEMENT",
#             obj,
#             "Daily financial statement created.",
#         )

#         return Response(
#             DailyFinancialStatementSerializer(
#                 obj,
#                 context={
#                     "request": request
#                 }
#             ).data,
#             status=201,
#         )


# class DailyFinancialStatementDetailAPIView(APIView):
#     """
#     Admin / Accountant:

#         GET
#         PUT
#         PATCH
#         DELETE
#     """

#     permission_classes = [IsAuthenticated]

#     # ========================================================
#     # GET OBJECT
#     # ========================================================

#     def get_object(self, pk):

#         return (
#             DailyFinancialStatement.objects
#             .filter(
#                 pk=pk,
#                 deleted_at__isnull=True,
#             )
#             .select_related(
#                 "business",
#                 "submitted_by",
#                 "reviewed_by",
#             )
#             .first()
#         )

#     # ========================================================
#     # GET
#     # ========================================================

#     def get(self, request, pk):

#         if not _accountant_financial_permission(
#             request,
#             "view"
#         ):
#             from rest_framework.exceptions import PermissionDenied

#             raise PermissionDenied(
#                 "You do not have permission to view daily financial statements."
#             )

#         obj = self.get_object(pk)

#         if not obj:

#             return Response(
#                 {
#                     "detail": "Daily financial statement not found."
#                 },
#                 status=404,
#             )

#         return Response(
#             DailyFinancialStatementSerializer(
#                 obj,
#                 context={
#                     "request": request
#                 }
#             ).data
#         )

#     # ========================================================
#     # PUT
#     # ========================================================

#     @transaction.atomic
#     def put(self, request, pk):

#         if not _accountant_financial_permission(
#             request,
#             "edit"
#         ):
#             from rest_framework.exceptions import PermissionDenied

#             raise PermissionDenied(
#                 "You do not have permission to edit daily financial statements."
#             )

#         obj = self.get_object(pk)

#         if not obj:

#             return Response(
#                 {
#                     "detail": "Daily financial statement not found."
#                 },
#                 status=404,
#             )

#         serializer = DailyFinancialStatementSerializer(
#             obj,
#             data=request.data,
#             partial=True,
#             context={
#                 "request": request
#             }
#         )

#         serializer.is_valid(
#             raise_exception=True
#         )

#         # ----------------------------------------------------
#         # PREVENT DUPLICATE BUSINESS + DATE
#         # ----------------------------------------------------

#         new_business = serializer.validated_data.get(
#             "business",
#             obj.business
#         )

#         new_date = serializer.validated_data.get(
#             "statement_date",
#             obj.statement_date
#         )

#         duplicate = (
#             DailyFinancialStatement.objects
#             .filter(
#                 business=new_business,
#                 statement_date=new_date,
#                 deleted_at__isnull=True,
#             )
#             .exclude(
#                 pk=obj.pk
#             )
#             .exists()
#         )

#         if duplicate:

#             return Response(
#                 {
#                     "detail": (
#                         "Another daily financial statement already "
#                         "exists for this business and date."
#                     )
#                 },
#                 status=400,
#             )

#         old_status = obj.status

#         obj = serializer.save()

#         # ----------------------------------------------------
#         # REVIEW INFORMATION
#         # ----------------------------------------------------

#         if (
#             obj.status
#             in {
#                 DailyFinancialStatement.Status.REVIEWED,
#                 DailyFinancialStatement.Status.APPROVED,
#                 DailyFinancialStatement.Status.REJECTED,
#             }
#             and (
#                 old_status != obj.status
#                 or obj.reviewed_by_id != request.user.id
#             )
#         ):

#             obj.reviewed_by = request.user
#             obj.reviewed_at = timezone.now()

#             obj.save(
#                 update_fields=[
#                     "reviewed_by",
#                     "reviewed_at",
#                     "updated_at",
#                 ]
#             )

#         audit(
#             request.user,
#             "UPDATE",
#             "DAILY_FINANCIAL_STATEMENT",
#             obj,
#             "Daily financial statement updated.",
#         )

#         return Response(
#             DailyFinancialStatementSerializer(
#                 obj,
#                 context={
#                     "request": request
#                 }
#             ).data
#         )

#     # ========================================================
#     # PATCH
#     # ========================================================

#     def patch(self, request, pk):

#         return self.put(
#             request,
#             pk
#         )

#     # ========================================================
#     # DELETE
#     # ========================================================

#     @transaction.atomic
#     def delete(self, request, pk):

#         if not _accountant_financial_permission(
#             request,
#             "delete"
#         ):
#             from rest_framework.exceptions import PermissionDenied

#             raise PermissionDenied(
#                 "You do not have permission to delete daily financial statements."
#             )

#         obj = self.get_object(pk)

#         if not obj:

#             return Response(
#                 {
#                     "detail": "Daily financial statement not found."
#                 },
#                 status=404,
#             )

#         obj.soft_delete()

#         audit(
#             request.user,
#             "DELETE",
#             "DAILY_FINANCIAL_STATEMENT",
#             obj,
#             "Daily financial statement moved to restore bin.",
#         )

#         return Response(
#             status=204
#         )


# class BusinessPerformanceDashboardAPIView(APIView):
#     """Recent P&L trend and latest Balance Sheet KPIs for an accessible business."""
#     permission_classes = [IsAuthenticated]

#     def get(self, request):
#         business_id = request.query_params.get("business")
#         if not business_id:
#             return Response({"detail": "business query parameter is required."}, status=400)

#         business = Business.objects.filter(id=business_id, deleted_at__isnull=True).first()
#         if not business:
#             return Response({"detail": "Business not found."}, status=404)

#         role = getattr(request.user, "role", None)
#         if role == User.Roles.BUSINESS:
#             if not Business.objects.filter(user=request.user, id=business.id, deleted_at__isnull=True).exists():
#                 return Response({"detail": "You do not have access to this business."}, status=403)
#         elif role not in (User.Roles.ADMIN, User.Roles.INVESTOR, User.Roles.ACCOUNTANT):
#             if not request.user.is_staff and not request.user.is_superuser:
#                 return Response({"detail": "You do not have access to this business."}, status=403)

#         try:
#             limit = max(3, min(int(request.query_params.get("limit", 12)), 24))
#         except (TypeError, ValueError):
#             limit = 12

#         pnl = list(
#             DailyFinancialStatement.objects.filter(
#                 business=business, deleted_at__isnull=True
#             ).order_by("-statement_date", "-id")[:limit]
#         )
#         pnl.reverse()
#         latest_pnl = pnl[-1] if pnl else None
#         previous_pnl = pnl[-2] if len(pnl) > 1 else None

#         balance_qs = BalanceSheet.objects.filter(
#             business=business, deleted_at__isnull=True
#         )
#         if latest_pnl:
#             balance_qs = balance_qs.filter(statement_date__lte=latest_pnl.statement_date)
#         balance = balance_qs.order_by("-statement_date", "-id").first()

#         def dec(value):
#             return Decimal(value or 0)

#         latest_profit = dec(latest_pnl.net_profit) if latest_pnl else Decimal("0.00")
#         previous_profit = dec(previous_pnl.net_profit) if previous_pnl else None

#         return Response({
#             "business": {
#                 "id": business.id,
#                 "name": business.name,
#                 "location": getattr(business, "location", ""),
#                 "owner_name": getattr(business, "owner_name", ""),
#             },
#             "kpis": {
#                 "latest_period": str(latest_pnl.statement_date) if latest_pnl else None,
#                 "revenue": dec(latest_pnl.revenue) if latest_pnl else Decimal("0.00"),
#                 "net_profit": latest_profit,
#                 "profit_margin": dec(latest_pnl.profit_margin) if latest_pnl else Decimal("0.00"),
#                 "total_equity": dec(balance.total_equity) if balance else Decimal("0.00"),
#                 "total_assets": dec(balance.total_assets) if balance else Decimal("0.00"),
#                 "total_liabilities": dec(balance.total_liabilities) if balance else Decimal("0.00"),
#                 "balance_sheet_date": str(balance.statement_date) if balance else None,
#                 "profit_change_from_previous_period": latest_profit - previous_profit if previous_profit is not None else None,
#                 "financial_status": latest_pnl.financial_status if latest_pnl else None,
#             },
#             "pnl_chart": [{
#                 "period": str(x.statement_date),
#                 "revenue": dec(x.revenue),
#                 "gross_profit": dec(x.gross_profit),
#                 "operating_profit": dec(x.operating_profit),
#                 "net_profit": dec(x.net_profit),
#                 "profit_margin": dec(x.profit_margin),
#                 "financial_status": x.financial_status,
#             } for x in pnl],
#         })


# class InvestorPortalBusinessPerformanceAPIView(APIView):
#     """
#     Investor-safe business performance.

#     Only businesses in which the logged-in investor has an active investment
#     are returned. Financial position fields such as assets/liabilities/equity
#     are deliberately not exposed here.
#     """

#     permission_classes = [IsAuthenticated]

#     def get(self, request):
#         investor = Investor.objects.filter(
#             user=request.user,
#             deleted_at__isnull=True
#         ).first()

#         if not investor:
#             return Response(
#                 {
#                     "detail": (
#                         "Investor account is not linked "
#                         "to an Investor record."
#                     )
#                 },
#                 status=404
#             )

#         business_ids = (
#             Investment.objects
#             .filter(
#                 investor=investor,
#                 deleted_at__isnull=True
#             )
#             .values_list("business_id", flat=True)
#             .distinct()
#         )

#         businesses = Business.objects.filter(
#             id__in=business_ids,
#             deleted_at__isnull=True
#         ).order_by("name")

#         results = []

#         for business in businesses:
#             statements = list(
#                 DailyFinancialStatement.objects
#                 .filter(
#                     business=business,
#                     deleted_at__isnull=True
#                 )
#                 .order_by("-statement_date", "-id")[:2]
#             )

#             if not statements:
#                 continue

#             latest = statements[0]
#             previous = statements[1] if len(statements) > 1 else None

#             latest_net_profit = Decimal(latest.net_profit or 0)
#             previous_net_profit = (
#                 Decimal(previous.net_profit or 0)
#                 if previous
#                 else None
#             )

#             change = None
#             if previous is not None:
#                 change = latest_net_profit - previous_net_profit

#             results.append({
#                 "business_id": business.id,
#                 "business_name": business.name,
#                 "period": str(latest.statement_date),
#                 "status": latest.financial_status,
#                 "net_profit": latest_net_profit,
#                 "profit_margin": latest.profit_margin,
#                 "updated_at": latest.updated_at,
#                 "change_from_previous_period": change,
#             })

#         return Response({
#             "count": len(results),
#             "results": results,
#         })


import json
from datetime import timedelta
from decimal import Decimal
from django.contrib.auth import authenticate
from django.core.mail import send_mail
from django.db import transaction
from django.db.models import Count, Sum, Q
from django.db.models import Sum, Max, Avg, Count
from django.contrib.auth.hashers import make_password, check_password
from django.utils import timezone
from rest_framework import status
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken
from openpyxl import Workbook
from django.http import HttpResponse
from io import BytesIO

from .models import (
    User, Business, BusinessBankDetail, BusinessDetailField, BusinessDocument,
    Investor, InvestorDetailField, InvestorDocument, AccountantDetailField, AccountantDocument, Scheme, KuberBusinessInvestment,
    Investment, BalanceSheet, DailyFinancialStatement, LedgerEntry, Payout, Certificate, CertificateAttachment,
    AuditLog, RecurringContribution, PasswordResetOTP, AccountantPermission
)
from .permissions import IsAdmin
from .serializers import (
    UserCreateSerializer, UserOutputSerializer, BusinessSerializer,
    BusinessDocumentSerializer, InvestorSerializer, InvestorDocumentSerializer,
    InvestorDetailFieldSerializer, AccountantDocumentSerializer, SchemeSerializer, KuberBusinessInvestmentSerializer,
    InvestmentSerializer, BalanceSheetSerializer, DailyFinancialStatementSerializer, LedgerEntrySerializer, PayoutSerializer,
    CertificateSerializer, CertificateAttachmentSerializer, AuditLogSerializer,
    RecurringContributionSerializer, AccountantPermissionSerializer,InvestorProfileSerializer,
    InvestorInvestmentSerializer
)
from .services import (
    audit, create_investment_with_snapshot, generate_daily_ledger,
    maturity_value, premature_value, build_default_certificate_html,
    diamond_interest_till_date
)


def page_response(request, queryset, serializer_class):
    from rest_framework.pagination import PageNumberPagination
    p = PageNumberPagination()
    p.page_size = 20
    p.page_size_query_param = "page_size"
    p.max_page_size = 500
    page = p.paginate_queryset(queryset, request)
    return p.get_paginated_response(serializer_class(page, many=True, context={"request": request}).data)


class AdminOnlyAPIView(APIView):
    permission_classes = [IsAdmin]


class LoginAPIView(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        username = request.data.get("username")
        password = request.data.get("password")
        user = authenticate(username=username, password=password)
        if not user or not user.is_active:
            return Response({"detail": "Invalid credentials."}, status=status.HTTP_401_UNAUTHORIZED)
        refresh = RefreshToken.for_user(user)
        return Response({
            "access": str(refresh.access_token),
            "refresh": str(refresh),
            "user": UserOutputSerializer(user, context={"request": request}).data,
        })




class IsBusinessUser(APIView):
    permission_classes = []

    def initial(self, request, *args, **kwargs):
        super().initial(request, *args, **kwargs)
        user = request.user
        if not user or not user.is_authenticated or not user.is_active or user.role != User.Roles.BUSINESS:
            from rest_framework.exceptions import PermissionDenied
            raise PermissionDenied("You do not have permission to perform this action.")


class BusinessPortalBaseAPIView(IsBusinessUser):
    pass


class BusinessPortalMeAPIView(BusinessPortalBaseAPIView):
    def get(self, request):
        business = Business.objects.filter(
            user=request.user, deleted_at__isnull=True
        ).prefetch_related("bank_detail_fields", "detail_fields", "uploaded_documents").first()
        if not business:
            return Response({"detail": "Business account is not linked to a Business record."}, status=404)
        return Response(BusinessSerializer(business, context={"request": request}).data)


class BusinessPortalDashboardAPIView(BusinessPortalBaseAPIView):
    def get(self, request):
        business = Business.objects.filter(user=request.user, deleted_at__isnull=True).first()
        if not business:
            return Response({"detail": "Business account is not linked to a Business record."}, status=404)

        investment_qs = Investment.objects.filter(business=business, deleted_at__isnull=True)
        ledger_qs = LedgerEntry.objects.filter(business=business)
        investors_count = investment_qs.values("investor_id").distinct().count()
        total_investment = investment_qs.filter(amount__isnull=False).aggregate(v=Sum("amount"))["v"] or Decimal("0")
        platinum_total = RecurringContribution.objects.filter(
            investment__business=business,
            investment__deleted_at__isnull=True,
            deleted_at__isnull=True,
        ).aggregate(v=Sum("amount"))["v"] or Decimal("0")
        ledger_qs = ledger_qs.exclude(investment__scheme__category=Scheme.Categories.DIAMOND)
        credit = ledger_qs.filter(entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT).aggregate(v=Sum("amount"))["v"] or Decimal("0")
        debit = ledger_qs.filter(entry_type=LedgerEntry.EntryTypes.DEBIT).aggregate(v=Sum("amount"))["v"] or Decimal("0")
        return Response({
            "business": BusinessSerializer(business, context={"request": request}).data,
            "kpis": {
                "investors": investors_count,
                "total_investment": total_investment + platinum_total,
                "credit_profit": credit,
                "debit": debit,
                "accumulated_funding": credit - debit,
            },
        })


class BusinessPortalInvestorsAPIView(BusinessPortalBaseAPIView):
    def get(self, request):
        business = Business.objects.filter(user=request.user, deleted_at__isnull=True).first()
        if not business:
            return Response({"detail": "Business account is not linked to a Business record."}, status=404)
        ids = Investment.objects.filter(business=business, deleted_at__isnull=True).values_list("investor_id", flat=True).distinct()
        qs = Investor.objects.filter(id__in=ids, deleted_at__isnull=True).order_by("name")
        return page_response(request, qs, InvestorSerializer)


class BusinessPortalInvestmentsAPIView(BusinessPortalBaseAPIView):
    def get(self, request):
        business = Business.objects.filter(user=request.user, deleted_at__isnull=True).first()
        if not business:
            return Response({"detail": "Business account is not linked to a Business record."}, status=404)
        qs = Investment.objects.filter(business=business, deleted_at__isnull=True).select_related("investor", "business", "scheme").prefetch_related("payouts")
        return page_response(request, qs, InvestmentSerializer)

from decimal import Decimal

from django.db.models import Q, Sum, DecimalField, Value
from django.db.models.functions import Coalesce
class BusinessPortalKuberInvestmentsAPIView(BusinessPortalBaseAPIView):
    """
    Read-only Kuber -> Business investments for the logged-in
    Business account.

    Interest is calculated from LedgerEntry CREDIT_PROFIT records
    belonging to each KuberBusinessInvestment.
    """

    def get(self, request):
        business = Business.objects.filter(
            user=request.user,
            deleted_at__isnull=True
        ).first()

        if not business:
            return Response(
                {
                    "detail": (
                        "Business account is not linked "
                        "to a Business record."
                    )
                },
                status=404
            )

        qs = (
            KuberBusinessInvestment.objects
            .filter(
                business=business,
                deleted_at__isnull=True
            )
            .select_related("business")
            .annotate(
                interest=Coalesce(
                    Sum(
                        "ledger_entries__amount",
                        filter=Q(
                            ledger_entries__entry_type=(
                                LedgerEntry.EntryTypes.CREDIT_PROFIT
                            )
                        )
                    ),
                    Value(Decimal("0.00")),
                    output_field=DecimalField(
                        max_digits=14,
                        decimal_places=2
                    )
                )
            )
            .order_by(
                "-investment_date",
                "-id"
            )
        )

        return page_response(
            request,
            qs,
            KuberBusinessInvestmentSerializer
        )


class BusinessPortalBalanceSheetsAPIView(BusinessPortalBaseAPIView):
    """
    Business balance-sheet endpoint.

    The Business UI intentionally submits one combined form containing:
      1. BalanceSheet fields
      2. DailyFinancialStatement fields

    Both records are persisted together for the same business + statement date.
    """

    def get(self, request):
        business = Business.objects.filter(
            user=request.user, deleted_at__isnull=True
        ).first()
        if not business:
            return Response({"detail": "Business account is not linked to a Business record."}, status=404)

        qs = BalanceSheet.objects.filter(
            business=business, deleted_at__isnull=True
        ).select_related("business", "entered_by").order_by("-statement_date", "-id")
        return page_response(request, qs, BalanceSheetSerializer)

    @transaction.atomic
    def post(self, request):
        business = Business.objects.filter(
            user=request.user, deleted_at__isnull=True
        ).first()
        if not business:
            return Response({"detail": "Business account is not linked to a Business record."}, status=404)

        payload = request.data.copy()
        payload["business"] = business.pk

        balance_fields = {
            "business", "statement_date",
            "cash_in_hand", "bank_balance", "accounts_receivable", "inventory",
            "prepaid_expenses", "other_current_assets", "fixed_assets",
            "accumulated_depreciation", "other_non_current_assets",
            "accounts_payable", "short_term_borrowings", "accrued_expenses",
            "taxes_payable", "other_current_liabilities", "long_term_loans",
            "other_non_current_liabilities", "owner_capital", "reserves_surplus",
            "retained_earnings", "drawings", "remarks",
        }
        financial_fields = {
            "business", "statement_date", "revenue", "direct_cost",
            "operating_expenses", "other_income", "finance_cost", "tax",
            "operating_cash_inflow", "operating_cash_outflow",
            "investing_cash_inflow", "investing_cash_outflow",
            "financing_cash_inflow", "financing_cash_outflow", "remarks",
        }

        balance_payload = {k: payload.get(k) for k in balance_fields if k in payload}
        balance_payload["business"] = business.pk
        balance_serializer = BalanceSheetSerializer(data=balance_payload, context={"request": request})
        balance_serializer.is_valid(raise_exception=True)

        statement_date = balance_serializer.validated_data["statement_date"]
        if BalanceSheet.objects.filter(
            business=business, statement_date=statement_date, deleted_at__isnull=True
        ).exists():
            return Response({"detail": "A balance sheet already exists for this business and date."}, status=400)

        financial_payload = {k: payload.get(k) for k in financial_fields if k in payload}
        financial_payload["business"] = business.pk
        financial_payload["statement_date"] = statement_date
        financial_serializer = DailyFinancialStatementSerializer(
            data=financial_payload, context={"request": request}
        )
        financial_serializer.is_valid(raise_exception=True)
        if DailyFinancialStatement.objects.filter(
            business=business, statement_date=statement_date, deleted_at__isnull=True
        ).exists():
            return Response({"detail": "A daily financial statement already exists for this business and date."}, status=400)

        balance = balance_serializer.save(entered_by=request.user)
        financial = financial_serializer.save(
            business=business, submitted_by=request.user,
            status=DailyFinancialStatement.Status.SUBMITTED
        )

        audit(request.user, "CREATE", "BALANCE_SHEET", balance, "Balance sheet uploaded by business user.")
        audit(request.user, "CREATE", "DAILY_FINANCIAL_STATEMENT", financial, "Daily financial statement uploaded with balance sheet.")

        return Response({
            "balance_sheet": BalanceSheetSerializer(balance, context={"request": request}).data,
            "daily_financial_statement": DailyFinancialStatementSerializer(financial, context={"request": request}).data,
        }, status=201)


class BusinessPortalLedgerAPIView(BusinessPortalBaseAPIView):
    def get(self, request):
        business = Business.objects.filter(user=request.user, deleted_at__isnull=True).first()
        if not business:
            return Response({"detail": "Business account is not linked to a Business record."}, status=404)
        qs = LedgerEntry.objects.filter(business=business).exclude(
            investment__scheme__category=Scheme.Categories.DIAMOND
        ).select_related("business", "investor", "investment", "kuber_investment")
        if request.query_params.get("entry_type"):
            qs = qs.filter(entry_type=request.query_params["entry_type"])
        if request.query_params.get("date"):
            qs = qs.filter(entry_date=request.query_params["date"])
        return page_response(request, qs, LedgerEntrySerializer)


class ForgotPasswordAPIView(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        email = request.data.get("email")
        user = User.objects.filter(email__iexact=email, is_active=True).first()
        # Do not reveal whether an email exists.
        if user:
            import secrets
            otp = f"{secrets.randbelow(1000000):06d}"
            PasswordResetOTP.objects.filter(user=user, used=False).update(used=True)
            PasswordResetOTP.objects.create(
                user=user,
                otp_hash=make_password(otp),
                expires_at=timezone.now() + timedelta(minutes=10),
            )
            send_mail(
                "Kuber Trobe Password Reset OTP",
                f"Your OTP is {otp}. It expires in 10 minutes.",
                None,
                [user.email],
            )
        return Response({"detail": "If the email is registered, an OTP has been sent."})


class ResetPasswordAPIView(APIView):
    permission_classes = [AllowAny]

    def post(self, request):
        email = request.data.get("email")
        otp = request.data.get("otp")
        new_password = request.data.get("new_password")
        user = User.objects.filter(email__iexact=email, is_active=True).first()
        if not user or not otp or not new_password:
            return Response({"detail": "Invalid reset request."}, status=400)

        reset = PasswordResetOTP.objects.filter(
            user=user, used=False, expires_at__gt=timezone.now()
        ).first()
        if not reset or reset.attempts >= 5 or not check_password(otp, reset.otp_hash):
            if reset:
                reset.attempts += 1
                reset.save(update_fields=["attempts"])
            return Response({"detail": "Invalid or expired OTP."}, status=400)

        user.set_password(new_password)
        user.save(update_fields=["password"])
        reset.used = True
        reset.save(update_fields=["used"])
        return Response({"detail": "Password reset successfully."})


class DashboardAPIView(AdminOnlyAPIView):
    def get(self, request):
        businesses = Business.objects.filter(deleted_at__isnull=True)
        investors = Investor.objects.filter(deleted_at__isnull=True)
        investments = Investment.objects.filter(deleted_at__isnull=True)
        kuber_investments = KuberBusinessInvestment.objects.filter(deleted_at__isnull=True)
        one_time_total = investments.filter(amount__isnull=False).aggregate(v=Sum("amount"))["v"] or Decimal("0")
        platinum_total = RecurringContribution.objects.filter(
            deleted_at__isnull=True,
            investment__deleted_at__isnull=True,
            investment__scheme__category=Scheme.Categories.PLATINUM,
        ).aggregate(v=Sum("amount"))["v"] or Decimal("0")
        total_investment = one_time_total + platinum_total
        payable = LedgerEntry.objects.filter(
            entry_type=LedgerEntry.EntryTypes.SOFT_DEBIT
        ).exclude(
            investment__scheme__category=Scheme.Categories.DIAMOND
        ).aggregate(total=Sum("amount"))["total"] or Decimal("0")
        credits = LedgerEntry.objects.filter(
            entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT
        ).aggregate(total=Sum("amount"))["total"] or Decimal("0")
        debits = LedgerEntry.objects.filter(
            entry_type=LedgerEntry.EntryTypes.DEBIT
        ).aggregate(total=Sum("amount"))["total"] or Decimal("0")
        soft_debits = payable
        return Response({
            "kpis": {
                "total_businesses": businesses.count(),
                "total_investors": investors.count(),
                "total_investment": total_investment,
                "total_kuber_business_investment": kuber_investments.aggregate(v=Sum("amount"))["v"] or 0,
                "interest_payable_to_investors": payable,
                "accumulated_funding": credits - debits,
                "actual_funds": credits - soft_debits - debits,
            },
            "note": "Charts can be built from the same date-grouped aggregates exposed by reporting endpoints.",
        })


class BusinessListCreateAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def get(self, request):
        qs = Business.objects.filter(deleted_at__isnull=True).annotate(
            investor_count=Count("investments", filter=Q(investments__deleted_at__isnull=True), distinct=True),
        )
        return page_response(request, qs, BusinessSerializer)

    @transaction.atomic
    def post(self, request):
        serializer = BusinessSerializer(data=request.data, context={"request": request})
        serializer.is_valid(raise_exception=True)
        business = serializer.save()
        audit(request.user, "CREATE", "BUSINESS", business, "Business created.")
        return Response(BusinessSerializer(business, context={"request": request}).data, status=201)


class BusinessDetailAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def get_object(self, pk):
        return Business.objects.filter(pk=pk, deleted_at__isnull=True).first()

    def get(self, request, pk):
        obj = self.get_object(pk)
        if not obj:
            return Response({"detail": "Business not found."}, status=404)
        return Response(BusinessSerializer(obj, context={"request": request}).data)

    def put(self, request, pk):
        obj = self.get_object(pk)
        if not obj:
            return Response({"detail": "Business not found."}, status=404)
        serializer = BusinessSerializer(obj, data=request.data, partial=True, context={"request": request})
        serializer.is_valid(raise_exception=True)
        obj = serializer.save()
        audit(request.user, "UPDATE", "BUSINESS", obj, "Business updated.")
        return Response(BusinessSerializer(obj, context={"request": request}).data)

    def delete(self, request, pk):
        obj = self.get_object(pk)
        if not obj:
            return Response({"detail": "Business not found."}, status=404)
        obj.soft_delete()
        audit(request.user, "DELETE", "BUSINESS", obj, "Business moved to restore bin.")
        return Response(status=204)


class BusinessDocumentCreateAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser]

    def post(self, request, business_id):
        business = Business.objects.filter(pk=business_id, deleted_at__isnull=True).first()
        if not business:
            return Response({"detail": "Business not found."}, status=404)
        serializer = BusinessDocumentSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        doc = serializer.save(business=business, uploaded_by=request.user)
        audit(request.user, "CREATE", "BUSINESS_DOCUMENT", doc, "Business document uploaded.")
        return Response(BusinessDocumentSerializer(doc).data, status=201)


class InvestorDocumentListAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser]
    def get(self, request, investor_id):
        qs = InvestorDocument.objects.filter(investor_id=investor_id)
        return page_response(request, qs, InvestorDocumentSerializer)

    def post(self, request, investor_id):
        investor = Investor.objects.filter(pk=investor_id, deleted_at__isnull=True).first()
        if not investor:
            return Response({"detail": "Investor not found."}, status=404)
        files = request.FILES.getlist("files")
        section = request.data.get("section", InvestorDocument.Sections.OTHER)
        titles = request.data.getlist("titles") if hasattr(request.data, "getlist") else []
        created = []
        for index, file in enumerate(files):
            title = titles[index] if index < len(titles) else file.name
            created.append(InvestorDocument.objects.create(
                investor=investor, section=section, title=title or file.name,
                file=file, uploaded_by=request.user
            ))
        return Response(InvestorDocumentSerializer(created, many=True).data, status=201)


class BusinessInvestorsAPIView(AdminOnlyAPIView):
    def get(self, request, business_id):
        business = Business.objects.filter(pk=business_id, deleted_at__isnull=True).first()
        if not business:
            return Response({"detail": "Business not found."}, status=404)
        qs = Investor.objects.filter(
            investments__business=business,
            investments__deleted_at__isnull=True,
            deleted_at__isnull=True,
        ).distinct()
        return page_response(request, qs, InvestorSerializer)


class InvestorListCreateAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]
    def get(self, request):
        qs = Investor.objects.filter(deleted_at__isnull=True).annotate(
            business_count=Count("investments__business", filter=Q(investments__deleted_at__isnull=True), distinct=True),
        )
        return page_response(request, qs, InvestorSerializer)

    @transaction.atomic
    def post(self, request):
        serializer = InvestorSerializer(data=request.data, context={"request": request})
        serializer.is_valid(raise_exception=True)
        investor = serializer.save()
        audit(request.user, "CREATE", "INVESTOR", investor, "Investor created.")
        return Response(InvestorSerializer(investor, context={"request": request}).data, status=201)


class InvestorDetailAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]
    def get(self, request, pk):
        investor = Investor.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not investor:
            return Response({"detail": "Investor not found."}, status=404)
        investor.business_count = investor.investments.filter(deleted_at__isnull=True).values("business").distinct().count()
        return Response(InvestorSerializer(investor, context={"request": request}).data)

    def put(self, request, pk):
        investor = Investor.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not investor:
            return Response({"detail": "Investor not found."}, status=404)
        serializer = InvestorSerializer(investor, data=request.data, partial=True, context={"request": request})
        serializer.is_valid(raise_exception=True)
        investor = serializer.save()
        audit(request.user, "UPDATE", "INVESTOR", investor, "Investor updated.")
        return Response(InvestorSerializer(investor, context={"request": request}).data)

    def delete(self, request, pk):
        investor = Investor.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not investor:
            return Response({"detail": "Investor not found."}, status=404)
        investor.soft_delete()
        audit(request.user, "DELETE", "INVESTOR", investor, "Investor moved to restore bin.")
        return Response(status=204)


class InvestorPortfolioAPIView(AdminOnlyAPIView):
    def get(self, request, pk):
        investor = Investor.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not investor:
            return Response({"detail": "Investor not found."}, status=404)
        qs = Investment.objects.filter(investor=investor, deleted_at__isnull=True).select_related("business", "scheme")
        return page_response(request, qs, InvestmentSerializer)


class SchemeListCreateAPIView(AdminOnlyAPIView):
    def get(self, request):
        qs = Scheme.objects.filter(deleted_at__isnull=True)
        category = request.query_params.get("category")
        if category:
            qs = qs.filter(category=category.upper())
        return page_response(request, qs, SchemeSerializer)

    def post(self, request):
        serializer = SchemeSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        scheme = serializer.save(created_by=request.user)
        audit(request.user, "CREATE", "SCHEME", scheme, "Scheme created.")
        return Response(SchemeSerializer(scheme).data, status=201)


class SchemeDetailAPIView(AdminOnlyAPIView):
    def put(self, request, pk):
        scheme = Scheme.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not scheme:
            return Response({"detail": "Scheme not found."}, status=404)
        serializer = SchemeSerializer(scheme, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        scheme = serializer.save()
        audit(request.user, "UPDATE", "SCHEME", scheme, "Scheme updated.")
        return Response(SchemeSerializer(scheme).data)

    def delete(self, request, pk):
        scheme = Scheme.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not scheme:
            return Response({"detail": "Scheme not found."}, status=404)
        scheme.soft_delete()
        audit(request.user, "DELETE", "SCHEME", scheme, "Scheme moved to restore bin.")
        return Response(status=204)


class KuberBusinessInvestmentListCreateAPIView(AdminOnlyAPIView):
    def get(self, request):
        qs = KuberBusinessInvestment.objects.filter(deleted_at__isnull=True).select_related("business")
        return page_response(request, qs, KuberBusinessInvestmentSerializer)

    def post(self, request):
        serializer = KuberBusinessInvestmentSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        obj = serializer.save(created_by=request.user)
        audit(request.user, "CREATE", "KUBER_BUSINESS_INVESTMENT", obj, "Kuber investment into business created.")
        return Response(KuberBusinessInvestmentSerializer(obj).data, status=201)


class KuberBusinessInvestmentDetailAPIView(AdminOnlyAPIView):
    def put(self, request, pk):
        obj = KuberBusinessInvestment.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not obj:
            return Response({"detail": "Investment not found."}, status=404)
        serializer = KuberBusinessInvestmentSerializer(obj, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        obj = serializer.save()
        audit(request.user, "UPDATE", "KUBER_BUSINESS_INVESTMENT", obj, "Kuber investment updated.")
        return Response(KuberBusinessInvestmentSerializer(obj).data)

    def delete(self, request, pk):
        obj = KuberBusinessInvestment.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not obj:
            return Response({"detail": "Investment not found."}, status=404)
        obj.soft_delete()
        audit(request.user, "DELETE", "KUBER_BUSINESS_INVESTMENT", obj, "Kuber investment moved to restore bin.")
        return Response(status=204)


class BusinessCreditProfitAPIView(AdminOnlyAPIView):
    def get(self, request):
        qs = Business.objects.filter(deleted_at__isnull=True).order_by("name")
        business_id = request.query_params.get("business")
        if business_id:
            qs = qs.filter(id=business_id)

        rows = []
        for business in qs:
            credit_qs = LedgerEntry.objects.filter(
                business=business,
                kuber_investment__isnull=False,
                entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT,
            )
            payout_qs = LedgerEntry.objects.filter(
                business=business,
                entry_type=LedgerEntry.EntryTypes.DEBIT,
                reference__startswith="KUBER-PAYMENT-",
            )
            credit_data = credit_qs.aggregate(total=Sum("amount"), last_date=Max("entry_date"))
            payout_data = payout_qs.aggregate(total=Sum("amount"), last_date=Max("entry_date"))
            credit = credit_data["total"] or Decimal("0.00")
            payments = payout_data["total"] or Decimal("0.00")
            rows.append({
                "business": business.id,
                "business_name": business.name,
                "accumulated_credit_profit": credit,
                "total_payouts": payments,
                "available_for_kuber_payout": max(Decimal("0.00"), credit - payments),
                "last_payout_date": payout_data["last_date"],
                "last_credit_profit_date": credit_data["last_date"],
            })
        return Response({"results": rows})


class KuberBusinessPayoutAPIView(AdminOnlyAPIView):
    """Record a payment received by Kuber from a business in the ledger."""

    @transaction.atomic
    def post(self, request):
        business_id = request.data.get("business")
        amount = request.data.get("amount")
        payout_date = request.data.get("payout_date") or timezone.localdate()
        notes = str(request.data.get("notes") or "").strip()

        business = Business.objects.filter(
            id=business_id, deleted_at__isnull=True
        ).first()
        if not business:
            return Response({"detail": "Business not found."}, status=404)

        try:
            from datetime import datetime
            if isinstance(payout_date, str):
                payout_date = datetime.strptime(payout_date, "%Y-%m-%d").date()
            amount = Decimal(str(amount))
        except (TypeError, ValueError, ArithmeticError):
            return Response({"detail": "Valid amount and payout_date are required."}, status=400)

        if amount <= 0:
            return Response({"detail": "Kuber payment amount must be greater than zero."}, status=400)

        credit = (
            LedgerEntry.objects.filter(
                business=business,
                kuber_investment__isnull=False,
                entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT,
            ).aggregate(v=Sum("amount"))["v"]
            or Decimal("0.00")
        )
        paid = (
            LedgerEntry.objects.filter(
                business=business,
                entry_type=LedgerEntry.EntryTypes.DEBIT,
                reference__startswith="KUBER-PAYMENT-",
            ).aggregate(v=Sum("amount"))["v"]
            or Decimal("0.00")
        )
        available = max(Decimal("0.00"), credit - paid)

        if amount > available:
            return Response(
                {
                    "detail": "Kuber payment cannot exceed the available Credit Profit.",
                    "available_for_kuber_payout": available,
                },
                status=400,
            )

        entry = LedgerEntry.objects.create(
            business=business,
            entry_date=payout_date,
            entry_type=LedgerEntry.EntryTypes.DEBIT,
            amount=amount,
            description=notes or "Kuber Payment received from business.",
            reference=f"KUBER-PAYMENT-{business.pk}-{timezone.now().strftime('%Y%m%d%H%M%S%f')}",
        )

        audit(
            request.user,
            "KUBER_PAYMENT",
            "LEDGER",
            entry,
            f"Kuber payment of {amount} recorded for {business.name}.",
        )

        return Response({
            "id": entry.id,
            "business": business.id,
            "business_name": business.name,
            "amount": entry.amount,
            "payout_date": entry.entry_date,
            "entry_type": entry.entry_type,
            "description": entry.description,
            "reference": entry.reference,
            "available_for_kuber_payout": available - amount,
        }, status=201)


class InvestmentListCreateAPIView(AdminOnlyAPIView):
    def get(self, request):
        qs = Investment.objects.filter(
            deleted_at__isnull=True
        ).select_related("investor", "business", "scheme").prefetch_related("recurring_contributions", "payouts")
        investor_id = request.query_params.get("investor")
        business_id = request.query_params.get("business")
        scheme_id = request.query_params.get("scheme")
        if investor_id:
            qs = qs.filter(investor_id=investor_id)
        if business_id:
            qs = qs.filter(business_id=business_id)
        if scheme_id:
            qs = qs.filter(scheme_id=scheme_id)
        return page_response(request, qs, InvestmentSerializer)

    @transaction.atomic
    def post(self, request):
        serializer = InvestmentSerializer(data=request.data, context={"request": request})
        serializer.is_valid(raise_exception=True)
        obj = serializer.save()
        audit(request.user, "CREATE", "INVESTMENT", obj, "Investor investment created.")
        return Response(
            InvestmentSerializer(obj, context={"request": request}).data,
            status=201,
        )


class InvestmentDetailAPIView(AdminOnlyAPIView):
    def get(self, request, pk):
        obj = Investment.objects.filter(pk=pk, deleted_at__isnull=True).select_related("investor","business","scheme").first()
        if not obj:
            return Response({"detail": "Investment not found."}, status=404)
        return Response(InvestmentSerializer(obj).data)

    def put(self, request, pk):
        obj = Investment.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not obj:
            return Response({"detail": "Investment not found."}, status=404)
        # Investment terms should normally be changed only by an explicit business rule.
        serializer = InvestmentSerializer(obj, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        obj = serializer.save()
        audit(request.user, "UPDATE", "INVESTMENT", obj, "Investor investment updated.")
        return Response(InvestmentSerializer(obj).data)

    def delete(self, request, pk):
        obj = Investment.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not obj:
            return Response({"detail": "Investment not found."}, status=404)
        obj.soft_delete()
        audit(request.user, "DELETE", "INVESTMENT", obj, "Investor investment moved to restore bin.")
        return Response(status=204)


class BalanceSheetListCreateAPIView(AdminOnlyAPIView):
    """Admin/Accountant combined Balance Sheet + Daily Financial persistence."""

    def get(self, request):
        qs = BalanceSheet.objects.filter(
            deleted_at__isnull=True
        ).select_related("business", "entered_by")
        business_id = request.query_params.get("business")
        if business_id:
            qs = qs.filter(business_id=business_id)
        return page_response(request, qs, BalanceSheetSerializer)

    @transaction.atomic
    def post(self, request):
        payload = request.data.copy()
        balance_fields = {
            "business", "statement_date", "cash_in_hand", "bank_balance",
            "accounts_receivable", "inventory", "prepaid_expenses",
            "other_current_assets", "fixed_assets", "accumulated_depreciation",
            "other_non_current_assets", "accounts_payable", "short_term_borrowings",
            "accrued_expenses", "taxes_payable", "other_current_liabilities",
            "long_term_loans", "other_non_current_liabilities", "owner_capital",
            "reserves_surplus", "retained_earnings", "drawings", "remarks",
        }
        financial_fields = {
            "business", "statement_date", "revenue", "direct_cost",
            "operating_expenses", "other_income", "finance_cost", "tax",
            "operating_cash_inflow", "operating_cash_outflow",
            "investing_cash_inflow", "investing_cash_outflow",
            "financing_cash_inflow", "financing_cash_outflow", "remarks",
        }
        balance_payload = {k: payload.get(k) for k in balance_fields if k in payload}
        balance_serializer = BalanceSheetSerializer(data=balance_payload, context={"request": request})
        balance_serializer.is_valid(raise_exception=True)
        business = balance_serializer.validated_data["business"]
        statement_date = balance_serializer.validated_data["statement_date"]

        if BalanceSheet.objects.filter(business=business, statement_date=statement_date, deleted_at__isnull=True).exists():
            return Response({"detail": "A balance sheet already exists for this business and date."}, status=400)

        financial_payload = {k: payload.get(k) for k in financial_fields if k in payload}
        financial_payload["business"] = business.pk
        financial_payload["statement_date"] = statement_date
        financial_serializer = DailyFinancialStatementSerializer(data=financial_payload, context={"request": request})
        financial_serializer.is_valid(raise_exception=True)
        if DailyFinancialStatement.objects.filter(business=business, statement_date=statement_date, deleted_at__isnull=True).exists():
            return Response({"detail": "A daily financial statement already exists for this business and date."}, status=400)

        balance = balance_serializer.save(entered_by=request.user)
        financial = financial_serializer.save(
            business=business, submitted_by=request.user,
            status=payload.get("status", DailyFinancialStatement.Status.SUBMITTED)
        )
        audit(request.user, "CREATE", "BALANCE_SHEET", balance, "Structured balance sheet created.")
        audit(request.user, "CREATE", "DAILY_FINANCIAL_STATEMENT", financial, "Daily financial statement created with balance sheet.")
        return Response({
            "balance_sheet": BalanceSheetSerializer(balance, context={"request": request}).data,
            "daily_financial_statement": DailyFinancialStatementSerializer(financial, context={"request": request}).data,
        }, status=201)


class BalanceSheetDetailAPIView(AdminOnlyAPIView):
    @transaction.atomic
    def put(self, request, pk):
        obj = BalanceSheet.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not obj:
            return Response({"detail": "Balance sheet not found."}, status=404)

        payload = request.data.copy()
        balance_fields = {
            "business", "statement_date", "cash_in_hand", "bank_balance",
            "accounts_receivable", "inventory", "prepaid_expenses", "other_current_assets",
            "fixed_assets", "accumulated_depreciation", "other_non_current_assets",
            "accounts_payable", "short_term_borrowings", "accrued_expenses", "taxes_payable",
            "other_current_liabilities", "long_term_loans", "other_non_current_liabilities",
            "owner_capital", "reserves_surplus", "retained_earnings", "drawings", "remarks",
        }
        financial_fields = {
            "business", "statement_date", "revenue", "direct_cost", "operating_expenses",
            "other_income", "finance_cost", "tax", "operating_cash_inflow",
            "operating_cash_outflow", "investing_cash_inflow", "investing_cash_outflow",
            "financing_cash_inflow", "financing_cash_outflow", "remarks",
        }
        balance_payload = {k: payload.get(k) for k in balance_fields if k in payload}
        serializer = BalanceSheetSerializer(obj, data=balance_payload, partial=True, context={"request": request})
        serializer.is_valid(raise_exception=True)
        obj = serializer.save()

        business = obj.business
        statement_date = obj.statement_date
        financial_payload = {k: payload.get(k) for k in financial_fields if k in payload}
        financial_payload["business"] = business.pk
        financial_payload["statement_date"] = statement_date
        financial = DailyFinancialStatement.objects.filter(
            business=business, statement_date=statement_date, deleted_at__isnull=True
        ).first()
        if financial:
            fs = DailyFinancialStatementSerializer(financial, data=financial_payload, partial=True, context={"request": request})
        else:
            fs = DailyFinancialStatementSerializer(data=financial_payload, context={"request": request})
        fs.is_valid(raise_exception=True)
        financial = fs.save(
            business=business, submitted_by=(financial.submitted_by if financial else request.user),
            status=(financial.status if financial else DailyFinancialStatement.Status.SUBMITTED),
        )

        audit(request.user, "UPDATE", "BALANCE_SHEET", obj, "Structured balance sheet updated.")
        audit(request.user, "UPDATE" if financial else "CREATE", "DAILY_FINANCIAL_STATEMENT", financial, "Daily financial statement saved with balance sheet.")
        return Response({
            "balance_sheet": BalanceSheetSerializer(obj, context={"request": request}).data,
            "daily_financial_statement": DailyFinancialStatementSerializer(financial, context={"request": request}).data,
        })

    def patch(self, request, pk):
        return self.put(request, pk)

    def delete(self, request, pk):
        obj = BalanceSheet.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not obj:
            return Response({"detail": "Balance sheet not found."}, status=404)

        obj.soft_delete()
        audit(
            request.user,
            "DELETE",
            "BALANCE_SHEET",
            obj,
            "Balance sheet moved to restore bin.",
        )
        return Response(status=204)


class LedgerListAPIView(AdminOnlyAPIView):
    def get(self, request):
        qs = LedgerEntry.objects.all().exclude(
            investment__scheme__category=Scheme.Categories.DIAMOND
        ).select_related("business", "investor", "investment", "kuber_investment")
        if request.query_params.get("business"):
            qs = qs.filter(business_id=request.query_params["business"])
        if request.query_params.get("investor"):
            qs = qs.filter(investor_id=request.query_params["investor"])
        if request.query_params.get("investment"):
            qs = qs.filter(investment_id=request.query_params["investment"])
        if request.query_params.get("entry_type"):
            qs = qs.filter(entry_type=request.query_params["entry_type"])
        if request.query_params.get("date"):
            qs = qs.filter(entry_date=request.query_params["date"])
        return page_response(request, qs, LedgerEntrySerializer)


class GenerateDailyLedgerAPIView(AdminOnlyAPIView):
    @transaction.atomic
    def post(self, request):
        entry_date = request.data.get("date")
        if entry_date:
            from datetime import datetime
            try:
                entry_date = datetime.strptime(entry_date, "%Y-%m-%d").date()
            except ValueError:
                return Response({"detail": "date must be YYYY-MM-DD."}, status=400)
        else:
            entry_date = timezone.localdate()

        result = generate_daily_ledger(entry_date)
        return Response(result, status=200)


class PayoutListCreateAPIView(AdminOnlyAPIView):
    def get(self, request):
        qs = Payout.objects.all().select_related("investment", "investor")
        return page_response(request, qs, PayoutSerializer)

    @transaction.atomic
    def post(self, request):
        serializer = PayoutSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        payout = serializer.save(created_by=request.user)
        investment = payout.investment
        # Diamond payouts are company <-> investor transactions and must not
        # appear in the Business/Admin Ledger. The Payout record itself is
        # retained for the Diamond personal payout history.
        if investment.scheme.category != Scheme.Categories.DIAMOND:
            LedgerEntry.objects.create(
                business=investment.business,
                investment=investment,
                investor=investment.investor,
                entry_date=payout.payout_date,
                entry_type=LedgerEntry.EntryTypes.DEBIT,
                amount=payout.amount,
                description="Investor payout.",
                reference=f"PAYOUT-{payout.pk}",
            )
        audit(request.user, "PAYOUT", "LEDGER", payout, "Investor payout recorded.")
        return Response(PayoutSerializer(payout).data, status=201)


class CertificateListCreateAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def get(self, request):
        qs = Certificate.objects.filter(deleted_at__isnull=True).select_related(
            "investment", "investment__investor", "investment__business", "investment__scheme"
        ).prefetch_related("attachments")
        return page_response(request, qs, CertificateSerializer)

    @transaction.atomic
    def post(self, request):
        investment = Investment.objects.filter(
            pk=request.data.get("investment"), deleted_at__isnull=True
        ).select_related("investor", "business", "scheme").first()
        if not investment:
            return Response({"detail": "Investment not found."}, status=404)

        # The standard certificate design is generated below after attachments are saved.
        certificate = Certificate.objects.filter(investment=investment).first()
        if certificate:
            certificate.deleted_at = None
            certificate.version += 1
            certificate.created_by = request.user
            certificate.save(update_fields=["deleted_at", "html_content", "version", "created_by", "updated_at"])
            operation = "UPDATE"
        else:
            certificate = Certificate.objects.create(
                investment=investment, html_content="", created_by=request.user
            )
            operation = "CREATE"

        files = request.FILES.getlist("attachments")
        titles = request.data.getlist("attachment_titles") if hasattr(request.data, "getlist") else []
        for index, file in enumerate(files):
            title = titles[index] if index < len(titles) else file.name
            CertificateAttachment.objects.create(
                certificate=certificate,
                title=title or file.name,
                file=file,
                sort_order=index,
            )

        certificate.html_content = build_default_certificate_html(
            investment,
            certificate.attachments.filter(deleted_at__isnull=True),
            request=request,
        )
        certificate.save(update_fields=["html_content", "updated_at"])

        audit(
            request.user, operation, "CERTIFICATE", certificate,
            "Investment certificate generated with attachments."
        )
        return Response(
            CertificateSerializer(certificate, context={"request": request}).data,
            status=201 if operation == "CREATE" else 200,
        )


class CertificateDetailAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def get(self, request, pk):
        obj = Certificate.objects.filter(pk=pk, deleted_at__isnull=True).prefetch_related("attachments").first()
        if not obj:
            return Response({"detail": "Certificate not found."}, status=404)
        return Response(CertificateSerializer(obj, context={"request": request}).data)

    def put(self, request, pk):
        obj = Certificate.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not obj:
            return Response({"detail": "Certificate not found."}, status=404)

        obj.version += 1
        obj.created_by = request.user
        obj.save(update_fields=["version", "created_by", "updated_at"])

        files = request.FILES.getlist("attachments")
        titles = request.data.getlist("attachment_titles") if hasattr(request.data, "getlist") else []
        start_order = obj.attachments.count()
        for index, file in enumerate(files):
            title = titles[index] if index < len(titles) else file.name
            CertificateAttachment.objects.create(
                certificate=obj, title=title or file.name, file=file, sort_order=start_order + index
            )

        obj.html_content = build_default_certificate_html(
            obj.investment,
            obj.attachments.filter(deleted_at__isnull=True),
            request=request,
        )
        obj.save(update_fields=["html_content", "updated_at"])

        audit(request.user, "UPDATE", "CERTIFICATE", obj, "Certificate updated.")
        return Response(CertificateSerializer(obj, context={"request": request}).data)

    def delete(self, request, pk):
        obj = Certificate.objects.filter(pk=pk, deleted_at__isnull=True).first()
        if not obj:
            return Response({"detail": "Certificate not found."}, status=404)
        obj.soft_delete()
        audit(request.user, "DELETE", "CERTIFICATE", obj, "Certificate moved to restore bin.")
        return Response(status=204)


class CertificateAttachmentDeleteAPIView(AdminOnlyAPIView):
    def delete(self, request, pk, attachment_id):
        attachment = CertificateAttachment.objects.filter(
            pk=attachment_id, certificate_id=pk
        ).first()
        if not attachment:
            return Response({"detail": "Attachment not found."}, status=404)
        attachment.soft_delete()
        audit(request.user, "DELETE", "CERTIFICATE_ATTACHMENT", attachment, "Certificate attachment moved to restore bin.")
        return Response(status=204)


ACCOUNTANT_PERMISSION_DEFAULTS = {
    "business_add": True, "business_view": True, "business_edit": True, "business_delete": True,
    "investor_add": True, "investor_view": True, "investor_edit": True, "investor_delete": True,
    "scheme_add": True, "scheme_view": True, "scheme_edit": True, "scheme_delete": True,
    "kuber_investment_add": True, "kuber_investment_view": True, "kuber_investment_edit": True,
    "kuber_investment_delete": True,
    "investment_add": True, "investment_view": True, "investment_edit": True, "investment_delete": True,
    "balance_sheet_view": True, "balance_sheet_upload": True, "balance_sheet_edit": True,
    "ledger_view": True, "ledger_generate": True,
    "payout_view": True, "payout_create": True,
    "certificate_view": True, "certificate_generate": True,
    "audit_view": True, "restore": True,
}

class AccountantListCreateAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def get(self, request):
        qs = User.objects.filter(
            role=User.Roles.ACCOUNTANT
        ).prefetch_related(
            "accountant_detail_fields",
            "accountant_documents"
        ).order_by("-date_joined")
        return page_response(request, qs, UserOutputSerializer)

    @transaction.atomic
    def post(self, request):
        data = request.data.copy()
        data["role"] = User.Roles.ACCOUNTANT

        identity_details = request.data.get("identity_details", "[]")
        if isinstance(identity_details, str):
            try:
                identity_details = json.loads(identity_details)
            except json.JSONDecodeError:
                return Response({"detail": "identity_details must be valid JSON."}, status=400)

        serializer = UserCreateSerializer(data=data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()

        image = request.FILES.get("image")
        if image:
            user.image = image
            user.save(update_fields=["image"])

        for item in identity_details:
            if not isinstance(item, dict) or not str(item.get("key", "")).strip():
                return Response({"detail": "Each identity detail must contain a key."}, status=400)
            AccountantDetailField.objects.create(
                accountant=user, key=str(item["key"]).strip(), value=str(item.get("value", ""))
            )

        files = request.FILES.getlist("identity_files")
        titles = request.data.getlist("identity_files_titles") if hasattr(request.data, "getlist") else []
        for index, file in enumerate(files):
            title = titles[index] if index < len(titles) else file.name
            AccountantDocument.objects.create(
                accountant=user, title=title or file.name, file=file, uploaded_by=request.user
            )

        permissions = request.data.get("permissions") or {}
        if isinstance(permissions, str):
            try:
                permissions = json.loads(permissions)
            except json.JSONDecodeError:
                permissions = {}

        AccountantPermission.objects.update_or_create(
            user=user,
            defaults={"permissions": {**ACCOUNTANT_PERMISSION_DEFAULTS, **permissions}}
        )
        audit(request.user, "CREATE", "ACCOUNTANT", user, "Accountant created.")

        response_data = UserOutputSerializer(user, context={"request": request}).data
        response_data["identity_details"] = list(user.accountant_detail_fields.values("id", "key", "value"))
        response_data["identity_files"] = AccountantDocumentSerializer(
            user.accountant_documents.all(), many=True, context={"request": request}
        ).data
        response_data["permissions"] = user.accountant_permissions.permissions
        return Response(response_data, status=201)


class AccountantDetailAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def put(self, request, pk):
        user = User.objects.filter(pk=pk, role=User.Roles.ACCOUNTANT).first()
        if not user:
            return Response({"detail": "Accountant not found."}, status=404)

        allowed = {"email", "first_name", "last_name", "phone", "is_active"}
        for field in allowed:
            if field in request.data:
                setattr(user, field, request.data[field])
        if request.data.get("password"):
            user.set_password(request.data["password"])
        if "image" in request.FILES:
            user.image = request.FILES["image"]
        user.save()

        if "identity_details" in request.data:
            identity_details = request.data.get("identity_details")
            if isinstance(identity_details, str):
                try:
                    identity_details = json.loads(identity_details)
                except json.JSONDecodeError:
                    return Response({"detail": "identity_details must be valid JSON."}, status=400)
            AccountantDetailField.objects.filter(accountant=user).delete()
            for item in identity_details or []:
                if not isinstance(item, dict) or not str(item.get("key", "")).strip():
                    return Response({"detail": "Each identity detail must contain a key."}, status=400)
                AccountantDetailField.objects.create(
                    accountant=user, key=str(item["key"]).strip(), value=str(item.get("value", ""))
                )

        files = request.FILES.getlist("identity_files")
        titles = request.data.getlist("identity_files_titles") if hasattr(request.data, "getlist") else []
        for index, file in enumerate(files):
            title = titles[index] if index < len(titles) else file.name
            AccountantDocument.objects.create(
                accountant=user, title=title or file.name, file=file, uploaded_by=request.user
            )

        if "permissions" in request.data:
            permissions = request.data.get("permissions") or {}
            if isinstance(permissions, str):
                try:
                    permissions = json.loads(permissions)
                except json.JSONDecodeError:
                    permissions = {}
            AccountantPermission.objects.update_or_create(
                user=user, defaults={"permissions": {**ACCOUNTANT_PERMISSION_DEFAULTS, **permissions}}
            )

        audit(request.user, "UPDATE", "ACCOUNTANT", user, "Accountant updated.")
        response_data = UserOutputSerializer(user, context={"request": request}).data
        response_data["identity_details"] = list(user.accountant_detail_fields.values("id", "key", "value"))
        response_data["identity_files"] = AccountantDocumentSerializer(
            user.accountant_documents.all(), many=True, context={"request": request}
        ).data
        response_data["permissions"] = getattr(user, "accountant_permissions", None).permissions if hasattr(user, "accountant_permissions") else {}
        return Response(response_data)

    def delete(self, request, pk):
        user = User.objects.filter(pk=pk, role=User.Roles.ACCOUNTANT).first()
        if not user:
            return Response({"detail": "Accountant not found."}, status=404)
        user.is_active = False
        user.save(update_fields=["is_active"])
        audit(request.user, "DELETE", "ACCOUNTANT", user, "Accountant deactivated.")
        return Response(status=204)

class AccountantPermissionAPIView(AdminOnlyAPIView):
    def get(self, request, pk):
        user = User.objects.filter(pk=pk, role=User.Roles.ACCOUNTANT).first()
        if not user:
            return Response({"detail": "Accountant not found."}, status=404)
        obj, _ = AccountantPermission.objects.get_or_create(user=user)
        return Response(AccountantPermissionSerializer(obj).data)

    def put(self, request, pk):
        user = User.objects.filter(pk=pk, role=User.Roles.ACCOUNTANT).first()
        if not user:
            return Response({"detail": "Accountant not found."}, status=404)
        obj, _ = AccountantPermission.objects.get_or_create(user=user)
        serializer = AccountantPermissionSerializer(obj, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        obj = serializer.save()
        audit(request.user, "UPDATE", "ACCOUNTANT_PERMISSION", obj, "Accountant permissions updated.")
        return Response(serializer.data)


class BusinessDocumentListAPIView(AdminOnlyAPIView):
    def get(self, request, business_id):
        qs = BusinessDocument.objects.filter(business_id=business_id).select_related("uploaded_by")
        return page_response(request, qs, BusinessDocumentSerializer)


class RecurringContributionListCreateAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def get(self, request, investment_id):
        qs = RecurringContribution.objects.filter(investment_id=investment_id, deleted_at__isnull=True)
        return page_response(request, qs, RecurringContributionSerializer)

    @transaction.atomic
    def post(self, request, investment_id):
        investment = Investment.objects.filter(
            pk=investment_id, deleted_at__isnull=True
        ).select_related("scheme").first()
        if not investment or investment.scheme.category != Scheme.Categories.PLATINUM:
            return Response({"detail": "A valid Platinum investment is required."}, status=400)
        data = request.data.copy()
        data["investment"] = investment.pk
        serializer = RecurringContributionSerializer(
            data=data, context={"investment": investment}
        )
        serializer.is_valid(raise_exception=True)
        obj = serializer.save(created_by=request.user)
        audit(request.user, "CREATE", "RECURRING_CONTRIBUTION", obj, "Platinum monthly contribution recorded.")
        return Response(RecurringContributionSerializer(obj).data, status=201)


class RecurringContributionDetailAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def put(self, request, investment_id, pk):
        obj = RecurringContribution.objects.filter(
            pk=pk, investment_id=investment_id, deleted_at__isnull=True
        ).first()
        if not obj:
            return Response({"detail": "Contribution not found."}, status=404)
        serializer = RecurringContributionSerializer(obj, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        obj = serializer.save()
        audit(request.user, "UPDATE", "RECURRING_CONTRIBUTION", obj, "Platinum monthly contribution updated.")
        return Response(RecurringContributionSerializer(obj).data)

    def delete(self, request, investment_id, pk):
        obj = RecurringContribution.objects.filter(
            pk=pk, investment_id=investment_id, deleted_at__isnull=True
        ).first()
        if not obj:
            return Response({"detail": "Contribution not found."}, status=404)
        obj.soft_delete()
        audit(request.user, "DELETE", "RECURRING_CONTRIBUTION", obj, "Platinum monthly contribution moved to restore bin.")
        return Response(status=204)


class AuditLogListAPIView(AdminOnlyAPIView):
    def get(self, request):
        qs = AuditLog.objects.all().select_related("actor")
        if request.query_params.get("operation"):
            qs = qs.filter(operation=request.query_params["operation"])
        if request.query_params.get("date"):
            qs = qs.filter(created_at__date=request.query_params["date"])
        if request.query_params.get("module"):
            qs = qs.filter(module=request.query_params["module"])
        return page_response(request, qs, AuditLogSerializer)


class RestoreBinAPIView(AdminOnlyAPIView):
    model_map = {
        "business": Business,
        "investor": Investor,
        "scheme": Scheme,
        "kuber-investment": KuberBusinessInvestment,
        "investment": Investment,
        "balance-sheet": BalanceSheet,
        "daily-financial-statement": DailyFinancialStatement,
        "certificate": Certificate,
        "certificate-attachment": CertificateAttachment,
        "recurring-contribution": RecurringContribution,
    }

    def get(self, request):
        result = {}
        cutoff = timezone.now() - timedelta(days=30)
        for key, model in self.model_map.items():
            qs = model.objects.filter(deleted_at__isnull=False, deleted_at__gte=cutoff)
            result[key] = [
                {
                    "id": o.pk,
                    "deleted_at": o.deleted_at,
                    "label": str(o),
                } for o in qs[:100]
            ]
        return Response(result)


class RestoreItemAPIView(AdminOnlyAPIView):
    def post(self, request, resource, pk):
        model = RestoreBinAPIView.model_map.get(resource)
        if not model:
            return Response({"detail": "Unsupported restore resource."}, status=400)
        cutoff = timezone.now() - timedelta(days=30)
        obj = model.objects.filter(pk=pk, deleted_at__isnull=False, deleted_at__gte=cutoff).first()
        if not obj:
            return Response({"detail": "Item not found or restore window expired."}, status=404)
        obj.restore()
        audit(request.user, "RESTORE", resource.upper(), obj, "Item restored.")
        return Response({"detail": "Restored successfully.", "id": obj.pk})

from io import BytesIO

from django.http import HttpResponse

from rest_framework.response import Response

from openpyxl import Workbook
class ExportLedgerAPIView(AdminOnlyAPIView):
    """Export the same ledger report shown in the Admin Panel."""

    def _queryset(self, request):
        qs = (
            LedgerEntry.objects
            .all()
            .select_related(
                "business",
                "investor",
                "investment",
                "kuber_investment",
                "investment__scheme",
            )
            .exclude(investment__scheme__category=Scheme.Categories.DIAMOND)
            .order_by("entry_date", "id")
        )

        investment_id = request.query_params.get("investment")

        if investment_id:
            qs = qs.filter(
                investment_id=investment_id,
                entry_type__in=[
                    LedgerEntry.EntryTypes.SOFT_DEBIT,
                    LedgerEntry.EntryTypes.DEBIT,
                ],
            )

        business_id = request.query_params.get("business")
        investor_id = request.query_params.get("investor")
        entry_type = request.query_params.get("entry_type")
        entry_date = request.query_params.get("date")

        if business_id:
            qs = qs.filter(business_id=business_id)

        if investor_id:
            qs = qs.filter(investor_id=investor_id)

        if entry_type:
            qs = qs.filter(entry_type=entry_type)

        if entry_date:
            qs = qs.filter(entry_date=entry_date)

        return qs

    @staticmethod
    def _row(e):
        return [
            str(e.entry_date),
            e.entry_type,
            e.investor.name if e.investor else "—",
            e.business.name if e.business else "—",
            str(e.amount),
            f"{e.annual_rate}% p.a."
            if e.annual_rate is not None
            else "—",
            (
                e.investment.scheme.get_category_display()
                if e.investment
                else "—"
            ),
            e.description or "",
            e.reference or "",
        ]

    def get(self, request):
        # IMPORTANT:
        # Use export_format instead of format so DRF does not
        # interpret "format=pdf" as a renderer format.
        fmt = (
            request.query_params
            .get("export_format", "xlsx")
            .lower()
            .strip()
        )

        qs = self._queryset(request)
        rows = [self._row(e) for e in qs]

        headers = [
            "Date",
            "Transaction Type",
            "Investor",
            "Business",
            "Amount",
            "Rate",
            "Scheme Category",
            "Description",
            "Reference",
        ]

        try:
            # =========================================================
            # PDF EXPORT
            # =========================================================
            if fmt == "pdf":
                from reportlab.lib import colors
                from reportlab.lib.pagesizes import A4, landscape
                from reportlab.lib.styles import (
                    getSampleStyleSheet,
                    ParagraphStyle,
                )
                from reportlab.platypus import (
                    SimpleDocTemplate,
                    Table,
                    TableStyle,
                    Paragraph,
                    Spacer,
                )

                output = BytesIO()

                doc = SimpleDocTemplate(
                    output,
                    pagesize=landscape(A4),
                    rightMargin=18,
                    leftMargin=18,
                    topMargin=18,
                    bottomMargin=18,
                )

                styles = getSampleStyleSheet()

                body = ParagraphStyle(
                    "LedgerBody",
                    parent=styles["BodyText"],
                    fontSize=6.5,
                    leading=8,
                    spaceAfter=0,
                )

                head = ParagraphStyle(
                    "LedgerHead",
                    parent=styles["BodyText"],
                    fontSize=7,
                    leading=8,
                    textColor=colors.white,
                )

                data = [
                    [
                        Paragraph(str(header), head)
                        for header in headers
                    ]
                ]

                for row in rows:
                    data.append(
                        [
                            Paragraph(
                                str(value)
                                .replace("&", "&amp;")
                                .replace("<", "&lt;")
                                .replace(">", "&gt;"),
                                body,
                            )
                            for value in row
                        ]
                    )

                table = Table(
                    data,
                    repeatRows=1,
                    colWidths=[
                        48,
                        68,
                        80,
                        80,
                        58,
                        58,
                        68,
                        145,
                        105,
                    ],
                    hAlign="LEFT",
                )

                table.setStyle(
                    TableStyle(
                        [
                            (
                                "BACKGROUND",
                                (0, 0),
                                (-1, 0),
                                colors.HexColor("#172033"),
                            ),
                            (
                                "TEXTCOLOR",
                                (0, 0),
                                (-1, 0),
                                colors.white,
                            ),
                            (
                                "GRID",
                                (0, 0),
                                (-1, -1),
                                0.35,
                                colors.grey,
                            ),
                            (
                                "VALIGN",
                                (0, 0),
                                (-1, -1),
                                "TOP",
                            ),
                            (
                                "FONTSIZE",
                                (0, 0),
                                (-1, -1),
                                7,
                            ),
                            (
                                "LEFTPADDING",
                                (0, 0),
                                (-1, -1),
                                4,
                            ),
                            (
                                "RIGHTPADDING",
                                (0, 0),
                                (-1, -1),
                                4,
                            ),
                            (
                                "TOPPADDING",
                                (0, 0),
                                (-1, -1),
                                4,
                            ),
                            (
                                "BOTTOMPADDING",
                                (0, 0),
                                (-1, -1),
                                4,
                            ),
                        ]
                    )
                )

                elements = [
                    Paragraph(
                        "Kuber Trobe — Ledger Report",
                        styles["Title"],
                    ),
                    Spacer(1, 8),
                    table,
                ]

                doc.build(elements)

                response = HttpResponse(
                    output.getvalue(),
                    content_type="application/pdf",
                )

                response["Content-Disposition"] = (
                    'attachment; filename="kuber_trobe_ledger.pdf"'
                )

                return response

            # =========================================================
            # INVALID FORMAT
            # =========================================================
            if fmt != "xlsx":
                return Response(
                    {
                        "detail": (
                            "export_format must be xlsx or pdf."
                        )
                    },
                    status=400,
                )

            # =========================================================
            # EXCEL / XLSX EXPORT
            # =========================================================
            output = BytesIO()

            wb = Workbook()
            ws = wb.active
            ws.title = "Ledger"

            # Header
            ws.append(headers)

            # Data
            for row in rows:
                ws.append(row)

            # Freeze header
            ws.freeze_panes = "A2"

            # Filter
            ws.auto_filter.ref = ws.dimensions

            # Column widths
            widths = [
                14,
                20,
                24,
                24,
                16,
                16,
                18,
                48,
                34,
            ]

            for index, width in enumerate(widths, start=1):
                ws.column_dimensions[
                    chr(64 + index)
                ].width = width

            # Save workbook
            wb.save(output)

            response = HttpResponse(
                output.getvalue(),
                content_type=(
                    "application/"
                    "vnd.openxmlformats-officedocument."
                    "spreadsheetml.sheet"
                ),
            )

            response["Content-Disposition"] = (
                'attachment; filename="kuber_trobe_ledger.xlsx"'
            )

            return response

        # =============================================================
        # MISSING DEPENDENCY
        # =============================================================
        except ImportError as exc:
            return Response(
                {
                    "detail": (
                        f"Export dependency missing: {exc}. "
                        "Run pip install -r requirements.txt"
                    )
                },
                status=500,
            )

        # =============================================================
        # GENERAL EXPORT ERROR
        # =============================================================
        except Exception as exc:
            return Response(
                {
                    "detail": f"Ledger export failed: {exc}"
                },
                status=500,
            )


class ExportInvestmentsAPIView(AdminOnlyAPIView):
    def get(self, request):
        qs = Investment.objects.filter(deleted_at__isnull=True).select_related("investor","business","scheme")
        wb = Workbook()
        ws = wb.active
        ws.title = "Investments"
        ws.append(["Investor", "Business", "Amount", "Scheme Category", "Scheme", "Rate % p.a.", "Investment Date", "Maturity Date"])
        for i in qs:
            ws.append([
                i.investor.name, i.business.name, float(i.amount or 0), i.scheme.category,
                i.scheme.name, float(i.investor_annual_rate), str(i.investment_date),
                str(i.maturity_date or ""),
            ])
        response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
        response["Content-Disposition"] = 'attachment; filename="kuber_trobe_investments.xlsx"'
        wb.save(response)
        return response


# =========================================================
# INVESTOR PORTAL
# =========================================================

class InvestorPortalProfileAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        investor = Investor.objects.filter(
            user=request.user,
            deleted_at__isnull=True
        ).first()

        if not investor:
            return Response(
                {"detail": "Investor account is not linked to an Investor record."},
                status=404
            )

        serializer = InvestorProfileSerializer(investor)
        return Response(serializer.data)


class InvestorPortalInvestmentsAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        investor = Investor.objects.filter(
            user=request.user,
            deleted_at__isnull=True
        ).first()

        if not investor:
            return Response(
                {"detail": "Investor account is not linked to an Investor record."},
                status=404
            )

        investments = (
            Investment.objects
            .filter(
                investor=investor,
                deleted_at__isnull=True
            )
            .select_related(
                "investor",
                "business",
                "scheme"
            )
            .prefetch_related("payouts")
            .order_by("-investment_date")
        )

        serializer = InvestorInvestmentSerializer(
            investments,
            many=True
        )

        return Response({
            "count": investments.count(),
            "results": serializer.data
        })


# =========================================================
# INVESTOR DASHBOARD
# =========================================================

class InvestorPortalDashboardAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        investor = Investor.objects.filter(
            user=request.user,
            deleted_at__isnull=True
        ).first()

        if not investor:
            return Response(
                {
                    "detail": (
                        "Investor account is not linked "
                        "to an Investor record."
                    )
                },
                status=404
            )

        investments = (
            Investment.objects
            .filter(
                investor=investor,
                deleted_at__isnull=True
            )
            .select_related(
                "business",
                "scheme"
            )
        )

        investment_list = list(investments)

        total_investment = Decimal("0")
        total_interest = Decimal("0")
        total_maturity = Decimal("0")
        total_premature = Decimal("0")

        business_map = {}
        scheme_map = {}

        for investment in investment_list:

            # -----------------------------
            # CAPITAL
            # -----------------------------
            if investment.scheme.category == Scheme.Categories.PLATINUM:

                contributed = (
                    investment.recurring_contributions
                    .filter(deleted_at__isnull=True)
                    .aggregate(
                        total=Sum("amount")
                    )["total"]
                    or Decimal("0")
                )

            else:
                contributed = (
                    investment.amount
                    or Decimal("0")
                )

            # -----------------------------
            # INTEREST
            # -----------------------------
            try:
                interest = (
                    diamond_interest_till_date(investment)
                    if investment.scheme.category == Scheme.Categories.DIAMOND
                    else interest_till_date(investment)
                )
            except (ValueError, TypeError):
                interest = Decimal("0")

            # -----------------------------
            # MATURITY
            # -----------------------------
            try:
                maturity = maturity_value(investment)
            except (ValueError, TypeError):
                maturity = Decimal("0")

            # -----------------------------
            # PREMATURE
            # -----------------------------
            try:
                premature = premature_value(investment)
            except (ValueError, TypeError):
                premature = Decimal("0")

            total_investment += Decimal(contributed)
            total_interest += Decimal(interest)
            total_maturity += Decimal(maturity)
            total_premature += Decimal(premature)

            # -----------------------------
            # BUSINESS CHART
            # -----------------------------
            business_name = investment.business.name

            if business_name not in business_map:
                business_map[business_name] = {
                    "business_id": investment.business_id,
                    "business_name": business_name,
                    "investment": Decimal("0"),
                    "interest": Decimal("0"),
                }

            business_map[business_name]["investment"] += Decimal(
                contributed
            )

            business_map[business_name]["interest"] += Decimal(
                interest
            )

            # -----------------------------
            # SCHEME CHART
            # -----------------------------
            scheme_name = investment.scheme.name
            scheme_category = investment.scheme.get_category_display()

            scheme_key = f"{scheme_category}-{scheme_name}"

            if scheme_key not in scheme_map:
                scheme_map[scheme_key] = {
                    "scheme_id": investment.scheme_id,
                    "scheme_category": scheme_category,
                    "scheme_name": scheme_name,
                    "investment": Decimal("0"),
                }

            scheme_map[scheme_key]["investment"] += Decimal(
                contributed
            )

        # -------------------------------------------------
        # RETURN RESPONSE
        # -------------------------------------------------

        business_chart = []

        for item in business_map.values():
            business_chart.append({
                "business_id": item["business_id"],
                "business_name": item["business_name"],
                "investment": item["investment"],
                "interest": item["interest"],
            })

        scheme_chart = []

        for item in scheme_map.values():
            scheme_chart.append({
                "scheme_id": item["scheme_id"],
                "scheme_category": item["scheme_category"],
                "scheme_name": item["scheme_name"],
                "investment": item["investment"],
            })

        return Response({
            "kpis": {
                "total_investment": total_investment,
                "total_interest": total_interest,
                "total_maturity": total_maturity,
                "total_premature": total_premature,
                "investment_count": len(investment_list),
                "business_count": len(business_map),
            },

            "charts": {
                "business_wise": business_chart,
                "scheme_wise": scheme_chart,
                "capital_vs_interest": {
                    "capital": total_investment,
                    "interest": total_interest,
                },
            }
        })


# =========================================================
# INVESTOR AVAILABLE BUSINESSES
# =========================================================

class InvestorPortalAvailableBusinessesAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):

        businesses = (
            Business.objects
            .filter(
                deleted_at__isnull=True
            )
            .order_by("name")
        )

        serializer = BusinessSerializer(
            businesses,
            many=True,
            context={"request": request}
        )

        return Response({
            "count": businesses.count(),
            "results": serializer.data
        })

from .services import interest_till_date, premature_value, maturity_value
# =========================================================
# INVESTOR AVAILABLE SCHEMES
# =========================================================

class InvestorPortalAvailableSchemesAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):

        schemes = (
            Scheme.objects
            .filter(
                deleted_at__isnull=True,
                is_active=True
            )
            .order_by(
                "category",
                "tenure_months",
                "name"
            )
        )

        serializer = SchemeSerializer(
            schemes,
            many=True
        )

        return Response({
            "count": schemes.count(),
            "results": serializer.data
        })


# =========================================================
# INVESTOR EXCEL EXPORT
# =========================================================

class InvestorPortalExportAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):

        investor = Investor.objects.filter(
            user=request.user,
            deleted_at__isnull=True
        ).first()

        if not investor:
            return Response(
                {
                    "detail": (
                        "Investor account is not linked "
                        "to an Investor record."
                    )
                },
                status=404
            )

        investments = (
            Investment.objects
            .filter(
                investor=investor,
                deleted_at__isnull=True
            )
            .select_related(
                "business",
                "scheme"
            )
            .order_by("-investment_date")
        )

        try:
            from openpyxl import Workbook

            wb = Workbook()
            ws = wb.active
            ws.title = "My Investments"

            # -------------------------------------------------
            # HEADER
            # -------------------------------------------------

            ws.append([
                "Investment ID",
                "Business",
                "Scheme Category",
                "Scheme Name",
                "Tenure (Months)",
                "Interest Rate % p.a.",
                "Investment Date",
                "Maturity Date",
                "Capital Invested",
                "Monthly Installment",
                "Total Contributed",
                "Interest Till Date",
                "Premature Amount",
                "Maturity Amount",
                "Status",
            ])

            # -------------------------------------------------
            # DATA
            # -------------------------------------------------

            for investment in investments:

                if (
                    investment.scheme.category
                    == Scheme.Categories.PLATINUM
                ):
                    total_contributed = (
                        investment.recurring_contributions
                        .filter(
                            deleted_at__isnull=True
                        )
                        .aggregate(
                            total=Sum("amount")
                        )["total"]
                        or Decimal("0")
                    )
                else:
                    total_contributed = (
                        investment.amount
                        or Decimal("0")
                    )

                try:
                    interest = (
                        diamond_interest_till_date(investment)
                        if investment.scheme.category == Scheme.Categories.DIAMOND
                        else interest_till_date(investment)
                    )
                except (ValueError, TypeError):
                    interest = Decimal("0")

                try:
                    premature = premature_value(
                        investment
                    )
                except (ValueError, TypeError):
                    premature = None

                try:
                    maturity = maturity_value(
                        investment
                    )
                except (ValueError, TypeError):
                    maturity = None

                ws.append([
                    investment.id,
                    investment.business.name,
                    investment.scheme.get_category_display(),
                    investment.scheme.name,
                    investment.scheme.tenure_months,
                    float(
                        investment.investor_annual_rate
                        or 0
                    ),
                    str(investment.investment_date),
                    str(investment.maturity_date or ""),
                    float(
                        investment.amount
                        or 0
                    ),
                    float(
                        investment.monthly_installment
                        or 0
                    ),
                    float(total_contributed),
                    float(interest),
                    float(premature)
                    if premature is not None
                    else "",
                    float(maturity)
                    if maturity is not None
                    else "",
                    investment.get_status_display()
                    if hasattr(
                        investment,
                        "get_status_display"
                    )
                    else investment.status,
                ])

            # -------------------------------------------------
            # FORMATTING
            # -------------------------------------------------

            ws.freeze_panes = "A2"
            ws.auto_filter.ref = ws.dimensions

            widths = [
                15,
                25,
                20,
                30,
                15,
                20,
                18,
                18,
                20,
                20,
                20,
                20,
                20,
                20,
                15,
            ]

            for index, width in enumerate(
                widths,
                start=1
            ):
                ws.column_dimensions[
                    chr(64 + index)
                ].width = width

            # -------------------------------------------------
            # RESPONSE
            # -------------------------------------------------

            response = HttpResponse(
                content_type=(
                    "application/vnd.openxmlformats-"
                    "officedocument.spreadsheetml.sheet"
                )
            )

            response[
                "Content-Disposition"
            ] = (
                'attachment; '
                'filename="my_investments.xlsx"'
            )

            wb.save(response)

            return response

        except ImportError as exc:

            return Response(
                {
                    "detail": (
                        f"Export dependency missing: {exc}. "
                        "Run pip install openpyxl"
                    )
                },
                status=500
            )

        except Exception as exc:

            return Response(
                {
                    "detail": f"Investor export failed: {exc}"
                },
                status=500
            )


# =========================================================
# INVESTOR CERTIFICATES
# =========================================================

# =========================================================
# INVESTOR CERTIFICATES
# =========================================================

class InvestorPortalCertificatesAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):

        investor = Investor.objects.filter(
            user=request.user,
            deleted_at__isnull=True
        ).first()

        if not investor:
            return Response(
                {
                    "detail": (
                        "Investor account is not linked "
                        "to an Investor record."
                    )
                },
                status=404
            )

        certificates = (
            Certificate.objects
            .filter(
                investment__investor=investor,
                deleted_at__isnull=True
            )
            .select_related(
                "investment",
                "investment__business",
                "investment__scheme"
            )
            .prefetch_related("attachments")
            .order_by("-created_at")
        )

        serializer = CertificateSerializer(
            certificates,
            many=True,
            context={"request": request}
        )

        return Response({
            "count": certificates.count(),
            "results": serializer.data
        })
# =========================================================
# DAILY FINANCIAL STATEMENTS
# =========================================================

def _accountant_financial_permission(request, action="view"):
    """
    Admin/superuser always has access.
    Accountant access is controlled by AccountantPermission JSON.
    """
    user = request.user
    if not user or not user.is_authenticated or not user.is_active:
        return False
    if getattr(user, "is_superuser", False) or getattr(user, "role", None) == User.Roles.ADMIN:
        return True
    if getattr(user, "role", None) != User.Roles.ACCOUNTANT:
        return False

    permission_key = {
        "view": "financial_statement_view",
        "add": "financial_statement_add",
        "edit": "financial_statement_edit",
        "delete": "financial_statement_delete",
    }.get(action, "financial_statement_view")

    permission_obj = getattr(user, "accountant_permissions", None)
    if not permission_obj:
        # Preserve the existing broad accountant behavior if the permission
        # row has not yet been created.
        return True
    permissions = permission_obj.permissions or {}
    return permissions.get(permission_key, True) is not False


class BusinessPortalFinancialStatementsAPIView(BusinessPortalBaseAPIView):
    """
    Business users can:
      GET  -> view their own daily financial statements
      POST -> submit a new statement for their own business

    Existing submitted statements are intentionally view-only for Business.
    Admin/Accountant use the admin financial-statement endpoints for editing
    and deletion.
    """

    def get(self, request):
        business = Business.objects.filter(
            user=request.user,
            deleted_at__isnull=True
        ).first()

        if not business:
            return Response(
                {"detail": "Business account is not linked to a Business record."},
                status=404
            )

        qs = (
            DailyFinancialStatement.objects
            .filter(
                business=business,
                deleted_at__isnull=True
            )
            .select_related("business", "submitted_by", "reviewed_by")
            .order_by("-statement_date", "-id")
        )

        return page_response(
            request,
            qs,
            DailyFinancialStatementSerializer
        )

    @transaction.atomic
    def post(self, request):
        business = Business.objects.filter(
            user=request.user,
            deleted_at__isnull=True
        ).first()

        if not business:
            return Response(
                {"detail": "Business account is not linked to a Business record."},
                status=404
            )

        payload = request.data.copy()
        payload["business"] = business.pk

        serializer = DailyFinancialStatementSerializer(
            data=payload,
            context={"request": request}
        )
        serializer.is_valid(raise_exception=True)

        # One live statement per business/date.
        statement_date = serializer.validated_data.get("statement_date")
        if DailyFinancialStatement.objects.filter(
            business=business,
            statement_date=statement_date,
            deleted_at__isnull=True
        ).exists():
            return Response(
                {
                    "detail": (
                        "A daily financial statement already exists "
                        "for this business and date."
                    )
                },
                status=400
            )

        obj = serializer.save(
            business=business,
            submitted_by=request.user,
            status=DailyFinancialStatement.Status.SUBMITTED
        )

        audit(
            request.user,
            "CREATE",
            "DAILY_FINANCIAL_STATEMENT",
            obj,
            "Daily financial statement submitted by business user."
        )

        return Response(
            DailyFinancialStatementSerializer(
                obj,
                context={"request": request}
            ).data,
            status=201
        )


# ============================================================
# DAILY FINANCIAL STATEMENTS
# ============================================================

def _accountant_financial_permission(request, action="view"):
    """
    Admin / Superuser:
        Full access.

    Accountant:
        Access controlled through AccountantPermission.

    Supported actions:
        view
        add
        edit
        delete
    """

    user = request.user

    if not user or not user.is_authenticated or not user.is_active:
        return False

    # Admin / Superuser
    if (
        getattr(user, "is_superuser", False)
        or getattr(user, "role", None) == User.Roles.ADMIN
    ):
        return True

    # Only Accountant reaches here
    if getattr(user, "role", None) != User.Roles.ACCOUNTANT:
        return False

    permission_key = {
        "view": "financial_statement_view",
        "add": "financial_statement_add",
        "edit": "financial_statement_edit",
        "delete": "financial_statement_delete",
    }.get(action, "financial_statement_view")

    permission_obj = getattr(
        user,
        "accountant_permissions",
        None
    )

    # Existing behaviour:
    # if permission object doesn't exist, allow Accountant access.
    if not permission_obj:
        return True

    permissions = permission_obj.permissions or {}

    return permissions.get(permission_key, True) is not False


class DailyFinancialStatementListCreateAPIView(APIView):
    """
    Admin / Accountant Daily Financial Statement API.

    GET:
        /daily-financial-statements/

    Optional filters:
        ?business=1
        ?date=2026-09-27
        ?status=SUBMITTED

    POST:
        Create a DailyFinancialStatement.
    """

    permission_classes = [IsAuthenticated]

    def initial(self, request, *args, **kwargs):
        super().initial(request, *args, **kwargs)

        if not _accountant_financial_permission(request, "view"):
            from rest_framework.exceptions import PermissionDenied

            raise PermissionDenied(
                "You do not have permission to view daily financial statements."
            )

    # ========================================================
    # GET
    # ========================================================

    def get(self, request):

        qs = (
            DailyFinancialStatement.objects
            .filter(
                deleted_at__isnull=True
            )
            .select_related(
                "business",
                "submitted_by",
                "reviewed_by",
            )
            .order_by(
                "-statement_date",
                "-id",
            )
        )

        # ----------------------------------------------------
        # BUSINESS FILTER
        # ----------------------------------------------------

        business_id = request.query_params.get("business")

        if business_id:
            qs = qs.filter(
                business_id=business_id
            )

        # ----------------------------------------------------
        # DATE FILTER
        # ----------------------------------------------------

        statement_date = request.query_params.get("date")

        if statement_date:
            qs = qs.filter(
                statement_date=statement_date
            )

        # ----------------------------------------------------
        # STATUS FILTER
        # ----------------------------------------------------

        status_filter = request.query_params.get("status")

        if status_filter:
            qs = qs.filter(
                status=status_filter.upper()
            )

        return page_response(
            request,
            qs,
            DailyFinancialStatementSerializer,
        )

    # ========================================================
    # POST
    # ========================================================

    @transaction.atomic
    def post(self, request):

        if not _accountant_financial_permission(
            request,
            "add"
        ):
            from rest_framework.exceptions import PermissionDenied

            raise PermissionDenied(
                "You do not have permission to add daily financial statements."
            )

        serializer = DailyFinancialStatementSerializer(
            data=request.data,
            context={
                "request": request
            }
        )

        serializer.is_valid(
            raise_exception=True
        )

        business = serializer.validated_data.get(
            "business"
        )

        statement_date = serializer.validated_data.get(
            "statement_date"
        )

        if not business:

            return Response(
                {
                    "detail": "business is required."
                },
                status=400
            )

        # ----------------------------------------------------
        # DUPLICATE CHECK
        # ----------------------------------------------------

        exists = DailyFinancialStatement.objects.filter(
            business=business,
            statement_date=statement_date,
            deleted_at__isnull=True,
        ).exists()

        if exists:

            return Response(
                {
                    "detail": (
                        "A daily financial statement already "
                        "exists for this business and date."
                    )
                },
                status=400
            )

        # ----------------------------------------------------
        # CREATE
        # ----------------------------------------------------

        obj = serializer.save(
            submitted_by=request.user,
            status=request.data.get(
                "status",
                DailyFinancialStatement.Status.SUBMITTED,
            ),
        )

        audit(
            request.user,
            "CREATE",
            "DAILY_FINANCIAL_STATEMENT",
            obj,
            "Daily financial statement created.",
        )

        return Response(
            DailyFinancialStatementSerializer(
                obj,
                context={
                    "request": request
                }
            ).data,
            status=201,
        )


class DailyFinancialStatementDetailAPIView(APIView):
    """
    Admin / Accountant:

        GET
        PUT
        PATCH
        DELETE
    """

    permission_classes = [IsAuthenticated]

    # ========================================================
    # GET OBJECT
    # ========================================================

    def get_object(self, pk):

        return (
            DailyFinancialStatement.objects
            .filter(
                pk=pk,
                deleted_at__isnull=True,
            )
            .select_related(
                "business",
                "submitted_by",
                "reviewed_by",
            )
            .first()
        )

    # ========================================================
    # GET
    # ========================================================

    def get(self, request, pk):

        if not _accountant_financial_permission(
            request,
            "view"
        ):
            from rest_framework.exceptions import PermissionDenied

            raise PermissionDenied(
                "You do not have permission to view daily financial statements."
            )

        obj = self.get_object(pk)

        if not obj:

            return Response(
                {
                    "detail": "Daily financial statement not found."
                },
                status=404,
            )

        return Response(
            DailyFinancialStatementSerializer(
                obj,
                context={
                    "request": request
                }
            ).data
        )

    # ========================================================
    # PUT
    # ========================================================

    @transaction.atomic
    def put(self, request, pk):

        if not _accountant_financial_permission(
            request,
            "edit"
        ):
            from rest_framework.exceptions import PermissionDenied

            raise PermissionDenied(
                "You do not have permission to edit daily financial statements."
            )

        obj = self.get_object(pk)

        if not obj:

            return Response(
                {
                    "detail": "Daily financial statement not found."
                },
                status=404,
            )

        serializer = DailyFinancialStatementSerializer(
            obj,
            data=request.data,
            partial=True,
            context={
                "request": request
            }
        )

        serializer.is_valid(
            raise_exception=True
        )

        # ----------------------------------------------------
        # PREVENT DUPLICATE BUSINESS + DATE
        # ----------------------------------------------------

        new_business = serializer.validated_data.get(
            "business",
            obj.business
        )

        new_date = serializer.validated_data.get(
            "statement_date",
            obj.statement_date
        )

        duplicate = (
            DailyFinancialStatement.objects
            .filter(
                business=new_business,
                statement_date=new_date,
                deleted_at__isnull=True,
            )
            .exclude(
                pk=obj.pk
            )
            .exists()
        )

        if duplicate:

            return Response(
                {
                    "detail": (
                        "Another daily financial statement already "
                        "exists for this business and date."
                    )
                },
                status=400,
            )

        old_status = obj.status

        obj = serializer.save()

        # ----------------------------------------------------
        # REVIEW INFORMATION
        # ----------------------------------------------------

        if (
            obj.status
            in {
                DailyFinancialStatement.Status.REVIEWED,
                DailyFinancialStatement.Status.APPROVED,
                DailyFinancialStatement.Status.REJECTED,
            }
            and (
                old_status != obj.status
                or obj.reviewed_by_id != request.user.id
            )
        ):

            obj.reviewed_by = request.user
            obj.reviewed_at = timezone.now()

            obj.save(
                update_fields=[
                    "reviewed_by",
                    "reviewed_at",
                    "updated_at",
                ]
            )

        audit(
            request.user,
            "UPDATE",
            "DAILY_FINANCIAL_STATEMENT",
            obj,
            "Daily financial statement updated.",
        )

        return Response(
            DailyFinancialStatementSerializer(
                obj,
                context={
                    "request": request
                }
            ).data
        )

    # ========================================================
    # PATCH
    # ========================================================

    def patch(self, request, pk):

        return self.put(
            request,
            pk
        )

    # ========================================================
    # DELETE
    # ========================================================

    @transaction.atomic
    def delete(self, request, pk):

        if not _accountant_financial_permission(
            request,
            "delete"
        ):
            from rest_framework.exceptions import PermissionDenied

            raise PermissionDenied(
                "You do not have permission to delete daily financial statements."
            )

        obj = self.get_object(pk)

        if not obj:

            return Response(
                {
                    "detail": "Daily financial statement not found."
                },
                status=404,
            )

        obj.soft_delete()

        audit(
            request.user,
            "DELETE",
            "DAILY_FINANCIAL_STATEMENT",
            obj,
            "Daily financial statement moved to restore bin.",
        )

        return Response(
            status=204
        )


class BusinessPerformanceDashboardAPIView(APIView):
    """Recent P&L trend and latest Balance Sheet KPIs for an accessible business."""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        business_id = request.query_params.get("business")
        if not business_id:
            return Response({"detail": "business query parameter is required."}, status=400)

        business = Business.objects.filter(id=business_id, deleted_at__isnull=True).first()
        if not business:
            return Response({"detail": "Business not found."}, status=404)

        role = getattr(request.user, "role", None)
        if role == User.Roles.BUSINESS:
            if not Business.objects.filter(user=request.user, id=business.id, deleted_at__isnull=True).exists():
                return Response({"detail": "You do not have access to this business."}, status=403)
        elif role not in (User.Roles.ADMIN, User.Roles.INVESTOR, User.Roles.ACCOUNTANT, User.Roles.AGENT):
            if not request.user.is_staff and not request.user.is_superuser:
                return Response({"detail": "You do not have access to this business."}, status=403)

        try:
            limit = max(3, min(int(request.query_params.get("limit", 12)), 24))
        except (TypeError, ValueError):
            limit = 12

        pnl = list(
            DailyFinancialStatement.objects.filter(
                business=business, deleted_at__isnull=True
            ).order_by("-statement_date", "-id")[:limit]
        )
        pnl.reverse()
        latest_pnl = pnl[-1] if pnl else None
        previous_pnl = pnl[-2] if len(pnl) > 1 else None

        balance_qs = BalanceSheet.objects.filter(
            business=business, deleted_at__isnull=True
        )
        if latest_pnl:
            balance_qs = balance_qs.filter(statement_date__lte=latest_pnl.statement_date)
        balance = balance_qs.order_by("-statement_date", "-id").first()

        def dec(value):
            return Decimal(value or 0)

        latest_profit = dec(latest_pnl.net_profit) if latest_pnl else Decimal("0.00")
        previous_profit = dec(previous_pnl.net_profit) if previous_pnl else None

        return Response({
            "business": {
                "id": business.id,
                "name": business.name,
                "location": getattr(business, "location", ""),
                "owner_name": getattr(business, "owner_name", ""),
            },
            "kpis": {
                "latest_period": str(latest_pnl.statement_date) if latest_pnl else None,
                "revenue": dec(latest_pnl.revenue) if latest_pnl else Decimal("0.00"),
                "net_profit": latest_profit,
                "profit_margin": dec(latest_pnl.profit_margin) if latest_pnl else Decimal("0.00"),
                "total_equity": dec(balance.total_equity) if balance else Decimal("0.00"),
                "total_assets": dec(balance.total_assets) if balance else Decimal("0.00"),
                "total_liabilities": dec(balance.total_liabilities) if balance else Decimal("0.00"),
                "balance_sheet_date": str(balance.statement_date) if balance else None,
                "profit_change_from_previous_period": latest_profit - previous_profit if previous_profit is not None else None,
                "financial_status": latest_pnl.financial_status if latest_pnl else None,
            },
            "pnl_chart": [{
                "period": str(x.statement_date),
                "revenue": dec(x.revenue),
                "gross_profit": dec(x.gross_profit),
                "operating_profit": dec(x.operating_profit),
                "net_profit": dec(x.net_profit),
                "profit_margin": dec(x.profit_margin),
                "financial_status": x.financial_status,
            } for x in pnl],
        })


class InvestorPortalBusinessPerformanceAPIView(APIView):
    """
    Investor-safe business performance.

    Only businesses in which the logged-in investor has an active investment
    are returned. Financial position fields such as assets/liabilities/equity
    are deliberately not exposed here.
    """

    permission_classes = [IsAuthenticated]

    def get(self, request):
        investor = Investor.objects.filter(
            user=request.user,
            deleted_at__isnull=True
        ).first()

        if not investor:
            return Response(
                {
                    "detail": (
                        "Investor account is not linked "
                        "to an Investor record."
                    )
                },
                status=404
            )

        business_ids = (
            Investment.objects
            .filter(
                investor=investor,
                deleted_at__isnull=True
            )
            .values_list("business_id", flat=True)
            .distinct()
        )

        businesses = Business.objects.filter(
            id__in=business_ids,
            deleted_at__isnull=True
        ).order_by("name")

        results = []

        for business in businesses:
            statements = list(
                DailyFinancialStatement.objects
                .filter(
                    business=business,
                    deleted_at__isnull=True
                )
                .order_by("-statement_date", "-id")[:2]
            )

            if not statements:
                continue

            latest = statements[0]
            previous = statements[1] if len(statements) > 1 else None

            latest_net_profit = Decimal(latest.net_profit or 0)
            previous_net_profit = (
                Decimal(previous.net_profit or 0)
                if previous
                else None
            )

            change = None
            if previous is not None:
                change = latest_net_profit - previous_net_profit

            results.append({
                "business_id": business.id,
                "business_name": business.name,
                "period": str(latest.statement_date),
                "status": latest.financial_status,
                "net_profit": latest_net_profit,
                "profit_margin": latest.profit_margin,
                "updated_at": latest.updated_at,
                "change_from_previous_period": change,
            })

        return Response({
            "count": len(results),
            "results": results,
        })


