"""Reproduce the FortuneLeaf budget article using fixed fictional records.

No input files, account connections, network access or extra packages are used.
Amounts are integer hundredths of an invented currency unit. This is a limited
record-keeping example, not a banking, tax or accounting product.
"""
import copy
import json

OPENING = {'cash': {'bank': 100000, 'wallet': 10000}, 'card_debt': 0}
TRANSACTIONS = [
    {'id': 'T1', 'kind': 'income', 'account': 'bank', 'amount': 200000},
    {'id': 'T2', 'kind': 'cash_expense', 'account': 'bank', 'amount': 12000},
    {'id': 'T3', 'kind': 'transfer', 'source': 'bank', 'target': 'wallet', 'amount': 20000},
    {'id': 'T4', 'kind': 'card_purchase', 'amount': 6000},
    {'id': 'T5', 'kind': 'card_refund', 'amount': 1000},
    {'id': 'T6', 'kind': 'card_payment', 'account': 'bank', 'amount': 2000},
    {'id': 'T7', 'kind': 'cash_expense', 'account': 'bank', 'amount': 70000},
    {'id': 'T8', 'kind': 'cash_expense', 'account': 'wallet', 'amount': 3000},
]


def calculate(opening, transactions):
    cash = dict(opening['cash'])
    debt = opening['card_debt']
    if not cash or any(type(x) is not int or x < 0 for x in [*cash.values(), debt]):
        raise ValueError('opening balances must be non-negative integer minor units')
    opening_cash = sum(cash.values())
    opening_net = opening_cash - debt
    income = expense = cash_paid = naive_expense = 0
    rows, seen = [], set()
    allowed = {'income', 'cash_expense', 'transfer', 'card_purchase', 'card_refund', 'card_payment'}
    for transaction in transactions:
        ident = transaction.get('id')
        if not isinstance(ident, str) or not ident or ident in seen:
            raise ValueError('missing or duplicate transaction id')
        seen.add(ident)
        kind, amount = transaction.get('kind'), transaction.get('amount')
        if kind not in allowed:
            raise ValueError('unknown transaction kind')
        if type(amount) is not int or amount <= 0:
            raise ValueError('amount must be a positive integer in minor units')
        if kind in {'income', 'cash_expense', 'card_payment'} and transaction.get('account') not in cash:
            raise ValueError('unknown cash account')
        if kind == 'transfer':
            source, target = transaction.get('source'), transaction.get('target')
            if source not in cash or target not in cash or source == target:
                raise ValueError('transfer must connect two different known cash accounts')
        before_cash, before_debt = sum(cash.values()), debt
        row_income = row_expense = row_paid = 0
        if kind == 'income':
            cash[transaction['account']] += amount
            income += amount
            row_income = amount
        elif kind == 'cash_expense':
            cash[transaction['account']] -= amount
            expense += amount
            cash_paid += amount
            row_expense = row_paid = amount
        elif kind == 'transfer':
            cash[source] -= amount
            cash[target] += amount
        elif kind == 'card_purchase':
            debt += amount
            expense += amount
            row_expense = amount
        elif kind == 'card_refund':
            debt -= amount
            expense -= amount
            row_expense = -amount
        elif kind == 'card_payment':
            cash[transaction['account']] -= amount
            debt -= amount
            cash_paid += amount
            row_paid = amount
        if min(cash.values()) < 0 or debt < 0:
            raise ValueError('this example excludes cash overdrafts and card credit balances')
        if kind != 'income':
            naive_expense += -amount if kind == 'card_refund' else amount
        row = {**transaction, 'income': row_income, 'consumption_expense': row_expense,
               'external_cash_paid': row_paid, 'combined_cash_change': sum(cash.values()) - before_cash,
               'card_debt_change': debt - before_debt,
               'cash_after': dict(cash), 'card_debt_after': debt}
        assert row['combined_cash_change'] - row['card_debt_change'] == row_income - row_expense
        rows.append(row)
    closing_cash = sum(cash.values())
    closing_net = closing_cash - debt
    assert opening_cash + income - cash_paid == closing_cash
    assert opening_net + income - expense == closing_net
    return {'opening_cash': opening_cash, 'opening_net_position': opening_net,
            'income': income, 'consumption_expense': expense,
            'external_cash_paid': cash_paid, 'naive_expense_with_transfer_and_payment': naive_expense,
            'closing_cash_accounts': cash, 'closing_cash': closing_cash,
            'closing_card_debt': debt, 'closing_net_position': closing_net, 'rows': rows}


def run():
    main = calculate(OPENING, TRANSACTIONS)
    assert {k: main[k] for k in ['income', 'consumption_expense', 'external_cash_paid',
                                 'naive_expense_with_transfer_and_payment', 'closing_cash',
                                 'closing_card_debt', 'closing_net_position']} == {
        'income': 200000, 'consumption_expense': 90000, 'external_cash_paid': 87000,
        'naive_expense_with_transfer_and_payment': 112000, 'closing_cash': 223000,
        'closing_card_debt': 3000, 'closing_net_position': 220000}
    assert main['closing_cash_accounts'] == {'bank': 196000, 'wallet': 27000}
    no_payment = calculate(OPENING, [x for x in TRANSACTIONS if x['id'] != 'T6'])
    assert (no_payment['consumption_expense'], no_payment['external_cash_paid'],
            no_payment['closing_cash'], no_payment['closing_card_debt'],
            no_payment['closing_net_position']) == (90000, 85000, 225000, 5000, 220000)
    old_debt = calculate({'cash': {'bank': 100000}, 'card_debt': 15000},
                         [{'id': 'OLD', 'kind': 'card_payment', 'account': 'bank', 'amount': 15000}])
    assert (old_debt['consumption_expense'], old_debt['external_cash_paid'],
            old_debt['opening_net_position'], old_debt['closing_net_position']) == (0, 15000, 85000, 85000)
    bad = []
    for label, mutate, expected in [
        ('unknown_kind', lambda x: x[0].update(kind='unclassified'), 'unknown transaction kind'),
        ('duplicate_id', lambda x: x.append(dict(x[0])), 'duplicate transaction id'),
        ('negative_amount', lambda x: x[0].update(amount=-1), 'positive integer'),
        ('fractional_amount', lambda x: x[0].update(amount=1.5), 'positive integer'),
        ('unknown_account', lambda x: x[0].update(account='other'), 'unknown cash account'),
        ('same_account_transfer', lambda x: x[2].update(target='bank'), 'two different'),
        ('card_overpayment', lambda x: x[5].update(amount=9000), 'excludes cash overdrafts'),
        ('missing_amount', lambda x: x[0].pop('amount'), 'positive integer'),
    ]:
        candidate = copy.deepcopy(TRANSACTIONS)
        mutate(candidate)
        try:
            calculate(OPENING, candidate)
        except ValueError as error:
            assert expected in str(error), (label, str(error))
            bad.append({'case': label, 'outcome': 'rejected_as_expected', 'reason': str(error)})
        else:
            raise AssertionError('invalid input accepted: ' + label)
    return {'date': '2026-10-24', 'units': 'integer hundredths of one fictional currency unit; divide by 100 for article figures',
            'scope': 'Fixed generated records, one household, two cash locations and one card liability. Purchase-date consumption and cash-flow views differ. No interest, fees, tax, real statements, investment, overdraft or card credit balances are modeled.',
            'opening': OPENING, 'transactions': TRANSACTIONS,
            'main': main, 'payment_in_next_period': no_payment,
            'payment_of_opening_debt_only': old_debt, 'invalid_inputs': bad}


if __name__ == '__main__':
    print(json.dumps(run(), ensure_ascii=False, indent=2))
