"""
Payments Hub — centralised AP/AR collection and payment recording across all modules.

Endpoints:
  GET  /outstanding-ar        — all unpaid/partial AR items (invoices, job cards) across branches
  GET  /outstanding-ap        — all unpaid/partial AP items (purchases) across branches
  POST /record-receipt        — receive money from a debtor (invoice payment), posts journal + audit
  POST /record-ap-payment     — pay a creditor (purchase payment), posts journal + audit
  GET  /audit-log             — unified payment audit log (AR receipts + AP payments)
"""

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session, joinedload
from sqlalchemy import or_, and_
from typing import Optional, List
from decimal import Decimal
from datetime import date, datetime
import uuid

from app.core.database import get_db
from app.core.dependencies import get_current_company_id
from app.models.sales import Invoice, Payment, Customer
from app.models.purchases import Purchase, Supplier
from app.models.job_card import JobCard
from app.models.branch import Branch
from app.models.accounting import AccountingCode, AccountingEntry, JournalEntry
from app.models.purchase_payments import PurchasePayment

router = APIRouter()


# ─────────────────────────────────────────────────────────────────────────────
# Helpers
# ─────────────────────────────────────────────────────────────────────────────

def _fmt_dec(v) -> float:
    try:
        return float(v or 0)
    except Exception:
        return 0.0


def _outstanding(total, paid) -> float:
    return max(0.0, _fmt_dec(total) - _fmt_dec(paid))


# ─────────────────────────────────────────────────────────────────────────────
# GET /outstanding-ar
# ─────────────────────────────────────────────────────────────────────────────

@router.get("/outstanding-ar")
def get_outstanding_ar(
    branch_id: Optional[str] = Query(None),
    customer_id: Optional[str] = Query(None),
    status: Optional[str] = Query(None, description="pending|partial|overdue|all"),
    from_date: Optional[date] = Query(None),
    to_date: Optional[date] = Query(None),
    search: Optional[str] = Query(None),
    include_modules: Optional[str] = Query(None, description="Comma-separated: invoices,job_cards"),
    skip: int = Query(0, ge=0),
    limit: int = Query(200, ge=1, le=1000),
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Return all AR items with outstanding balance > 0 across all specified branches and modules."""
    modules = set((include_modules or "invoices,job_cards").split(","))
    items = []

    # ── Invoices ─────────────────────────────────────────────────────────────
    if "invoices" in modules:
        q = (
            db.query(Invoice)
            .options(
                joinedload(Invoice.customer),
                joinedload(Invoice.branch),
            )
            .filter(Invoice.status.notin_(["paid", "cancelled", "void"]))
        )
        if company_id and not branch_id:
            _bids = db.query(Branch.id).filter(Branch.company_id == company_id)
            q = q.filter(Invoice.branch_id.in_(_bids))
        if branch_id:
            q = q.filter(Invoice.branch_id == branch_id)
        if customer_id:
            q = q.filter(Invoice.customer_id == customer_id)
        if from_date:
            q = q.filter(Invoice.date >= from_date)
        if to_date:
            q = q.filter(Invoice.date <= to_date)
        if search:
            term = f"%{search}%"
            q = q.filter(
                or_(
                    Invoice.invoice_number.ilike(term),
                    Invoice.notes.ilike(term),
                )
            )

        today = date.today()
        for inv in q.all():
            total = _fmt_dec(inv.total or inv.total_amount)
            paid = _fmt_dec(inv.amount_paid)
            outstanding = _outstanding(total, paid)
            if outstanding <= 0:
                continue

            inv_status = (inv.status or "").lower()
            is_overdue = inv.due_date and inv.due_date < today and inv_status != "paid"

            item_status = "overdue" if is_overdue else ("partial" if paid > 0 else "pending")
            if status and status != "all" and item_status != status:
                continue

            items.append({
                "source_module": "invoices",
                "source_id": inv.id,
                "source_ref": inv.invoice_number,
                "entity_id": inv.customer_id,
                "entity_name": inv.customer.name if inv.customer else "Unknown Customer",
                "branch_id": inv.branch_id,
                "branch_name": inv.branch.name if inv.branch else None,
                "date": inv.date.isoformat() if inv.date else None,
                "due_date": inv.due_date.isoformat() if inv.due_date else None,
                "total_amount": total,
                "amount_paid": paid,
                "outstanding_amount": round(outstanding, 2),
                "status": item_status,
                "original_status": inv.status,
                "currency": "BWP",
                "notes": inv.notes,
                "payment_terms": inv.payment_terms,
            })

    # ── Job Cards ─────────────────────────────────────────────────────────────
    if "job_cards" in modules:
        q = (
            db.query(JobCard)
            .options(
                joinedload(JobCard.customer),
                joinedload(JobCard.branch),
            )
            .filter(JobCard.amount_due > 0)
            .filter(JobCard.status.notin_(["cancelled", "void"]))
        )
        if branch_id:
            q = q.filter(JobCard.branch_id == branch_id)
        if customer_id:
            q = q.filter(JobCard.customer_id == customer_id)
        if from_date:
            q = q.filter(JobCard.start_date >= from_date)
        if to_date:
            q = q.filter(JobCard.start_date <= to_date)
        if search:
            term = f"%{search}%"
            q = q.filter(
                or_(
                    JobCard.job_number.ilike(term),
                    JobCard.description.ilike(term),
                )
            )

        today = date.today()
        for jc in q.all():
            outstanding = _fmt_dec(jc.amount_due)
            if outstanding <= 0:
                continue
            paid = _fmt_dec(jc.amount_paid)
            is_overdue = jc.due_date and jc.due_date < today

            item_status = "overdue" if is_overdue else ("partial" if paid > 0 else "pending")
            if status and status != "all" and item_status != status:
                continue

            items.append({
                "source_module": "job_cards",
                "source_id": jc.id,
                "source_ref": jc.job_number,
                "entity_id": jc.customer_id,
                "entity_name": jc.customer.name if jc.customer else "Unknown Customer",
                "branch_id": jc.branch_id,
                "branch_name": jc.branch.name if jc.branch else None,
                "date": jc.start_date.isoformat() if jc.start_date else None,
                "due_date": jc.due_date.isoformat() if jc.due_date else None,
                "total_amount": _fmt_dec(jc.total_amount),
                "amount_paid": paid,
                "outstanding_amount": round(outstanding, 2),
                "status": item_status,
                "original_status": jc.status,
                "currency": jc.currency or "BWP",
                "notes": jc.notes,
                "payment_terms": None,
            })

    # Sort: overdue first, then by due_date ascending
    items.sort(key=lambda x: (
        0 if x["status"] == "overdue" else (1 if x["status"] == "partial" else 2),
        x["due_date"] or "9999-99-99"
    ))

    return {"success": True, "count": len(items), "items": items[skip: skip + limit]}


# ─────────────────────────────────────────────────────────────────────────────
# GET /outstanding-ap
# ─────────────────────────────────────────────────────────────────────────────

@router.get("/outstanding-ap")
def get_outstanding_ap(
    branch_id: Optional[str] = Query(None),
    supplier_id: Optional[str] = Query(None),
    status: Optional[str] = Query(None, description="pending|partial|overdue|all"),
    from_date: Optional[date] = Query(None),
    to_date: Optional[date] = Query(None),
    search: Optional[str] = Query(None),
    skip: int = Query(0, ge=0),
    limit: int = Query(200, ge=1, le=1000),
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Return all AP items (purchases) with outstanding balance > 0 across all branches."""
    q = (
        db.query(Purchase)
        .options(
            joinedload(Purchase.supplier),
            joinedload(Purchase.branch),
        )
        .filter(Purchase.status.notin_(["paid", "cancelled", "void"]))
    )
    if company_id and not branch_id:
        _bids = db.query(Branch.id).filter(Branch.company_id == company_id)
        q = q.filter(Purchase.branch_id.in_(_bids))
    if branch_id:
        q = q.filter(Purchase.branch_id == branch_id)
    if supplier_id:
        q = q.filter(Purchase.supplier_id == supplier_id)
    if from_date:
        q = q.filter(Purchase.purchase_date >= from_date)
    if to_date:
        q = q.filter(Purchase.purchase_date <= to_date)
    if search:
        term = f"%{search}%"
        q = q.filter(
            or_(
                Purchase.supplier_invoice_number.ilike(term),
                Purchase.reference.ilike(term),
                Purchase.notes.ilike(term),
            )
        )

    today = date.today()
    items = []
    for pur in q.all():
        total = _fmt_dec(pur.total_amount)
        paid = _fmt_dec(pur.amount_paid)
        outstanding = _outstanding(total, paid)
        if outstanding <= 0:
            continue

        is_overdue = pur.due_date and pur.due_date < today
        item_status = "overdue" if is_overdue else ("partial" if paid > 0 else "pending")
        if status and status != "all" and item_status != status:
            continue

        ref = pur.supplier_invoice_number or pur.reference or pur.id[:8]
        items.append({
            "source_module": "purchases",
            "source_id": pur.id,
            "source_ref": ref,
            "entity_id": pur.supplier_id,
            "entity_name": pur.supplier.name if pur.supplier else "Unknown Supplier",
            "branch_id": pur.branch_id,
            "branch_name": pur.branch.name if pur.branch else None,
            "date": pur.purchase_date.isoformat() if pur.purchase_date else None,
            "due_date": pur.due_date.isoformat() if pur.due_date else None,
            "total_amount": total,
            "amount_paid": paid,
            "outstanding_amount": round(outstanding, 2),
            "status": item_status,
            "original_status": pur.status,
            "currency": "BWP",
            "notes": pur.notes,
        })

    items.sort(key=lambda x: (
        0 if x["status"] == "overdue" else (1 if x["status"] == "partial" else 2),
        x["due_date"] or "9999-99-99"
    ))

    return {"success": True, "count": len(items), "items": items[skip: skip + limit]}


# ─────────────────────────────────────────────────────────────────────────────
# POST /record-receipt  (receive from debtor — invoice payment)
# ─────────────────────────────────────────────────────────────────────────────

@router.post("/record-receipt")
def record_receipt(
    body: dict,
    db: Session = Depends(get_db),
):
    """
    Record a customer receipt against one or more invoices / job cards.

    Body fields:
      source_items     : list of {source_module, source_id, amount}  (required)
      payment_date     : ISO date string (required)
      payment_method   : bank_transfer | cash | cheque | mobile_money  (required)
      bank_account_id  : accounting_code_id of the bank/cash account (required)
      ar_account_id    : accounting_code_id of the AR account (required)
      reference        : customer ref / cheque number etc.
      notes            : free text notes
      entity_name      : customer name for narration
      branch_id        : override branch (optional, uses invoice branch if absent)
    """
    try:
        source_items: list = body.get("source_items") or []
        payment_date_str: str = body.get("payment_date") or date.today().isoformat()
        payment_method: str = body.get("payment_method", "bank_transfer")
        bank_account_id: str = body.get("bank_account_id", "")
        ar_account_id: str = body.get("ar_account_id", "")
        reference: str = body.get("reference") or f"RCPT-{payment_date_str.replace('-','')}-{str(uuid.uuid4())[:4].upper()}"
        notes: str = body.get("notes", "")
        entity_name: str = body.get("entity_name", "Customer")

        if not source_items:
            raise HTTPException(status_code=400, detail="At least one source item is required")
        if not bank_account_id or not ar_account_id:
            raise HTTPException(status_code=400, detail="bank_account_id and ar_account_id are required")

        try:
            pay_date = date.fromisoformat(payment_date_str)
        except ValueError:
            raise HTTPException(status_code=400, detail=f"Invalid payment_date: {payment_date_str}")

        # Validate accounts exist
        bank_code = db.query(AccountingCode).filter(AccountingCode.id == bank_account_id).first()
        ar_code = db.query(AccountingCode).filter(AccountingCode.id == ar_account_id).first()
        if not bank_code:
            raise HTTPException(status_code=400, detail="bank_account_id not found")
        if not ar_code:
            raise HTTPException(status_code=400, detail="ar_account_id not found")

        applied_items = []
        total_amount = Decimal("0")
        branch_id = body.get("branch_id")

        for item in source_items:
            src_module = item.get("source_module")
            src_id = item.get("source_id")
            pay_amt = Decimal(str(item.get("amount", 0)))

            if pay_amt <= 0:
                continue

            if src_module == "invoices":
                inv = db.query(Invoice).filter(Invoice.id == src_id).first()
                if not inv:
                    raise HTTPException(status_code=404, detail=f"Invoice {src_id} not found")
                total = _fmt_dec(inv.total or inv.total_amount)
                paid = _fmt_dec(inv.amount_paid)
                outstanding = Decimal(str(_outstanding(total, paid)))
                if pay_amt > outstanding + Decimal("0.01"):
                    raise HTTPException(
                        status_code=400,
                        detail=f"Payment amount {pay_amt} exceeds outstanding {outstanding} on invoice {inv.invoice_number}"
                    )

                # Create Payment record
                payment_record = Payment(
                    invoice_id=inv.id,
                    customer_id=inv.customer_id,
                    amount=pay_amt,
                    payment_date=pay_date,
                    payment_method=payment_method,
                    reference=reference,
                    note=f"{notes} | Source: Invoice {inv.invoice_number}" if notes else f"Invoice {inv.invoice_number}",
                    payment_status="completed",
                    bank_account_id=None,  # accounting code, not bank account model
                )
                db.add(payment_record)

                # Update invoice
                new_paid = Decimal(str(paid)) + pay_amt
                inv.amount_paid = new_paid
                inv.status = "paid" if new_paid >= Decimal(str(total)) - Decimal("0.01") else "partial"
                if inv.status == "paid":
                    inv.paid_at = datetime.utcnow()

                branch_id = branch_id or inv.branch_id
                applied_items.append({
                    "source_module": "invoices",
                    "source_ref": inv.invoice_number,
                    "amount": float(pay_amt),
                    "new_status": inv.status,
                })

            elif src_module == "job_cards":
                jc = db.query(JobCard).filter(JobCard.id == src_id).first()
                if not jc:
                    raise HTTPException(status_code=404, detail=f"Job card {src_id} not found")
                outstanding = Decimal(str(_fmt_dec(jc.amount_due)))
                if pay_amt > outstanding + Decimal("0.01"):
                    raise HTTPException(
                        status_code=400,
                        detail=f"Payment {pay_amt} exceeds outstanding {outstanding} on job card {jc.job_number}"
                    )
                jc.amount_paid = Decimal(str(_fmt_dec(jc.amount_paid))) + pay_amt
                jc.amount_due = max(Decimal("0"), outstanding - pay_amt)
                branch_id = branch_id or jc.branch_id
                applied_items.append({
                    "source_module": "job_cards",
                    "source_ref": jc.job_number,
                    "amount": float(pay_amt),
                    "new_status": "partial" if jc.amount_due > 0 else "billed",
                })
            else:
                raise HTTPException(status_code=400, detail=f"Unsupported source_module: {src_module}")

            total_amount += pay_amt

        if total_amount <= 0:
            raise HTTPException(status_code=400, detail="Total payment amount must be > 0")

        db.flush()

        # Resolve branch
        if not branch_id:
            first_branch = db.query(Branch).first()
            branch_id = first_branch.id if first_branch else None
        if not branch_id:
            raise HTTPException(status_code=400, detail="No branch context available")

        # Build source refs string for narration
        refs_str = ", ".join(i["source_ref"] for i in applied_items)
        narration = f"Receipt from {entity_name} | {refs_str} | {notes}".strip(" |")

        # ── Journal Entry: Dr Bank / Cr AR ────────────────────────────────────
        accounting_entry = AccountingEntry(
            date_prepared=pay_date,
            particulars=narration,
            book="Cash Receipts Book",
            status="posted",
            branch_id=branch_id,
        )
        db.add(accounting_entry)
        db.flush()

        je_dr = JournalEntry(
            accounting_code_id=bank_account_id,
            accounting_entry_id=accounting_entry.id,
            entry_type="debit",
            narration=narration,
            date=pay_date,
            debit_amount=total_amount,
            credit_amount=Decimal("0"),
            description=f"Receipt – {entity_name}",
            reference=reference,
            branch_id=branch_id,
        )
        je_cr = JournalEntry(
            accounting_code_id=ar_account_id,
            accounting_entry_id=accounting_entry.id,
            entry_type="credit",
            narration=narration,
            date=pay_date,
            debit_amount=Decimal("0"),
            credit_amount=total_amount,
            description=f"Receipt – {entity_name}",
            reference=reference,
            branch_id=branch_id,
        )
        db.add(je_dr)
        db.add(je_cr)
        db.commit()

        # Update accounting code balances
        try:
            from app.services.accounting_service import AccountingService
            svc = AccountingService(db)
            svc.update_accounting_code_balance(bank_account_id)
            svc.update_accounting_code_balance(ar_account_id)
        except Exception:
            pass

        return {
            "success": True,
            "message": "Receipt recorded successfully",
            "reference": reference,
            "total_amount": float(total_amount),
            "applied_to": applied_items,
            "journal_entry_id": accounting_entry.id,
            "audit": {
                "recorded_at": datetime.utcnow().isoformat(),
                "payment_method": payment_method,
                "bank_account": f"{bank_code.code} – {bank_code.name}",
                "ar_account": f"{ar_code.code} – {ar_code.name}",
                "narration": narration,
            },
        }

    except HTTPException:
        db.rollback()
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Error recording receipt: {e}")


# ─────────────────────────────────────────────────────────────────────────────
# POST /record-ap-payment  (pay a creditor — purchase payment)
# ─────────────────────────────────────────────────────────────────────────────

@router.post("/record-ap-payment")
def record_ap_payment(
    body: dict,
    db: Session = Depends(get_db),
):
    """
    Record a supplier payment against one or more purchases.

    Body fields:
      source_items     : list of {source_id, amount}  (required; source_module always 'purchases')
      payment_date     : ISO date string (required)
      payment_method   : bank_transfer | cash | cheque | mobile_money
      payment_source_id: accounting_code_id or bank_account id of the paying account (required)
      ap_account_id    : accounting_code_id of the AP account (required)
      reference        : e.g. cheque number
      notes            : free text
      entity_name      : supplier name for narration
      branch_id        : override branch
    """
    try:
        source_items: list = body.get("source_items") or []
        payment_date_str: str = body.get("payment_date") or date.today().isoformat()
        payment_method: str = body.get("payment_method", "bank_transfer")
        payment_source_id: str = body.get("payment_source_id", "")
        ap_account_id: str = body.get("ap_account_id", "")
        reference: str = body.get("reference") or f"PMT-{payment_date_str.replace('-','')}-{str(uuid.uuid4())[:4].upper()}"
        notes: str = body.get("notes", "")
        entity_name: str = body.get("entity_name", "Supplier")

        if not source_items:
            raise HTTPException(status_code=400, detail="At least one source item is required")
        if not payment_source_id or not ap_account_id:
            raise HTTPException(status_code=400, detail="payment_source_id and ap_account_id are required")

        try:
            pay_date = date.fromisoformat(payment_date_str)
        except ValueError:
            raise HTTPException(status_code=400, detail=f"Invalid payment_date: {payment_date_str}")

        bank_code = db.query(AccountingCode).filter(AccountingCode.id == payment_source_id).first()
        ap_code = db.query(AccountingCode).filter(AccountingCode.id == ap_account_id).first()
        if not bank_code:
            raise HTTPException(status_code=400, detail="payment_source_id not found")
        if not ap_code:
            raise HTTPException(status_code=400, detail="ap_account_id not found")

        applied_items = []
        total_amount = Decimal("0")
        branch_id = body.get("branch_id")

        for item in source_items:
            src_id = item.get("source_id")
            pay_amt = Decimal(str(item.get("amount", 0)))
            if pay_amt <= 0:
                continue

            pur = db.query(Purchase).filter(Purchase.id == src_id).first()
            if not pur:
                raise HTTPException(status_code=404, detail=f"Purchase {src_id} not found")

            total = _fmt_dec(pur.total_amount)
            paid = _fmt_dec(pur.amount_paid)
            outstanding = Decimal(str(_outstanding(total, paid)))
            if pay_amt > outstanding + Decimal("0.01"):
                raise HTTPException(
                    status_code=400,
                    detail=f"Payment {pay_amt} exceeds outstanding {outstanding} on purchase {pur.supplier_invoice_number or src_id[:8]}"
                )

            # Create PurchasePayment record
            pp = PurchasePayment(
                purchase_id=pur.id,
                amount=pay_amt,
                payment_date=pay_date,
                payment_method=payment_method,
                reference=reference,
                notes=f"{notes} | Batch payment" if notes else "Batch payment via Payments Hub",
                branch_id=branch_id or pur.branch_id,
            )
            db.add(pp)

            # Update purchase
            new_paid = Decimal(str(paid)) + pay_amt
            pur.amount_paid = new_paid
            if new_paid >= Decimal(str(total)) - Decimal("0.01"):
                pur.status = "paid"
            elif new_paid > 0:
                pur.status = "partially_paid"

            branch_id = branch_id or pur.branch_id
            ref_str = pur.supplier_invoice_number or pur.reference or pur.id[:8]
            applied_items.append({
                "source_module": "purchases",
                "source_ref": ref_str,
                "amount": float(pay_amt),
                "new_status": pur.status,
            })
            total_amount += pay_amt

        if total_amount <= 0:
            raise HTTPException(status_code=400, detail="Total payment amount must be > 0")

        db.flush()

        if not branch_id:
            first_branch = db.query(Branch).first()
            branch_id = first_branch.id if first_branch else None
        if not branch_id:
            raise HTTPException(status_code=400, detail="No branch context available")

        refs_str = ", ".join(i["source_ref"] for i in applied_items)
        narration = f"Payment to {entity_name} | {refs_str} | {notes}".strip(" |")

        # ── Journal Entry: Dr AP / Cr Bank ────────────────────────────────────
        accounting_entry = AccountingEntry(
            date_prepared=pay_date,
            particulars=narration,
            book="Cash Payments Book",
            status="posted",
            branch_id=branch_id,
        )
        db.add(accounting_entry)
        db.flush()

        je_dr = JournalEntry(
            accounting_code_id=ap_account_id,
            accounting_entry_id=accounting_entry.id,
            entry_type="debit",
            narration=narration,
            date=pay_date,
            debit_amount=total_amount,
            credit_amount=Decimal("0"),
            description=f"Payment – {entity_name}",
            reference=reference,
            branch_id=branch_id,
        )
        je_cr = JournalEntry(
            accounting_code_id=payment_source_id,
            accounting_entry_id=accounting_entry.id,
            entry_type="credit",
            narration=narration,
            date=pay_date,
            debit_amount=Decimal("0"),
            credit_amount=total_amount,
            description=f"Payment – {entity_name}",
            reference=reference,
            branch_id=branch_id,
        )
        db.add(je_dr)
        db.add(je_cr)
        db.commit()

        try:
            from app.services.accounting_service import AccountingService
            svc = AccountingService(db)
            svc.update_accounting_code_balance(payment_source_id)
            svc.update_accounting_code_balance(ap_account_id)
        except Exception:
            pass

        return {
            "success": True,
            "message": "Payment recorded successfully",
            "reference": reference,
            "total_amount": float(total_amount),
            "applied_to": applied_items,
            "journal_entry_id": accounting_entry.id,
            "audit": {
                "recorded_at": datetime.utcnow().isoformat(),
                "payment_method": payment_method,
                "payment_account": f"{bank_code.code} – {bank_code.name}",
                "ap_account": f"{ap_code.code} – {ap_code.name}",
                "narration": narration,
            },
        }

    except HTTPException:
        db.rollback()
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Error recording payment: {e}")


# ─────────────────────────────────────────────────────────────────────────────
# GET /audit-log  — unified AR + AP payment log
# ─────────────────────────────────────────────────────────────────────────────

@router.get("/audit-log")
def get_payment_audit_log(
    source_module: Optional[str] = Query(None, description="invoices | purchases | job_cards"),
    branch_id: Optional[str] = Query(None),
    entity_id: Optional[str] = Query(None),
    from_date: Optional[date] = Query(None),
    to_date: Optional[date] = Query(None),
    reference: Optional[str] = Query(None),
    limit: int = Query(200, ge=1, le=1000),
    skip: int = Query(0, ge=0),
    db: Session = Depends(get_db),
    company_id: Optional[str] = Depends(get_current_company_id),
):
    """Unified payment audit log combining AR receipts (Payment table) and AP payments (PurchasePayment table)."""
    records = []

    # ── AR Receipts (Payment model) ───────────────────────────────────────────
    if not source_module or source_module == "invoices" or source_module == "job_cards":
        q = (
            db.query(Payment)
            .options(
                joinedload(Payment.invoice),
                joinedload(Payment.customer),
            )
            .order_by(Payment.payment_date.desc())
        )
        if company_id:
            _bids = db.query(Branch.id).filter(Branch.company_id == company_id)
            _inv_ids = db.query(Invoice.id).filter(Invoice.branch_id.in_(_bids))
            q = q.filter(Payment.invoice_id.in_(_inv_ids))
        if from_date:
            q = q.filter(Payment.payment_date >= from_date)
        if to_date:
            q = q.filter(Payment.payment_date <= to_date)
        if entity_id:
            q = q.filter(Payment.customer_id == entity_id)
        if reference:
            q = q.filter(Payment.reference.ilike(f"%{reference}%"))

        for p in q.all():
            inv = p.invoice
            records.append({
                "type": "receipt",
                "source_module": "invoices",
                "id": p.id,
                "reference": p.reference,
                "date": p.payment_date.isoformat() if p.payment_date else None,
                "entity_name": p.customer.name if p.customer else (inv.customer.name if inv and inv.customer else "Unknown"),
                "entity_id": p.customer_id,
                "source_ref": inv.invoice_number if inv else None,
                "source_id": p.invoice_id,
                "amount": float(p.amount or 0),
                "payment_method": p.payment_method,
                "payment_status": p.payment_status or "completed",
                "note": p.note,
                "branch_id": inv.branch_id if inv else None,
                "branch_name": inv.branch.name if (inv and inv.branch) else None,
                "recorded_at": None,
            })

    # ── AP Payments (PurchasePayment model) ───────────────────────────────────
    if not source_module or source_module == "purchases":
        q = (
            db.query(PurchasePayment)
            .options(
                joinedload(PurchasePayment.purchase).joinedload(Purchase.supplier),
                joinedload(PurchasePayment.branch),
            )
            .order_by(PurchasePayment.payment_date.desc())
        )
        if from_date:
            q = q.filter(PurchasePayment.payment_date >= from_date)
        if to_date:
            q = q.filter(PurchasePayment.payment_date <= to_date)
        if branch_id:
            q = q.filter(PurchasePayment.branch_id == branch_id)
        if reference:
            q = q.filter(PurchasePayment.reference.ilike(f"%{reference}%"))

        for pp in q.all():
            pur = pp.purchase
            supplier = pur.supplier if pur else None
            if entity_id and (not supplier or supplier.id != entity_id):
                continue
            records.append({
                "type": "payment",
                "source_module": "purchases",
                "id": pp.id,
                "reference": pp.reference,
                "date": pp.payment_date.isoformat() if pp.payment_date else None,
                "entity_name": supplier.name if supplier else "Unknown Supplier",
                "entity_id": supplier.id if supplier else None,
                "source_ref": pur.supplier_invoice_number or pur.reference if pur else None,
                "source_id": pp.purchase_id,
                "amount": float(pp.amount or 0),
                "payment_method": pp.payment_method,
                "payment_status": "completed",
                "note": pp.notes,
                "branch_id": pp.branch_id,
                "branch_name": pp.branch.name if pp.branch else None,
                "recorded_at": pp.recorded_at.isoformat() if pp.recorded_at else None,
            })

    # Sort by date descending
    records.sort(key=lambda x: x["date"] or "0000-00-00", reverse=True)

    return {"success": True, "count": len(records), "records": records[skip: skip + limit]}
