# from calendar import monthrange
# from datetime import date, timedelta
# from decimal import Decimal, ROUND_HALF_UP
# from django.db import transaction
# from django.db.models import Sum
# from django.utils import timezone

# D = Decimal
# CENT = D("0.01")


# def money(value):
#     return D(value).quantize(CENT, rounding=ROUND_HALF_UP)


# def months_after(start_date, months):
#     y = start_date.year + (start_date.month - 1 + months) // 12
#     m = (start_date.month - 1 + months) % 12 + 1
#     day = min(start_date.day, monthrange(y, m)[1])
#     return date(y, m, day)


# def simple_interest(principal, annual_rate, months):
#     return D(principal) * D(annual_rate) / D("100") * D(months) / D("12")


# def compound_maturity(principal, annual_rate, months, frequency="MONTHLY"):
#     freq_map = {"MONTHLY": 12, "QUARTERLY": 4, "HALF_YEARLY": 2, "YEARLY": 1}
#     n_per_year = freq_map.get((frequency or "MONTHLY").upper(), 12)
#     periods = D(months) / D("12") * D(n_per_year)
#     periods_int = int(periods)
#     if D(periods_int) != periods:
#         raise ValueError("Tenure is not compatible with the selected compounding frequency.")
#     return D(principal) * (D("1") + D(annual_rate) / D("100") / D(n_per_year)) ** periods_int


# def platinum_rd_maturity(monthly_payment, months, annual_rate):
#     p = D(monthly_payment)
#     n = D(months)
#     r = D(annual_rate)
#     principal = p * n
#     interest = p * n * (n + D("1")) / (D("2") * D("12")) * r / D("100")
#     return money(principal + interest)


# def platinum_actual_rd_maturity(investment):
#     """
#     Uses the actual monthly contribution records.
#     Each contribution is treated as one RD installment under the supplied
#     standard RD formula. Missing months contribute zero.
#     """
#     contributions = investment.recurring_contributions.all().order_by("contribution_date")
#     total_principal = D("0")
#     total_interest = D("0")
#     maturity_date = investment.maturity_date or months_after(
#         investment.investment_date, investment.scheme.tenure_months
#     )
#     for c in contributions:
#         total_principal += D(c.amount)
#         remaining_months = max(
#             D("0"),
#             D((maturity_date.year - c.contribution_date.year) * 12
#               + (maturity_date.month - c.contribution_date.month))
#         )
#         # Standard monthly RD approximation for the remaining months.
#         total_interest += D(c.amount) * remaining_months * (remaining_months + D("1")) / (D("2") * D("12"))             * D(investment.investor_annual_rate) / D("100")
#     return money(total_principal + total_interest)


# def latest_business_equity(business, as_of=None):
#     """Return the latest Balance Sheet total equity for a business.

#     The valuation source for Diamond is the Balance Sheet total equity.
#     Only statements dated on or before ``as_of`` are considered so a future
#     balance sheet can never change a historical investment valuation.
#     """
#     from .models import BalanceSheet

#     qs = BalanceSheet.objects.filter(
#         business=business,
#         deleted_at__isnull=True,
#     )
#     if as_of is not None:
#         qs = qs.filter(statement_date__lte=as_of)

#     sheet = qs.order_by("-statement_date", "-id").first()
#     if not sheet:
#         raise ValueError("Diamond investment requires a latest Balance Sheet for the business.")

#     return money(sheet.total_equity), sheet


# def diamond_maturity_valuation(investment, as_of=None):
#     """Return the latest business equity used as Diamond maturity valuation."""
#     valuation, _ = latest_business_equity(investment.business, as_of=as_of)
#     return valuation


# def diamond_ownership_percentage(investment):
#     """Return the ownership percentage locked at Diamond investment creation."""
#     snapshot = investment.terms_snapshot or {}
#     value = snapshot.get("diamond_ownership_percentage")
#     if value is None:
#         raise ValueError("Diamond ownership percentage is missing from the investment snapshot.")
#     return D(value)


# def kuber_equity_share_percentage(investment):
#     """Return the ownership percentage locked for a Kuber Equity Share investment."""
#     value = investment.equity_share_percentage
#     if value is None:
#         raise ValueError("Kuber Equity Share percentage is missing from the investment.")
#     return D(value)


# def kuber_equity_current_principal(investment, as_of=None):
#     """Return the displayed principal for a Kuber investment.

#     Equity Share percentage is display-only and is deliberately excluded from
#     all financial calculations. Therefore the displayed principal remains the
#     recorded investment amount, exactly like an Ordinary investment.
#     """
#     return money(investment.amount)


# def record_kuber_equity_investment_on_balance_sheet(*, business, investment_date, amount, entered_by):
#     """Record Kuber Equity Share funding in the business Balance Sheet."""
#     from .models import BalanceSheet, DailyFinancialStatement

#     amount = money(amount)
#     previous = BalanceSheet.objects.filter(
#         business=business,
#         statement_date__lte=investment_date,
#         deleted_at__isnull=True,
#     ).order_by("-statement_date", "-id").first()
#     if previous is None:
#         raise ValueError("Kuber Equity Share investment requires a Balance Sheet on or before the investment date.")

#     if previous.statement_date == investment_date:
#         sheet = previous
#     else:
#         copy_fields = [
#             "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",
#         ]
#         data = {field: getattr(previous, field) for field in copy_fields}
#         sheet = BalanceSheet(
#             business=business,
#             statement_date=investment_date,
#             entered_by=entered_by,
#             **data,
#         )

#     sheet.bank_balance = D(sheet.bank_balance) + amount
#     sheet.owner_capital = D(sheet.owner_capital) + amount
#     sheet.remarks = (
#         f"{sheet.remarks} | Kuber Equity Share funding +{amount} "
#         f"recorded on {investment_date}."
#     ).strip(" |")
#     sheet.entered_by = entered_by
#     sheet.save()

#     financial = DailyFinancialStatement.objects.filter(
#         business=business,
#         statement_date=investment_date,
#         deleted_at__isnull=True,
#     ).first()
#     if financial:
#         financial.financing_cash_inflow = D(financial.financing_cash_inflow) + amount
#         financial.save(update_fields=["financing_cash_inflow", "updated_at"])

#     return sheet


# @transaction.atomic
# def create_kuber_business_investment(*, business, investment_type, amount, annual_rate,
#                                       equity_share_percentage=None, investment_date=None, maturity_date=None,
#                                       notes="", created_by=None):
#     """Create an Ordinary or Equity Share Kuber -> Business investment."""
#     from .models import KuberBusinessInvestment

#     investment_date = investment_date or timezone.localdate()
#     amount = money(amount)
#     investment_type = investment_type or KuberBusinessInvestment.InvestmentTypes.ORDINARY

#     if maturity_date is None:
#         raise ValueError("Maturity date is required for Kuber Business investment.")
#     if maturity_date < investment_date:
#         raise ValueError("Maturity date cannot be before the investment date.")

#     if amount <= 0:
#         raise ValueError("Investment amount must be greater than zero.")

#     if investment_type == KuberBusinessInvestment.InvestmentTypes.EQUITY_SHARE:
#         # Share percentage is entered manually and is display-only. It is NOT
#         # used to calculate principal, valuation, interest, maturity, or any
#         # other financial value.
#         if equity_share_percentage is None:
#             raise ValueError("Equity Share investment requires a share percentage.")
#         share = D(equity_share_percentage)
#         if share <= 0 or share > 100:
#             raise ValueError("Equity Share percentage must be greater than 0 and cannot exceed 100.")
#         annual_rate = D(annual_rate or 0)
#     else:
#         share = None
#         annual_rate = D(annual_rate or 0)

#     obj = KuberBusinessInvestment.objects.create(
#         business=business,
#         investment_type=investment_type,
#         amount=amount,
#         annual_rate=annual_rate,
#         equity_share_percentage=share,
#         investment_date=investment_date,
#         maturity_date=maturity_date,
#         notes=notes or "",
#         created_by=created_by,
#     )

#     if investment_type == KuberBusinessInvestment.InvestmentTypes.EQUITY_SHARE:
#         record_kuber_equity_investment_on_balance_sheet(
#             business=business,
#             investment_date=investment_date,
#             amount=amount,
#             entered_by=created_by,
#         )

#     return obj


# def investment_current_principal(investment, as_of=None):
#     """Return the current principal/value shown to Admin/Accountant.

#     Diamond is equity-linked and therefore uses current Balance Sheet Total
#     Equity multiplied by the locked ownership percentage. Other schemes keep
#     their existing principal/contributed-capital behaviour.
#     """
#     if investment.scheme.category == investment.scheme.Categories.DIAMOND:
#         equity = diamond_maturity_valuation(investment, as_of=as_of or timezone.localdate())
#         ownership = diamond_ownership_percentage(investment) / D("100")
#         return money(equity * ownership)

#     if investment.scheme.category == investment.scheme.Categories.PLATINUM:
#         principal = investment.recurring_contributions.filter(
#             contribution_date__lte=as_of or timezone.localdate(),
#             deleted_at__isnull=True,
#         ).aggregate(v=Sum("amount"))["v"] or D("0")
#         return money(principal)

#     return money(investment.amount or D("0"))


# def maturity_value(investment):
#     scheme = investment.scheme
#     if scheme.category == scheme.Categories.SILVER:
#         if investment.amount is None:
#             raise ValueError("Silver investment requires a one-time amount.")
#         return money(D(investment.amount) + simple_interest(
#             investment.amount, investment.investor_annual_rate, scheme.tenure_months
#         ))

#     if scheme.category == scheme.Categories.GOLD:
#         if investment.amount is None:
#             raise ValueError("Gold investment requires a one-time amount.")
#         return money(compound_maturity(
#             investment.amount, investment.investor_annual_rate,
#             scheme.tenure_months, scheme.compounding_frequency or "MONTHLY"
#         ))

#     if scheme.category == scheme.Categories.PLATINUM:
#         return platinum_actual_rd_maturity(investment)

#     if scheme.category == scheme.Categories.DIAMOND:
#         if investment.amount is None:
#             raise ValueError("Diamond investment requires a one-time amount.")

#         # Diamond does not have a fixed principal at maturity. The investor's
#         # ownership percentage is locked at investment time and the principal
#         # component at maturity is the latest business equity multiplied by
#         # that percentage. Interest remains a normal daily ledger accrual and
#         # is added separately to the valuation-linked maturity value.
#         maturity_date = investment.maturity_date or months_after(
#             investment.investment_date, scheme.tenure_months
#         )
#         final_equity = diamond_maturity_valuation(
#             investment, as_of=maturity_date
#         )
#         ownership = diamond_ownership_percentage(investment) / D("100")
#         valuation_value = final_equity * ownership
#         interest = interest_till_date(investment, as_of=maturity_date)
#         return money(valuation_value + interest)

#     raise ValueError("Unsupported scheme category.")


# def interest_till_date(investment, as_of=None):
#     """Return accumulated investor interest payable for this investment.

#     Interest is the sum of the investment's SOFT_DEBIT ledger entries up to
#     the requested date.  This keeps the displayed interest tied to the ledger.
#     """
#     from .models import LedgerEntry
#     as_of = as_of or timezone.localdate()
#     total = LedgerEntry.objects.filter(
#         investment=investment,
#         entry_type=LedgerEntry.EntryTypes.SOFT_DEBIT,
#         entry_date__lte=as_of,
#     ).aggregate(v=Sum("amount"))["v"] or D("0")
#     return money(total)

# def diamond_interest_till_date(investment, as_of=None):
#     """Return Diamond interest accrued through ``as_of``.

#     Diamond interest is calculated directly from the investment amount and
#     the locked annual scheme rate. It is intentionally separate from the
#     Business Ledger because Diamond is a direct company <-> investor
#     investment. Accrual stops at the investment maturity date.
#     """
#     if investment.scheme.category != investment.scheme.Categories.DIAMOND:
#         return interest_till_date(investment, as_of=as_of)

#     as_of = as_of or timezone.localdate()
#     start = investment.investment_date
#     if not start:
#         return D("0.00")

#     maturity = investment.maturity_date
#     end = min(as_of, maturity) if maturity else as_of

#     if end < start:
#         return D("0.00")

#     days = D((end - start).days + 1)
#     principal = D(investment.amount or D("0"))
#     annual_rate = D(
#         investment.investor_annual_rate
#         or investment.scheme.annual_interest_rate
#         or D("0")
#     )

#     if principal <= 0 or annual_rate <= 0:
#         return D("0.00")

#     return money(
#         principal * annual_rate * days / D("100") / D("365")
#     )

# def premature_value(investment, as_of=None):
#     """Return the premature value for schemes that support premature exit."""
#     as_of = as_of or timezone.localdate()

#     if investment.scheme.category == investment.scheme.Categories.DIAMOND:
#         raise ValueError("Diamond investment does not support premature withdrawal.")

#     principal = D("0")
#     if investment.scheme.category == investment.scheme.Categories.PLATINUM:
#         principal = investment.recurring_contributions.filter(
#             contribution_date__lte=as_of, deleted_at__isnull=True
#         ).aggregate(v=Sum("amount"))["v"] or D("0")
#     else:
#         principal = investment.amount or D("0")

#     interest = interest_till_date(investment, as_of)
#     gross = D(principal) + D(interest)
#     penalty = gross * D(investment.scheme.penalty_rate) / D("100")
#     return money(max(D("0"), gross - penalty))


# def daily_business_credit(amount, annual_rate):
#     return money(D(amount) * D(annual_rate) / D("100") / D("365"))


# def daily_investor_soft_debit(investment, entry_date):
#     """
#     Daily payable accrual based on the investment principal/contributions.
#     The scheme's annual rate is copied onto the investment at creation.
#     """
#     if investment.scheme.category == investment.scheme.Categories.PLATINUM:
#         principal = investment.recurring_contributions.filter(
#             contribution_date__lte=entry_date, deleted_at__isnull=True
#         ).aggregate(v=Sum("amount"))["v"] or D("0")
#     else:
#         principal = investment.amount or D("0")
#     return money(D(principal) * D(investment.investor_annual_rate) / D("100") / D("365"))



# def calculate_daily_financials(statement):
#     """Return the canonical P&L and cash-flow calculations for a daily statement."""
#     return {
#         "gross_profit": money(statement.gross_profit),
#         "operating_profit": money(statement.operating_profit),
#         "profit_before_tax": money(statement.profit_before_tax),
#         "net_profit": money(statement.net_profit),
#         "profit_margin": money(statement.profit_margin),
#         "financial_status": statement.financial_status,
#         "operating_cash_flow": money(statement.operating_cash_flow),
#         "investing_cash_flow": money(statement.investing_cash_flow),
#         "financing_cash_flow": money(statement.financing_cash_flow),
#         "net_cash_flow": money(statement.net_cash_flow),
#     }


# def build_default_certificate_html(investment, attachments=(), request=None):
#     """Build the standard multi-page certificate template used for every certificate."""
#     from html import escape

#     def file_url(att):
#         try:
#             url = att.file.url
#             return request.build_absolute_uri(url) if request else url
#         except Exception:
#             return ""

#     rules = investment.scheme.rules or []
#     rules_html = "".join(f"<li>{escape(str(r))}</li>" for r in rules) or "<li>No additional rules specified.</li>"
#     if investment.scheme.category == investment.scheme.Categories.DIAMOND:
#         ownership = (investment.terms_snapshot or {}).get("diamond_ownership_percentage")
#         valuation_before = (investment.terms_snapshot or {}).get("diamond_valuation_before_investment")
#         valuation_after = (investment.terms_snapshot or {}).get("diamond_valuation_after_investment")
#         amount = (
#             f"Investment Amount: ₹{investment.amount} · "
#             f"Initial Business Equity: ₹{valuation_before or '—'} · "
#             f"Post-Investment Valuation: ₹{valuation_after or '—'} · "
#             f"Locked Ownership: {ownership or '—'}%"
#         )
#     else:
#         amount = (
#             f"Monthly Installment: ₹{investment.monthly_installment}"
#             if investment.scheme.category == investment.scheme.Categories.PLATINUM
#             else f"Investment Amount: ₹{investment.amount}"
#         )
#     pages = [f"""
#     <section class="certificate-page cover-page">
#       <div class="certificate-inner">
#         <div class="brand-mark">KUBER TROBE</div>
#         <div class="certificate-kicker">OFFICIAL DOCUMENT</div>
#         <h1>Investment Certificate</h1>
#         <div class="certificate-line"></div>
#         <p class="lead">This certificate confirms the investment recorded with Kuber Trobe.</p>
#         <div class="certificate-number">Certificate ID: {investment.pk}</div>
#       </div>
#     </section>
#     <section class="certificate-page">
#       <div class="certificate-inner">
#         <div class="page-header"><b>KUBER TROBE</b><span>Investment Certificate</span></div>
#         <h2>Investment Details</h2>
#         <div class="info-grid">
#           <div><label>Investor</label><strong>{escape(investment.investor.name)}</strong></div>
#           <div><label>Business</label><strong>{escape(investment.business.name)}</strong></div>
#           <div><label>Scheme Category</label><strong>{escape(investment.scheme.get_category_display())}</strong></div>
#           <div><label>Scheme</label><strong>{escape(investment.scheme.name)}</strong></div>
#           <div><label>Amount</label><strong>{escape(amount)}</strong></div>
#           <div><label>Interest Rate</label><strong>{investment.investor_annual_rate}% p.a.</strong></div>
#           <div><label>Tenure</label><strong>{investment.scheme.tenure_months} months</strong></div>
#           <div><label>Investment Date</label><strong>{investment.investment_date}</strong></div>
#           <div><label>Maturity Date</label><strong>{investment.maturity_date or "—"}</strong></div>
#           <div><label>Withdrawal Type</label><strong>{escape("No Premature Withdrawal" if investment.scheme.category == investment.scheme.Categories.DIAMOND else (investment.scheme.withdrawal_type or "—"))}</strong></div>
#         </div>
#         <h2>Terms &amp; Rules</h2>
#         <ul class="rules">{rules_html}</ul>
#         <div class="signature-row"><span>Authorised Signatory</span><span>Kuber Trobe</span></div>
#       </div>
#     </section>
#     """]

#     for att in attachments:
#         url = file_url(att)
#         lower = (att.file.name or "").lower()
#         if lower.endswith((".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg")):
#             content = f'<img class="attachment-image" src="{escape(url)}" alt="Certificate attachment">'
#         elif lower.endswith(".pdf"):
#             content = f'<iframe class="attachment-frame" src="{escape(url)}"></iframe>'
#         else:
#             content = f'<iframe class="attachment-frame" src="{escape(url)}"></iframe>'
#         pages.append(f"""
#         <section class="certificate-page attachment-page">
#           <div class="certificate-inner">
#             <div class="page-header"><b>KUBER TROBE</b><span>Certificate Attachment</span></div>
#             <div class="attachment-content">{content}</div>
#           </div>
#         </section>
#         """)

#     return f"""<!doctype html><html><head><meta charset="utf-8"><title>Kuber Trobe Investment Certificate</title>
#     <style>
#     *{{box-sizing:border-box}} body{{margin:0;background:#eef1f5;color:#172033;font-family:Arial,Helvetica,sans-serif}}
#     .certificate-page{{width:210mm;min-height:297mm;margin:0 auto 10px;background:#fff;page-break-after:always;position:relative}}
#     .certificate-page:last-child{{page-break-after:auto}} .certificate-inner{{min-height:297mm;padding:22mm 20mm;position:relative;border:12px solid #f4f5f7}}
#     .cover-page .certificate-inner{{display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center}}
#     .brand-mark{{font-size:26px;font-weight:800;letter-spacing:3px}} .certificate-kicker{{margin-top:45mm;font-size:12px;letter-spacing:4px;color:#667085}}
#     h1{{font-size:38px;margin:14px 0}} h2{{font-size:20px;margin:24px 0 12px}} .certificate-line{{width:90px;border-top:3px solid #172033}}
#     .lead{{color:#667085;font-size:15px;max-width:500px;line-height:1.7}} .certificate-number{{margin-top:25mm;font-size:12px;color:#667085}}
#     .page-header{{display:flex;justify-content:space-between;border-bottom:1px solid #ddd;padding-bottom:10px;font-size:12px;color:#667085}}
#     .info-grid{{display:grid;grid-template-columns:1fr 1fr;gap:12px}} .info-grid div{{border:1px solid #e4e7ec;border-radius:6px;padding:11px}}
#     label{{display:block;color:#667085;font-size:10px;text-transform:uppercase;margin-bottom:5px}} strong{{font-size:13px}} .rules{{line-height:1.7;padding-left:20px}}
#     .signature-row{{display:flex;justify-content:space-between;border-top:1px solid #ddd;margin-top:35mm;padding-top:10px;font-size:11px;color:#667085}}
#     .attachment-content{{height:250mm;margin-top:10mm;display:flex;justify-content:center;align-items:center;overflow:hidden}}
#     .attachment-image{{max-width:100%;max-height:245mm;object-fit:contain}} .attachment-frame{{width:100%;height:245mm;border:1px solid #ddd}}
#     @media print{{body{{background:#fff}} .certificate-page{{margin:0}}}}
#     </style></head><body>{"".join(pages)}</body></html>"""

# def record_diamond_investment_on_balance_sheet(*, business, investment_date, amount, entered_by):
#     """Record Diamond funding in the business Balance Sheet.

#     The investment is treated as new business funding: the received amount
#     increases bank balance (asset) and owner capital (equity) by the same
#     amount, so the Balance Sheet remains balanced. If there is no statement
#     on the investment date, a new point-in-time statement is copied from the
#     latest statement on/before that date.
#     """
#     from .models import BalanceSheet, DailyFinancialStatement

#     amount = money(amount)
#     previous = BalanceSheet.objects.filter(
#         business=business,
#         statement_date__lte=investment_date,
#         deleted_at__isnull=True,
#     ).order_by("-statement_date", "-id").first()
#     if previous is None:
#         raise ValueError("Diamond investment requires a Balance Sheet on or before the investment date.")

#     # Update the most recent Balance Sheet for this business. Diamond funding
#     # increases the business equity by the investment amount, so the latest
#     # statement's calculated Total Equity increases automatically through
#     # owner_capital. Do not create a separate historical statement merely
#     # because the investment date differs from the statement date.
#     sheet = previous

#     sheet.bank_balance = D(sheet.bank_balance) + amount
#     sheet.owner_capital = D(sheet.owner_capital) + amount
#     if not sheet.remarks:
#         sheet.remarks = ""
#     sheet.remarks = (
#         f"{sheet.remarks} | Diamond investment funding +{amount} "
#         f"recorded on {investment_date}."
#     ).strip(" |")
#     sheet.entered_by = entered_by
#     sheet.save()

#     # The same cash receipt is also reflected in the day's financing cash flow.
#     financial = DailyFinancialStatement.objects.filter(
#         business=business,
#         statement_date=investment_date,
#         deleted_at__isnull=True,
#     ).first()
#     if financial:
#         financial.financing_cash_inflow = D(financial.financing_cash_inflow) + amount
#         financial.save(update_fields=["financing_cash_inflow", "updated_at"])

#     return sheet


# def build_terms_snapshot(scheme, investment, diamond_valuation_before=None, diamond_sheet=None):
#     snapshot = {
#         "category": scheme.category,
#         "scheme_name": scheme.name,
#         "tenure_months": scheme.tenure_months,
#         "annual_interest_rate": str(scheme.annual_interest_rate),
#         "penalty_rate": str(scheme.penalty_rate),
#         "withdrawal_type": scheme.withdrawal_type,
#         "compounding_frequency": scheme.compounding_frequency,
#         "rules": scheme.rules,
#     }

#     if scheme.category == scheme.Categories.PLATINUM:
#         snapshot["monthly_installment"] = str(investment.monthly_installment)
#     elif scheme.category == scheme.Categories.DIAMOND:
#         if diamond_valuation_before is None:
#             valuation, sheet = latest_business_equity(
#                 investment.business, as_of=investment.investment_date
#             )
#         else:
#             valuation = money(diamond_valuation_before)
#             sheet = diamond_sheet
#         amount = D(investment.amount)
#         post_investment_valuation = valuation + amount
#         if post_investment_valuation <= 0:
#             raise ValueError("Diamond post-investment valuation must be greater than zero.")

#         ownership = (amount / post_investment_valuation) * D("100")
#         snapshot.update({
#             "investment_amount_type": "ONE_TIME",
#             "diamond_valuation_before_investment": str(money(valuation)),
#             "diamond_valuation_after_investment": str(money(post_investment_valuation)),
#             "diamond_ownership_percentage": str(ownership),
#             "diamond_valuation_statement_date": str(sheet.statement_date),
#             "diamond_valuation_source": "BALANCE_SHEET_TOTAL_EQUITY",
#             "diamond_maturity_rule": "LATEST_BUSINESS_EQUITY_X_LOCKED_OWNERSHIP_PERCENTAGE_PLUS_LEDGER_INTEREST",
#             "diamond_premature_withdrawal": False,
#         })
#     else:
#         snapshot["investment_amount_type"] = "ONE_TIME"

#     return snapshot


# def audit(actor, operation, module, obj=None, description="", metadata=None):
#     from .models import AuditLog
#     return AuditLog.objects.create(
#         actor=actor,
#         operation=operation,
#         module=module,
#         object_type=obj.__class__.__name__ if obj else "",
#         object_id=str(obj.pk) if obj and getattr(obj, "pk", None) else "",
#         description=description,
#         metadata=metadata or {},
#     )


# @transaction.atomic
# def create_investment_with_snapshot(
#     *, investor, business, scheme, amount=None, monthly_installment=None,
#     created_by, investment_date=None
# ):
#     from .models import Investment
#     investment_date = investment_date or timezone.localdate()

#     if scheme.category == scheme.Categories.PLATINUM:
#         if monthly_installment is None or D(monthly_installment) <= 0:
#             raise ValueError("Platinum investment requires a positive monthly_installment.")
#         amount = None
#     else:
#         if amount is None or D(amount) <= 0:
#             raise ValueError("Silver, Gold and Diamond investments require a positive one-time amount.")
#         monthly_installment = None

#     maturity_date = months_after(investment_date, scheme.tenure_months)
#     # The scheme percentage is authoritative; admin does not type a separate rate.
#     investor_rate = scheme.annual_interest_rate

#     diamond_pre_valuation = None
#     diamond_pre_sheet = None
#     if scheme.category == scheme.Categories.DIAMOND:
#         diamond_pre_valuation, diamond_pre_sheet = latest_business_equity(
#             business, as_of=investment_date
#         )

#     obj = Investment.objects.create(
#         investor=investor,
#         business=business,
#         scheme=scheme,
#         amount=amount,
#         monthly_installment=monthly_installment,
#         investor_annual_rate=investor_rate,
#         investment_date=investment_date,
#         maturity_date=maturity_date,
#         created_by=created_by,
#     )
#     if scheme.category == scheme.Categories.DIAMOND:
#         # Diamond is a direct company <-> investor Investment record. It does
#         # not create KuberBusinessInvestment, but the received funding is
#         # added to the business's most recent Balance Sheet so Total Equity
#         # reflects the new Diamond investment.
#         record_diamond_investment_on_balance_sheet(
#             business=business,
#             investment_date=investment_date,
#             amount=amount,
#             entered_by=created_by,
#         )

#     obj.terms_snapshot = build_terms_snapshot(
#         scheme,
#         obj,
#         diamond_valuation_before=diamond_pre_valuation,
#         diamond_sheet=diamond_pre_sheet,
#     )
#     obj.save(update_fields=["terms_snapshot"])
#     return obj


# @transaction.atomic
# def generate_daily_ledger(entry_date=None):
#     """
#     Generate every missing daily ledger entry up to entry_date.

#     The function is idempotent. If the server was offline for previous
#     days, running Generate Today backfills those missing dates without
#     duplicating entries that already exist.

#     Platinum starts accruing only from the first actual recurring
#     contribution date, and the daily amount uses all actual contributions
#     received up to each ledger date.
#     """
#     from .models import KuberBusinessInvestment, Investment, LedgerEntry

#     entry_date = entry_date or timezone.localdate()
#     created_ids = []

#     # ------------------------------------------------------------
#     # KUBER -> BUSINESS CREDIT PROFIT
#     # ------------------------------------------------------------
#     kuber_qs = (
#         KuberBusinessInvestment.objects
#         .filter(
#             deleted_at__isnull=True,
#             investment_date__lte=entry_date,
#         )
#         .select_related("business")
#     )

#     for ki in kuber_qs:
#         current_date = ki.investment_date
#         end_date = (
#             min(entry_date, ki.maturity_date)
#             if ki.maturity_date
#             else entry_date
#         )

#         while current_date <= end_date:
#             exists = LedgerEntry.objects.filter(
#                 kuber_investment=ki,
#                 entry_date=current_date,
#                 entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT,
#             ).exists()

#             if not exists:
#                 amount = daily_business_credit(ki.amount, ki.annual_rate)
#                 description = "Automatic daily Kuber-to-business credit profit."

#                 # The existing ledger constraint allows one CREDIT_PROFIT entry
#                 # per Kuber investment per day. On the maturity date, the daily
#                 # profit and the principal are therefore recorded together in
#                 # the same Credit Profit entry.
#                 if ki.maturity_date and current_date == ki.maturity_date:
#                     amount = money(D(amount) + D(ki.amount))
#                     description = (
#                         "Kuber investment matured: principal plus daily profit "
#                         "credited to business ledger."
#                     )

#                 if amount:
#                     e = LedgerEntry.objects.create(
#                         business=ki.business,
#                         kuber_investment=ki,
#                         entry_date=current_date,
#                         entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT,
#                         amount=amount,
#                         annual_rate=ki.annual_rate,
#                         description=description,
#                         reference=f"KBI-{ki.pk}-{current_date}",
#                     )
#                     created_ids.append(e.pk)

#             current_date += timedelta(days=1)

#     # ------------------------------------------------------------
#     # INVESTOR SOFT DEBIT
#     # ------------------------------------------------------------
#     investments = (
#         Investment.objects
#         .filter(
#             deleted_at__isnull=True,
#             investment_date__lte=entry_date,
#             status=Investment.Status.ACTIVE,
#         )
#         .select_related("business", "investor", "scheme")
#     )

#     for inv in investments:
#         if inv.scheme.category == inv.scheme.Categories.PLATINUM:
#             first_contribution = (
#                 inv.recurring_contributions
#                 .filter(
#                     deleted_at__isnull=True,
#                     contribution_date__lte=entry_date,
#                 )
#                 .order_by("contribution_date")
#                 .first()
#             )

#             if not first_contribution:
#                 # No actual Platinum payment means no investor interest yet.
#                 continue

#             start_date = first_contribution.contribution_date
#         else:
#             start_date = inv.investment_date

#         current_date = start_date
#         end_date = (
#             min(entry_date, inv.maturity_date)
#             if inv.maturity_date
#             else entry_date
#         )

#         while current_date <= end_date:
#             amount = daily_investor_soft_debit(inv, current_date)
#             description = (
#                 f"Automatic daily {inv.scheme.get_category_display()} "
#                 "investor soft debit."
#             )

#             # On maturity, add the original principal to the same Soft Debit
#             # ledger entry as the final day's accrued return. This preserves
#             # the existing one-entry-per-day constraint.
#             if (
#                 inv.maturity_date
#                 and current_date == inv.maturity_date
#                 and inv.scheme.category != inv.scheme.Categories.DIAMOND
#             ):
#                 principal = (
#                     inv.recurring_contributions
#                     .filter(
#                         contribution_date__lte=current_date,
#                         deleted_at__isnull=True,
#                     )
#                     .aggregate(v=Sum("amount"))["v"]
#                     or D("0")
#                 ) if inv.scheme.category == inv.scheme.Categories.PLATINUM else (inv.amount or D("0"))
#                 amount = money(D(amount) + D(principal))
#                 description = (
#                     f"{inv.scheme.get_category_display()} investment matured: "
#                     "principal plus final accrued return recorded as investor "
#                     "soft debit."
#                 )

#             if amount:
#                 exists = LedgerEntry.objects.filter(
#                     investment=inv,
#                     entry_date=current_date,
#                     entry_type=LedgerEntry.EntryTypes.SOFT_DEBIT,
#                 ).exists()

#                 if not exists:
#                     e = LedgerEntry.objects.create(
#                         business=inv.business,
#                         investment=inv,
#                         investor=inv.investor,
#                         entry_date=current_date,
#                         entry_type=LedgerEntry.EntryTypes.SOFT_DEBIT,
#                         amount=amount,
#                         annual_rate=inv.investor_annual_rate,
#                         description=description,
#                         reference=f"INV-{inv.pk}-{current_date}",
#                     )
#                     created_ids.append(e.pk)

#             current_date += timedelta(days=1)

#         if inv.maturity_date and entry_date >= inv.maturity_date and inv.status == inv.Status.ACTIVE:
#             inv.status = inv.Status.MATURED
#             inv.save(update_fields=["status", "updated_at"] if hasattr(inv, "updated_at") else ["status"])

#     return {
#         "date": entry_date,
#         "created_entry_ids": created_ids,
#         "created_count": len(created_ids),
#     }


# def daily_ledger_job():
#     return generate_daily_ledger(timezone.localdate())


from calendar import monthrange
from datetime import date, timedelta
from decimal import Decimal, ROUND_HALF_UP
from django.db import transaction
from django.db.models import Sum
from django.utils import timezone

D = Decimal
CENT = D("0.01")


def money(value):
    return D(value).quantize(CENT, rounding=ROUND_HALF_UP)


def months_after(start_date, months):
    y = start_date.year + (start_date.month - 1 + months) // 12
    m = (start_date.month - 1 + months) % 12 + 1
    day = min(start_date.day, monthrange(y, m)[1])
    return date(y, m, day)


def simple_interest(principal, annual_rate, months):
    return D(principal) * D(annual_rate) / D("100") * D(months) / D("12")


def compound_maturity(principal, annual_rate, months, frequency="MONTHLY"):
    freq_map = {"MONTHLY": 12, "QUARTERLY": 4, "HALF_YEARLY": 2, "YEARLY": 1}
    n_per_year = freq_map.get((frequency or "MONTHLY").upper(), 12)
    periods = D(months) / D("12") * D(n_per_year)
    periods_int = int(periods)
    if D(periods_int) != periods:
        raise ValueError("Tenure is not compatible with the selected compounding frequency.")
    return D(principal) * (D("1") + D(annual_rate) / D("100") / D(n_per_year)) ** periods_int


def platinum_rd_maturity(monthly_payment, months, annual_rate):
    p = D(monthly_payment)
    n = D(months)
    r = D(annual_rate)
    principal = p * n
    interest = p * n * (n + D("1")) / (D("2") * D("12")) * r / D("100")
    return money(principal + interest)


def platinum_actual_rd_maturity(investment):
    """
    Uses the actual monthly contribution records.
    Each contribution is treated as one RD installment under the supplied
    standard RD formula. Missing months contribute zero.
    """
    contributions = investment.recurring_contributions.all().order_by("contribution_date")
    total_principal = D("0")
    total_interest = D("0")
    maturity_date = investment.maturity_date or months_after(
        investment.investment_date, investment.scheme.tenure_months
    )
    for c in contributions:
        total_principal += D(c.amount)
        remaining_months = max(
            D("0"),
            D((maturity_date.year - c.contribution_date.year) * 12
              + (maturity_date.month - c.contribution_date.month))
        )
        # Standard monthly RD approximation for the remaining months.
        total_interest += D(c.amount) * remaining_months * (remaining_months + D("1")) / (D("2") * D("12"))             * D(investment.investor_annual_rate) / D("100")
    return money(total_principal + total_interest)


def latest_business_equity(business, as_of=None):
    """Return the latest Balance Sheet total equity for a business.

    The valuation source for Diamond is the Balance Sheet total equity.
    Only statements dated on or before ``as_of`` are considered so a future
    balance sheet can never change a historical investment valuation.
    """
    from .models import BalanceSheet

    qs = BalanceSheet.objects.filter(
        business=business,
        deleted_at__isnull=True,
    )
    if as_of is not None:
        qs = qs.filter(statement_date__lte=as_of)

    sheet = qs.order_by("-statement_date", "-id").first()
    if not sheet:
        raise ValueError("Diamond investment requires a latest Balance Sheet for the business.")

    return money(sheet.total_equity), sheet


def diamond_maturity_valuation(investment, as_of=None):
    """Return the latest business equity used as Diamond maturity valuation."""
    valuation, _ = latest_business_equity(investment.business, as_of=as_of)
    return valuation


def diamond_ownership_percentage(investment):
    """Return the ownership percentage locked at Diamond investment creation."""
    snapshot = investment.terms_snapshot or {}
    value = snapshot.get("diamond_ownership_percentage")
    if value is None:
        raise ValueError("Diamond ownership percentage is missing from the investment snapshot.")
    return D(value)


def kuber_equity_share_percentage(investment):
    """Return the ownership percentage locked for a Kuber Equity Share investment."""
    value = investment.equity_share_percentage
    if value is None:
        raise ValueError("Kuber Equity Share percentage is missing from the investment.")
    return D(value)


def kuber_equity_current_principal(investment, as_of=None):
    """Return the displayed principal for a Kuber investment.

    Equity Share percentage is display-only and is deliberately excluded from
    all financial calculations. Therefore the displayed principal remains the
    recorded investment amount, exactly like an Ordinary investment.
    """
    return money(investment.amount)


def record_kuber_equity_investment_on_balance_sheet(*, business, investment_date, amount, entered_by):
    """Record Kuber Equity Share funding in the business Balance Sheet."""
    from .models import BalanceSheet, DailyFinancialStatement

    amount = money(amount)
    previous = BalanceSheet.objects.filter(
        business=business,
        statement_date__lte=investment_date,
        deleted_at__isnull=True,
    ).order_by("-statement_date", "-id").first()
    if previous is None:
        raise ValueError("Kuber Equity Share investment requires a Balance Sheet on or before the investment date.")

    if previous.statement_date == investment_date:
        sheet = previous
    else:
        copy_fields = [
            "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",
        ]
        data = {field: getattr(previous, field) for field in copy_fields}
        sheet = BalanceSheet(
            business=business,
            statement_date=investment_date,
            entered_by=entered_by,
            **data,
        )

    sheet.bank_balance = D(sheet.bank_balance) + amount
    sheet.owner_capital = D(sheet.owner_capital) + amount
    sheet.remarks = (
        f"{sheet.remarks} | Kuber Equity Share funding +{amount} "
        f"recorded on {investment_date}."
    ).strip(" |")
    sheet.entered_by = entered_by
    sheet.save()

    financial = DailyFinancialStatement.objects.filter(
        business=business,
        statement_date=investment_date,
        deleted_at__isnull=True,
    ).first()
    if financial:
        financial.financing_cash_inflow = D(financial.financing_cash_inflow) + amount
        financial.save(update_fields=["financing_cash_inflow", "updated_at"])

    return sheet


@transaction.atomic
def create_kuber_business_investment(*, business, investment_type, amount, annual_rate,
                                       equity_share_percentage=None, investment_date=None, maturity_date=None,
                                       notes="", created_by=None):
    """Create an Ordinary or Equity Share Kuber -> Business investment."""
    from .models import KuberBusinessInvestment

    investment_date = investment_date or timezone.localdate()
    amount = money(amount)
    investment_type = investment_type or KuberBusinessInvestment.InvestmentTypes.ORDINARY

    if maturity_date is None:
        raise ValueError("Maturity date is required for Kuber Business investment.")
    if maturity_date < investment_date:
        raise ValueError("Maturity date cannot be before the investment date.")

    if amount <= 0:
        raise ValueError("Investment amount must be greater than zero.")

    if investment_type == KuberBusinessInvestment.InvestmentTypes.EQUITY_SHARE:
        # Share percentage is entered manually and is display-only. It is NOT
        # used to calculate principal, valuation, interest, maturity, or any
        # other financial value.
        if equity_share_percentage is None:
            raise ValueError("Equity Share investment requires a share percentage.")
        share = D(equity_share_percentage)
        if share <= 0 or share > 100:
            raise ValueError("Equity Share percentage must be greater than 0 and cannot exceed 100.")
        annual_rate = D(annual_rate or 0)
    else:
        share = None
        annual_rate = D(annual_rate or 0)

    obj = KuberBusinessInvestment.objects.create(
        business=business,
        investment_type=investment_type,
        amount=amount,
        annual_rate=annual_rate,
        equity_share_percentage=share,
        investment_date=investment_date,
        maturity_date=maturity_date,
        notes=notes or "",
        created_by=created_by,
    )

    if investment_type == KuberBusinessInvestment.InvestmentTypes.EQUITY_SHARE:
        record_kuber_equity_investment_on_balance_sheet(
            business=business,
            investment_date=investment_date,
            amount=amount,
            entered_by=created_by,
        )

    return obj


def investment_current_principal(investment, as_of=None):
    """Return the current principal/value shown to Admin/Accountant.

    Diamond is equity-linked and therefore uses current Balance Sheet Total
    Equity multiplied by the locked ownership percentage. Other schemes keep
    their existing principal/contributed-capital behaviour.
    """
    if investment.scheme.category == investment.scheme.Categories.DIAMOND:
        equity = diamond_maturity_valuation(investment, as_of=as_of or timezone.localdate())
        ownership = diamond_ownership_percentage(investment) / D("100")
        return money(equity * ownership)

    if investment.scheme.category == investment.scheme.Categories.PLATINUM:
        principal = investment.recurring_contributions.filter(
            contribution_date__lte=as_of or timezone.localdate(),
            deleted_at__isnull=True,
        ).aggregate(v=Sum("amount"))["v"] or D("0")
        return money(principal)

    return money(investment.amount or D("0"))


def maturity_value(investment):
    scheme = investment.scheme
    if scheme.category == scheme.Categories.SILVER:
        if investment.amount is None:
            raise ValueError("Silver investment requires a one-time amount.")
        return money(D(investment.amount) + simple_interest(
            investment.amount, investment.investor_annual_rate, scheme.tenure_months
        ))

    if scheme.category == scheme.Categories.GOLD:
        if investment.amount is None:
            raise ValueError("Gold investment requires a one-time amount.")
        return money(compound_maturity(
            investment.amount, investment.investor_annual_rate,
            scheme.tenure_months, scheme.compounding_frequency or "MONTHLY"
        ))

    if scheme.category == scheme.Categories.PLATINUM:
        return platinum_actual_rd_maturity(investment)

    if scheme.category == scheme.Categories.DIAMOND:
        if investment.amount is None:
            raise ValueError("Diamond investment requires a one-time amount.")

        # Diamond does not have a fixed principal at maturity. The investor's
        # ownership percentage is locked at investment time and the principal
        # component at maturity is the latest business equity multiplied by
        # that percentage. Interest remains a normal daily ledger accrual and
        # is added separately to the valuation-linked maturity value.
        maturity_date = investment.maturity_date or months_after(
            investment.investment_date, scheme.tenure_months
        )
        final_equity = diamond_maturity_valuation(
            investment, as_of=maturity_date
        )
        ownership = diamond_ownership_percentage(investment) / D("100")
        valuation_value = final_equity * ownership
        interest = interest_till_date(investment, as_of=maturity_date)
        return money(valuation_value + interest)

    raise ValueError("Unsupported scheme category.")


def interest_till_date(investment, as_of=None):
    """Return accumulated investor interest payable for this investment.

    Interest is the sum of the investment's SOFT_DEBIT ledger entries up to
    the requested date.  This keeps the displayed interest tied to the ledger.
    """
    from .models import LedgerEntry
    as_of = as_of or timezone.localdate()
    total = LedgerEntry.objects.filter(
        investment=investment,
        entry_type=LedgerEntry.EntryTypes.SOFT_DEBIT,
        entry_date__lte=as_of,
    ).aggregate(v=Sum("amount"))["v"] or D("0")
    return money(total)

def diamond_interest_till_date(investment, as_of=None):
    """Return Diamond interest accrued through ``as_of``.

    Diamond interest is calculated directly from the investment amount and
    the locked annual scheme rate. It is intentionally separate from the
    Business Ledger because Diamond is a direct company <-> investor
    investment. Accrual stops at the investment maturity date.
    """
    if investment.scheme.category != investment.scheme.Categories.DIAMOND:
        return interest_till_date(investment, as_of=as_of)

    as_of = as_of or timezone.localdate()
    start = investment.investment_date
    if not start:
        return D("0.00")

    maturity = investment.maturity_date
    end = min(as_of, maturity) if maturity else as_of

    if end < start:
        return D("0.00")

    days = D((end - start).days + 1)
    principal = D(investment.amount or D("0"))
    annual_rate = D(
        investment.investor_annual_rate
        or investment.scheme.annual_interest_rate
        or D("0")
    )

    if principal <= 0 or annual_rate <= 0:
        return D("0.00")

    return money(
        principal * annual_rate * days / D("100") / D("365")
    )

def premature_value(investment, as_of=None):
    """Return the premature value for schemes that support premature exit."""
    as_of = as_of or timezone.localdate()

    if investment.scheme.category == investment.scheme.Categories.DIAMOND:
        raise ValueError("Diamond investment does not support premature withdrawal.")

    principal = D("0")
    if investment.scheme.category == investment.scheme.Categories.PLATINUM:
        principal = investment.recurring_contributions.filter(
            contribution_date__lte=as_of, deleted_at__isnull=True
        ).aggregate(v=Sum("amount"))["v"] or D("0")
    else:
        principal = investment.amount or D("0")

    interest = interest_till_date(investment, as_of)
    gross = D(principal) + D(interest)
    penalty = gross * D(investment.scheme.penalty_rate) / D("100")
    return money(max(D("0"), gross - penalty))


def daily_business_credit(amount, annual_rate):
    return money(D(amount) * D(annual_rate) / D("100") / D("365"))


def daily_investor_soft_debit(investment, entry_date):
    """
    Daily payable accrual based on the investment principal/contributions.
    The scheme's annual rate is copied onto the investment at creation.
    """
    if investment.scheme.category == investment.scheme.Categories.PLATINUM:
        principal = investment.recurring_contributions.filter(
            contribution_date__lte=entry_date, deleted_at__isnull=True
        ).aggregate(v=Sum("amount"))["v"] or D("0")
    else:
        principal = investment.amount or D("0")
    return money(D(principal) * D(investment.investor_annual_rate) / D("100") / D("365"))



def calculate_daily_financials(statement):
    """Return the canonical P&L and cash-flow calculations for a daily statement."""
    return {
        "gross_profit": money(statement.gross_profit),
        "operating_profit": money(statement.operating_profit),
        "profit_before_tax": money(statement.profit_before_tax),
        "net_profit": money(statement.net_profit),
        "profit_margin": money(statement.profit_margin),
        "financial_status": statement.financial_status,
        "operating_cash_flow": money(statement.operating_cash_flow),
        "investing_cash_flow": money(statement.investing_cash_flow),
        "financing_cash_flow": money(statement.financing_cash_flow),
        "net_cash_flow": money(statement.net_cash_flow),
    }


def build_default_certificate_html(investment, attachments=(), request=None):
    """Build the standard multi-page certificate template used for every certificate."""
    from html import escape

    def file_url(att):
        try:
            url = att.file.url
            return request.build_absolute_uri(url) if request else url
        except Exception:
            return ""

    rules = investment.scheme.rules or []
    rules_html = "".join(f"<li>{escape(str(r))}</li>" for r in rules) or "<li>No additional rules specified.</li>"
    if investment.scheme.category == investment.scheme.Categories.DIAMOND:
        ownership = (investment.terms_snapshot or {}).get("diamond_ownership_percentage")
        valuation_before = (investment.terms_snapshot or {}).get("diamond_valuation_before_investment")
        valuation_after = (investment.terms_snapshot or {}).get("diamond_valuation_after_investment")
        amount = (
            f"Investment Amount: ₹{investment.amount} · "
            f"Initial Business Equity: ₹{valuation_before or '—'} · "
            f"Post-Investment Valuation: ₹{valuation_after or '—'} · "
            f"Locked Ownership: {ownership or '—'}%"
        )
    else:
        amount = (
            f"Monthly Installment: ₹{investment.monthly_installment}"
            if investment.scheme.category == investment.scheme.Categories.PLATINUM
            else f"Investment Amount: ₹{investment.amount}"
        )
    pages = [f"""
    <section class="certificate-page cover-page">
      <div class="certificate-inner">
        <div class="brand-mark">KUBER TROBE</div>
        <div class="certificate-kicker">OFFICIAL DOCUMENT</div>
        <h1>Investment Certificate</h1>
        <div class="certificate-line"></div>
        <p class="lead">This certificate confirms the investment recorded with Kuber Trobe.</p>
        <div class="certificate-number">Certificate ID: {investment.pk}</div>
      </div>
    </section>
    <section class="certificate-page">
      <div class="certificate-inner">
        <div class="page-header"><b>KUBER TROBE</b><span>Investment Certificate</span></div>
        <h2>Investment Details</h2>
        <div class="info-grid">
          <div><label>Investor</label><strong>{escape(investment.investor.name)}</strong></div>
          <div><label>Business</label><strong>{escape(investment.business.name)}</strong></div>
          <div><label>Scheme Category</label><strong>{escape(investment.scheme.get_category_display())}</strong></div>
          <div><label>Scheme</label><strong>{escape(investment.scheme.name)}</strong></div>
          <div><label>Amount</label><strong>{escape(amount)}</strong></div>
          <div><label>Interest Rate</label><strong>{investment.investor_annual_rate}% p.a.</strong></div>
          <div><label>Tenure</label><strong>{investment.scheme.tenure_months} months</strong></div>
          <div><label>Investment Date</label><strong>{investment.investment_date}</strong></div>
          <div><label>Maturity Date</label><strong>{investment.maturity_date or "—"}</strong></div>
          <div><label>Withdrawal Type</label><strong>{escape("No Premature Withdrawal" if investment.scheme.category == investment.scheme.Categories.DIAMOND else (investment.scheme.withdrawal_type or "—"))}</strong></div>
        </div>
        <h2>Terms &amp; Rules</h2>
        <ul class="rules">{rules_html}</ul>
        <div class="signature-row"><span>Authorised Signatory</span><span>Kuber Trobe</span></div>
      </div>
    </section>
    """]

    for att in attachments:
        url = file_url(att)
        lower = (att.file.name or "").lower()
        if lower.endswith((".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg")):
            content = f'<img class="attachment-image" src="{escape(url)}" alt="Certificate attachment">'
        elif lower.endswith(".pdf"):
            content = f'<iframe class="attachment-frame" src="{escape(url)}"></iframe>'
        else:
            content = f'<iframe class="attachment-frame" src="{escape(url)}"></iframe>'
        pages.append(f"""
        <section class="certificate-page attachment-page">
          <div class="certificate-inner">
            <div class="page-header"><b>KUBER TROBE</b><span>Certificate Attachment</span></div>
            <div class="attachment-content">{content}</div>
          </div>
        </section>
        """)

    return f"""<!doctype html><html><head><meta charset="utf-8"><title>Kuber Trobe Investment Certificate</title>
    <style>
    *{{box-sizing:border-box}} body{{margin:0;background:#eef1f5;color:#172033;font-family:Arial,Helvetica,sans-serif}}
    .certificate-page{{width:210mm;min-height:297mm;margin:0 auto 10px;background:#fff;page-break-after:always;position:relative}}
    .certificate-page:last-child{{page-break-after:auto}} .certificate-inner{{min-height:297mm;padding:22mm 20mm;position:relative;border:12px solid #f4f5f7}}
    .cover-page .certificate-inner{{display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center}}
    .brand-mark{{font-size:26px;font-weight:800;letter-spacing:3px}} .certificate-kicker{{margin-top:45mm;font-size:12px;letter-spacing:4px;color:#667085}}
    h1{{font-size:38px;margin:14px 0}} h2{{font-size:20px;margin:24px 0 12px}} .certificate-line{{width:90px;border-top:3px solid #172033}}
    .lead{{color:#667085;font-size:15px;max-width:500px;line-height:1.7}} .certificate-number{{margin-top:25mm;font-size:12px;color:#667085}}
    .page-header{{display:flex;justify-content:space-between;border-bottom:1px solid #ddd;padding-bottom:10px;font-size:12px;color:#667085}}
    .info-grid{{display:grid;grid-template-columns:1fr 1fr;gap:12px}} .info-grid div{{border:1px solid #e4e7ec;border-radius:6px;padding:11px}}
    label{{display:block;color:#667085;font-size:10px;text-transform:uppercase;margin-bottom:5px}} strong{{font-size:13px}} .rules{{line-height:1.7;padding-left:20px}}
    .signature-row{{display:flex;justify-content:space-between;border-top:1px solid #ddd;margin-top:35mm;padding-top:10px;font-size:11px;color:#667085}}
    .attachment-content{{height:250mm;margin-top:10mm;display:flex;justify-content:center;align-items:center;overflow:hidden}}
    .attachment-image{{max-width:100%;max-height:245mm;object-fit:contain}} .attachment-frame{{width:100%;height:245mm;border:1px solid #ddd}}
    @media print{{body{{background:#fff}} .certificate-page{{margin:0}}}}
    </style></head><body>{"".join(pages)}</body></html>"""

def record_diamond_investment_on_balance_sheet(*, business, investment_date, amount, entered_by):
    """Record Diamond funding in the business Balance Sheet.

    The investment is treated as new business funding: the received amount
    increases bank balance (asset) and owner capital (equity) by the same
    amount, so the Balance Sheet remains balanced. If there is no statement
    on the investment date, a new point-in-time statement is copied from the
    latest statement on/before that date.
    """
    from .models import BalanceSheet, DailyFinancialStatement

    amount = money(amount)
    previous = BalanceSheet.objects.filter(
        business=business,
        statement_date__lte=investment_date,
        deleted_at__isnull=True,
    ).order_by("-statement_date", "-id").first()
    if previous is None:
        raise ValueError("Diamond investment requires a Balance Sheet on or before the investment date.")

    # Update the most recent Balance Sheet for this business. Diamond funding
    # increases the business equity by the investment amount, so the latest
    # statement's calculated Total Equity increases automatically through
    # owner_capital. Do not create a separate historical statement merely
    # because the investment date differs from the statement date.
    sheet = previous

    sheet.bank_balance = D(sheet.bank_balance) + amount
    sheet.owner_capital = D(sheet.owner_capital) + amount
    if not sheet.remarks:
        sheet.remarks = ""
    sheet.remarks = (
        f"{sheet.remarks} | Diamond investment funding +{amount} "
        f"recorded on {investment_date}."
    ).strip(" |")
    sheet.entered_by = entered_by
    sheet.save()

    # The same cash receipt is also reflected in the day's financing cash flow.
    financial = DailyFinancialStatement.objects.filter(
        business=business,
        statement_date=investment_date,
        deleted_at__isnull=True,
    ).first()
    if financial:
        financial.financing_cash_inflow = D(financial.financing_cash_inflow) + amount
        financial.save(update_fields=["financing_cash_inflow", "updated_at"])

    return sheet


def build_terms_snapshot(scheme, investment, diamond_valuation_before=None, diamond_sheet=None):
    snapshot = {
        "category": scheme.category,
        "scheme_name": scheme.name,
        "tenure_months": scheme.tenure_months,
        "annual_interest_rate": str(scheme.annual_interest_rate),
        "penalty_rate": str(scheme.penalty_rate),
        "withdrawal_type": scheme.withdrawal_type,
        "compounding_frequency": scheme.compounding_frequency,
        "rules": scheme.rules,
    }

    if scheme.category == scheme.Categories.PLATINUM:
        snapshot["monthly_installment"] = str(investment.monthly_installment)
    elif scheme.category == scheme.Categories.DIAMOND:
        if diamond_valuation_before is None:
            valuation, sheet = latest_business_equity(
                investment.business, as_of=investment.investment_date
            )
        else:
            valuation = money(diamond_valuation_before)
            sheet = diamond_sheet
        amount = D(investment.amount)
        post_investment_valuation = valuation + amount
        if post_investment_valuation <= 0:
            raise ValueError("Diamond post-investment valuation must be greater than zero.")

        ownership = (amount / post_investment_valuation) * D("100")
        snapshot.update({
            "investment_amount_type": "ONE_TIME",
            "diamond_valuation_before_investment": str(money(valuation)),
            "diamond_valuation_after_investment": str(money(post_investment_valuation)),
            "diamond_ownership_percentage": str(ownership),
            "diamond_valuation_statement_date": str(sheet.statement_date),
            "diamond_valuation_source": "BALANCE_SHEET_TOTAL_EQUITY",
            "diamond_maturity_rule": "LATEST_BUSINESS_EQUITY_X_LOCKED_OWNERSHIP_PERCENTAGE_PLUS_LEDGER_INTEREST",
            "diamond_premature_withdrawal": False,
        })
    else:
        snapshot["investment_amount_type"] = "ONE_TIME"

    return snapshot


def audit(actor, operation, module, obj=None, description="", metadata=None):
    from .models import AuditLog
    return AuditLog.objects.create(
        actor=actor,
        operation=operation,
        module=module,
        object_type=obj.__class__.__name__ if obj else "",
        object_id=str(obj.pk) if obj and getattr(obj, "pk", None) else "",
        description=description,
        metadata=metadata or {},
    )


@transaction.atomic
def create_investment_with_snapshot(
    *, investor, business, scheme, amount=None, monthly_installment=None,
    created_by, investment_date=None
):
    from .models import Investment
    investment_date = investment_date or timezone.localdate()

    if scheme.category == scheme.Categories.PLATINUM:
        if monthly_installment is None or D(monthly_installment) <= 0:
            raise ValueError("Platinum investment requires a positive monthly_installment.")
        amount = None
    else:
        if amount is None or D(amount) <= 0:
            raise ValueError("Silver, Gold and Diamond investments require a positive one-time amount.")
        monthly_installment = None

    maturity_date = months_after(investment_date, scheme.tenure_months)
    # The scheme percentage is authoritative; admin does not type a separate rate.
    investor_rate = scheme.annual_interest_rate

    diamond_pre_valuation = None
    diamond_pre_sheet = None
    if scheme.category == scheme.Categories.DIAMOND:
        diamond_pre_valuation, diamond_pre_sheet = latest_business_equity(
            business, as_of=investment_date
        )

    obj = Investment.objects.create(
        investor=investor,
        business=business,
        scheme=scheme,
        amount=amount,
        monthly_installment=monthly_installment,
        investor_annual_rate=investor_rate,
        investment_date=investment_date,
        maturity_date=maturity_date,
        created_by=created_by,
    )
    if scheme.category == scheme.Categories.DIAMOND:
        # Diamond is a direct company <-> investor Investment record. It does
        # not create KuberBusinessInvestment, but the received funding is
        # added to the business's most recent Balance Sheet so Total Equity
        # reflects the new Diamond investment.
        record_diamond_investment_on_balance_sheet(
            business=business,
            investment_date=investment_date,
            amount=amount,
            entered_by=created_by,
        )

    obj.terms_snapshot = build_terms_snapshot(
        scheme,
        obj,
        diamond_valuation_before=diamond_pre_valuation,
        diamond_sheet=diamond_pre_sheet,
    )
    obj.save(update_fields=["terms_snapshot"])
    return obj


@transaction.atomic
def generate_daily_ledger(entry_date=None):
    """
    Generate every missing daily ledger entry up to entry_date.

    The function is idempotent. If the server was offline for previous
    days, running Generate Today backfills those missing dates without
    duplicating entries that already exist.

    Platinum starts accruing only from the first actual recurring
    contribution date, and the daily amount uses all actual contributions
    received up to each ledger date.
    """
    from .models import KuberBusinessInvestment, Investment, LedgerEntry

    entry_date = entry_date or timezone.localdate()
    created_ids = []

    # ------------------------------------------------------------
    # KUBER -> BUSINESS CREDIT PROFIT
    # ------------------------------------------------------------
    kuber_qs = (
        KuberBusinessInvestment.objects
        .filter(
            deleted_at__isnull=True,
            investment_date__lte=entry_date,
        )
        .select_related("business")
    )

    for ki in kuber_qs:
        current_date = ki.investment_date
        end_date = (
            min(entry_date, ki.maturity_date)
            if ki.maturity_date
            else entry_date
        )

        while current_date <= end_date:
            exists = LedgerEntry.objects.filter(
                kuber_investment=ki,
                entry_date=current_date,
                entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT,
            ).exists()

            if not exists:
                amount = daily_business_credit(ki.amount, ki.annual_rate)
                description = "Automatic daily Kuber-to-business credit profit."

                # The existing ledger constraint allows one CREDIT_PROFIT entry
                # per Kuber investment per day. On the maturity date, the daily
                # profit and the principal are therefore recorded together in
                # the same Credit Profit entry.
                if ki.maturity_date and current_date == ki.maturity_date:
                    amount = money(D(amount) + D(ki.amount))
                    description = (
                        "Kuber investment matured: principal plus daily profit "
                        "credited to business ledger."
                    )

                if amount:
                    e = LedgerEntry.objects.create(
                        business=ki.business,
                        kuber_investment=ki,
                        entry_date=current_date,
                        entry_type=LedgerEntry.EntryTypes.CREDIT_PROFIT,
                        amount=amount,
                        annual_rate=ki.annual_rate,
                        description=description,
                        reference=f"KBI-{ki.pk}-{current_date}",
                    )
                    created_ids.append(e.pk)

            current_date += timedelta(days=1)

    # ------------------------------------------------------------
    # INVESTOR SOFT DEBIT
    # ------------------------------------------------------------
    investments = (
        Investment.objects
        .filter(
            deleted_at__isnull=True,
            investment_date__lte=entry_date,
            status=Investment.Status.ACTIVE,
        )
        .select_related("business", "investor", "scheme")
    )

    for inv in investments:
        if inv.scheme.category == inv.scheme.Categories.PLATINUM:
            first_contribution = (
                inv.recurring_contributions
                .filter(
                    deleted_at__isnull=True,
                    contribution_date__lte=entry_date,
                )
                .order_by("contribution_date")
                .first()
            )

            if not first_contribution:
                # No actual Platinum payment means no investor interest yet.
                continue

            start_date = first_contribution.contribution_date
        else:
            start_date = inv.investment_date

        current_date = start_date
        end_date = (
            min(entry_date, inv.maturity_date)
            if inv.maturity_date
            else entry_date
        )

        while current_date <= end_date:
            amount = daily_investor_soft_debit(inv, current_date)
            description = (
                f"Automatic daily {inv.scheme.get_category_display()} "
                "investor soft debit."
            )

            # On maturity, add the original principal to the same Soft Debit
            # ledger entry as the final day's accrued return. This preserves
            # the existing one-entry-per-day constraint.
            if (
                inv.maturity_date
                and current_date == inv.maturity_date
                and inv.scheme.category != inv.scheme.Categories.DIAMOND
            ):
                principal = (
                    inv.recurring_contributions
                    .filter(
                        contribution_date__lte=current_date,
                        deleted_at__isnull=True,
                    )
                    .aggregate(v=Sum("amount"))["v"]
                    or D("0")
                ) if inv.scheme.category == inv.scheme.Categories.PLATINUM else (inv.amount or D("0"))
                amount = money(D(amount) + D(principal))
                description = (
                    f"{inv.scheme.get_category_display()} investment matured: "
                    "principal plus final accrued return recorded as investor "
                    "soft debit."
                )

            if amount:
                exists = LedgerEntry.objects.filter(
                    investment=inv,
                    entry_date=current_date,
                    entry_type=LedgerEntry.EntryTypes.SOFT_DEBIT,
                ).exists()

                if not exists:
                    e = LedgerEntry.objects.create(
                        business=inv.business,
                        investment=inv,
                        investor=inv.investor,
                        entry_date=current_date,
                        entry_type=LedgerEntry.EntryTypes.SOFT_DEBIT,
                        amount=amount,
                        annual_rate=inv.investor_annual_rate,
                        description=description,
                        reference=f"INV-{inv.pk}-{current_date}",
                    )
                    created_ids.append(e.pk)

            current_date += timedelta(days=1)

        if inv.maturity_date and entry_date >= inv.maturity_date and inv.status == inv.Status.ACTIVE:
            inv.status = inv.Status.MATURED
            inv.save(update_fields=["status", "updated_at"] if hasattr(inv, "updated_at") else ["status"])

    # Agent profit-share ledger is additive to the existing investor/business ledger.
    # Import lazily to avoid creating an app import cycle.
    try:
        from agent_panel.services import generate_daily_agent_ledger
        agent_created_ids = generate_daily_agent_ledger(entry_date)
    except Exception:
        # Never allow the new Agent feature to break the existing ledger job.
        agent_created_ids = []

    return {
        "date": entry_date,
        "created_entry_ids": created_ids,
        "agent_created_entry_ids": agent_created_ids,
        "created_count": len(created_ids),
    }


def daily_ledger_job():
    return generate_daily_ledger(timezone.localdate())
