# SPDX-License-Identifier: MIT
"""Native Decimal double-entry postings bound into the existing event hash chain.

Opening scope is the first instrumented snapshot; rc1 history is not reconstructed.
Unknown fees remain explicit management estimates, never invented actual fees.
"""
from decimal import Decimal, getcontext
import json

getcontext().prec = 50
D = Decimal
Z = D('0')


def install(db):
    db.execute('PRAGMA foreign_keys=ON')
    db.executescript('''CREATE TABLE IF NOT EXISTS journal_lines(
      event_id INTEGER NOT NULL,seq INTEGER NOT NULL,account TEXT NOT NULL,
      debit TEXT NOT NULL,credit TEXT NOT NULL,memo TEXT NOT NULL,
      PRIMARY KEY(event_id,seq),FOREIGN KEY(event_id) REFERENCES events(id));''')
    db.execute('CREATE INDEX IF NOT EXISTS native_events_at ON events(at,id)')
    for table in ('events', 'receipts', 'lessons', 'journal_lines'):
        for operation in ('UPDATE', 'DELETE'):
            db.execute(f"CREATE TRIGGER IF NOT EXISTS native_immutable_{table}_{operation} "
                       f"BEFORE {operation} ON {table} BEGIN "
                       "SELECT RAISE(ABORT,'append-only audit table'); END")
    db.commit()


def post(lines, account, amount, memo):
    amount = D(amount)
    if not amount.is_finite():
        raise ValueError('nonfinite posting')
    lines.append({'account': account, 'debit': max(Z, amount), 'credit': max(Z, -amount), 'memo': memo})


def finish(journal, body, lines, book):
    debit = sum((p['debit'] for p in lines), Z)
    credit = sum((p['credit'] for p in lines), Z)
    if debit != credit:
        raise ValueError('unbalanced journal')
    body['journal'] = lines
    body['accounting'] = {'debit_total': debit, 'credit_total': credit, 'book_after': book,
        'policy': 'internal_fee_inclusive_management_accounting',
        'actual_fees_complete': journal.get('actual_fees_complete', True)}
    journal.set('book', book)
    return body


def observe(journal, body, snapshot):
    cash, gross = D(snapshot['cash']), D(snapshot['gross_asset_value'])
    cost, qty = D(journal.get('asset_cost', '0')), D(journal.get('quantity', '0'))
    previous = journal.get('book')
    lines = []
    if previous is None:
        book = {'cash': cash, 'cost': cost, 'value': gross, 'quantity': qty}
        post(lines, 'Assets:Cash', cash, 'Opening instrumented cash; earlier history not reconstructed')
        post(lines, 'Assets:Inventory', cost, 'Opening fee-inclusive acquisition cost')
        post(lines, 'Assets:ValuationAdjustment', gross - cost, 'Opening recorded valuation')
        post(lines, 'Equity:Opening', -(cash + gross), 'Opening instrumented net assets')
        body['opening_scope'] = 'first_instrumented_snapshot_only'
        return finish(journal, body, lines, book)
    old = {k: D(v) for k, v in previous.items()}
    pending = bool(journal.db.execute("SELECT count(*) FROM intents WHERE status='pending'").fetchone()[0])
    # A snapshot may observe a fill before its authoritative receipt arrives.
    # Keep reported values, but defer cash/cost reconciliation rather than invent a fill.
    if pending or journal.order_gate.locked():
        body['reconciliation_deferred_pending_order'] = True
        value = old['value']
        if D(snapshot['quantity']) > 0:
            value = gross / D(snapshot['quantity']) * old['quantity']
        book = {**old, 'value': value}
    else:
        book = {'cash': cash, 'cost': cost, 'value': gross, 'quantity': qty}
        dcash, dcost = cash - old['cash'], cost - old['cost']
        low = D(journal.get('cash_lower', journal.cfg['starting_capital']))
        high = D(journal.get('cash_upper', journal.cfg['starting_capital']))
        explained_estimate = (not journal.get('actual_fees_complete', True) and
                              low - D('.01') <= cash <= high + D('.01') and dcost == 0)
        if dcash or dcost:
            post(lines, 'Assets:Cash', dcash, 'Observed cash reconciliation')
            post(lines, 'Assets:Inventory', dcost, 'Observed acquisition-cost reconciliation')
            if explained_estimate:
                post(lines, 'Equity:FeeEstimateReconciliation', -(dcash + dcost),
                     'Adjustment within disclosed unknown-fee reserve; not a classified actual fee')
            else:
                post(lines, 'Equity:UnclassifiedSuspense', -(dcash + dcost),
                     'Unexplained movement; operator reconciliation required')
                journal._lesson('account.reconciliation', {'code': 'UNCLASSIFIED_MOVEMENT',
                    'expected_cash': old['cash'], 'actual_cash': cash,
                    'expected_cost': old['cost'], 'actual_cost': cost})
                journal.set('accounting_unresolved', True)
    valuation = (book['value'] - book['cost']) - (old['value'] - old['cost'])
    post(lines, 'Assets:ValuationAdjustment', valuation, 'Mark recorded holdings to observed price')
    post(lines, 'Income:UnrealizedPnL', -valuation, 'Offset observed mark-to-market')
    return finish(journal, body, lines, book)


def execution(journal, body, intent, result, released_cost, prior_quantity):
    previous = journal.get('book')
    if previous is None:
        # Direct receipt recovery can precede a first new-version observation.
        previous = {'cash': journal.cfg['starting_capital'], 'cost': '0', 'value': '0', 'quantity': '0'}
        lines = []
        post(lines, 'Assets:Cash', D(previous['cash']), 'Opening receipt-recovery baseline')
        post(lines, 'Equity:Opening', -D(previous['cash']), 'Opening receipt-recovery scope')
    else:
        lines = []
    old = {k: D(v) for k, v in previous.items()}
    qty, price = D(result['quantity']), D(result['price'])
    if qty <= 0:
        return finish(journal, body, lines, old)
    gross = qty * price
    fee = result.get('fee')
    if fee is None:
        fee = gross * D(journal.cfg['fee_reserve_rate'])
        journal.set('actual_fees_complete', False)
    else:
        fee = D(fee)
    if fee < 0 or price <= 0:
        raise ValueError('invalid authoritative execution metrics')
    if intent['side'] == 'buy':
        post(lines, 'Assets:Inventory', gross + fee, 'BUY: capitalize entry fee or disclosed reserve')
        post(lines, 'Assets:Cash', -(gross + fee), 'BUY: gross debit plus fee/reserve')
        value = old['value'] + gross
        cash = old['cash'] - gross - fee
    else:
        post(lines, 'Assets:Cash', gross - fee, 'SELL: proceeds net of fee/reserve')
        post(lines, 'Assets:Inventory', -released_cost, 'SELL: release fee-inclusive acquisition cost')
        post(lines, 'Income:RealizedPnL', -(gross - fee - released_cost), 'SELL: net proceeds minus released cost')
        remaining = max(Z, prior_quantity - qty)
        value = old['value'] * remaining / prior_quantity if prior_quantity else Z
        cash = old['cash'] + gross - fee
    cost = D(journal.get('asset_cost', '0'))
    book = {'cash': cash, 'cost': cost, 'value': value, 'quantity': D(journal.get('quantity', '0'))}
    valuation = (value - cost) - (old['value'] - old['cost'])
    post(lines, 'Assets:ValuationAdjustment', valuation, 'Reclassify execution valuation')
    post(lines, 'Income:UnrealizedPnL', -valuation, 'Offset execution valuation reclassification')
    body['released_cost'] = released_cost
    body['fee_posted'] = fee
    body['fee_basis'] = result['fee_basis']
    return finish(journal, body, lines, book)


def rows(db, event_id):
    return [{'account': a, 'debit': d, 'credit': c, 'memo': m} for a,d,c,m in db.execute(
        'SELECT account,debit,credit,memo FROM journal_lines WHERE event_id=? ORDER BY seq', (event_id,))]
