"""Post procurement lifecycle events to the general ledger.

Called from apps.procurements.workflow — kept here (not in procurements)
because chart-of-accounts lookups and journal/line creation are this app's
concern.
"""
from rest_framework.exceptions import ValidationError

from .models import Account, JournalEntry, JournalLine

CASH_ACCOUNT_CODE = "1000"
ACCOUNTS_PAYABLE_CODE = "2000"
ENCUMBRANCE_CODE = "1900"
RESERVE_FOR_ENCUMBRANCE_CODE = "2900"


def _account(organization, code):
    try:
        return Account.objects.get(organization=organization, code=code)
    except Account.DoesNotExist:
        raise ValidationError(f"Chart of accounts is missing account {code}; run seed_chart_of_accounts.")


def post_encumbrance(*, organization, journal_no, date, amount, budget_item):
    """PO issued / contract signed: memo entry, excluded from real GL totals
    by account type (Encumbrance), not by status."""
    journal = JournalEntry.objects.create(
        organization=organization, journal_no=journal_no, date=date, reference=journal_no,
        description=f"Encumbrance — {journal_no}", status="Posted", budget_item=budget_item,
    )
    JournalLine.objects.create(journal=journal, account=_account(organization, ENCUMBRANCE_CODE), description="Encumbrance", debit_idr=amount, credit_idr=0)
    JournalLine.objects.create(journal=journal, account=_account(organization, RESERVE_FOR_ENCUMBRANCE_CODE), description="Reserve for encumbrance", debit_idr=0, credit_idr=amount)
    return journal


def reverse_encumbrance(*, organization, journal_no, date, amount, budget_item):
    """PO/contract cancelled: reverse a prior encumbrance entry."""
    journal = JournalEntry.objects.create(
        organization=organization, journal_no=journal_no, date=date, reference=journal_no,
        description=f"Encumbrance reversal — {journal_no}", status="Posted", budget_item=budget_item,
    )
    JournalLine.objects.create(journal=journal, account=_account(organization, RESERVE_FOR_ENCUMBRANCE_CODE), description="Reserve for encumbrance", debit_idr=amount, credit_idr=0)
    JournalLine.objects.create(journal=journal, account=_account(organization, ENCUMBRANCE_CODE), description="Encumbrance", debit_idr=0, credit_idr=amount)
    return journal


def post_invoice_accrual(*, organization, invoice, journal_no, date, expense_account, budget_item):
    """Invoice approved: Dr Expense, Cr Accounts Payable."""
    journal = JournalEntry.objects.create(
        organization=organization, journal_no=journal_no, date=date, reference=journal_no,
        description=f"Invoice accrual — {invoice.invoice_number}", status="Posted",
        budget_item=budget_item, source_payable_invoice=invoice,
    )
    JournalLine.objects.create(journal=journal, account=expense_account, description="Expense", debit_idr=invoice.total, credit_idr=0)
    JournalLine.objects.create(journal=journal, account=_account(organization, ACCOUNTS_PAYABLE_CODE), description="Accounts payable", debit_idr=0, credit_idr=invoice.total)
    return journal


def post_payment(*, organization, payment, journal_no, date, budget_item):
    """Payment released: Dr Accounts Payable, Cr Cash and Bank."""
    journal = JournalEntry.objects.create(
        organization=organization, journal_no=journal_no, date=date, reference=journal_no,
        description=f"Vendor payment — {journal_no}", status="Posted",
        budget_item=budget_item, source_payment=payment,
    )
    JournalLine.objects.create(journal=journal, account=_account(organization, ACCOUNTS_PAYABLE_CODE), description="Accounts payable", debit_idr=payment.amount, credit_idr=0)
    JournalLine.objects.create(journal=journal, account=_account(organization, CASH_ACCOUNT_CODE), description="Cash and bank", debit_idr=0, credit_idr=payment.amount)
    return journal
