"""
BUDGET — Phase 8, and the only slice where a rounding decision is a bug.

The contract is `front/src/renderer/src/features/budget/data/api.js` and the
field set is `features/budget/model.js`. Where this file disagrees with either,
this file is wrong.

**The endpoint is `/api/budget/transactions/`**, not `/api/budget/`. That is
what the shipped client calls.

Four things this model states deliberately:

1. **`amount_minor` IS AN INTEGER NUMBER OF MINOR UNITS, AND IT IS NAMED FOR
   IT.** Django's natural choice is `DecimalField`, which DRF serialises as a
   STRING ("41.75") — and the moment that string is `Number()`d on the way in,
   the ledger is back to floating point and the running balance drifts over
   four hundred rows until the total visibly is not the number the rows say.
   The column is a `BigIntegerField` of cents and the NAME says so, which makes
   a wrong reading impossible to make silently.

   **The scale is fixed at 100 and is a constant of the model, not a function
   of the currency.** `settings.currency.decimals` controls DISPLAY only. If the
   scale followed the currency, changing from a two-decimal currency to a
   zero-decimal one would silently multiply every stored amount by a hundred —
   the user changes a symbol in settings and their ledger moves two orders of
   magnitude.

2. **THE AMOUNT IS ALWAYS POSITIVE AND `kind` CARRIES THE SIGN.** A negative
   income is the same fact spelled a second way, and two spellings means every
   sum has to decide which one to trust. `parseAmount` drops a leading minus on
   the way in for exactly this reason; the serializer rejects one.

3. **NOTHING DERIVED IS STORED.** No balance-after, no limit-after, no totals,
   no category rollups. The frame draws "Balance After" and "Limit After" as
   stored-looking columns; they are functions of the list and are computed in
   the renderer. A stored running total has to be rewritten for every row after
   any insert, edit or delete, and the first time one of those rewrites is
   missed the column is silently wrong with nothing on screen saying so. A
   server that computed them too would be a second implementation of the same
   arithmetic, and the first time the two rounded differently the user would
   see a balance that disagreed with the rows it was made of.

4. **`category` IS A STRING ID, NOT A FOREIGN KEY.** `data/api.js` describes a
   FK with `SET_NULL`; that was written before the categories were read. They
   are a PAGE-SCOPED VOCABULARY living in that page's `settings` blob —
   `SEED_CATEGORIES` in `features/budget/settings.js`, each `{id, label, color,
   limit}` — exactly like tags, a sub-calendar id and a goal's colour. There is
   no table to point at, and making one would move a user-editable list out of
   the blob the client already owns and copies. Null is a real value:
   "uncategorised" is the state the breakdown chart most wants to be able to
   name, and an income row has no category by design — `kind` already says
   which way the money went, so a category saying it again would say one thing
   twice.
"""

from django.db import models

from core.models import TimeStampedModel

from .workspace import Page

# `KINDS` in `features/budget/model.js`. Two directions money can go, not two
# members of a list — which is why the client draws them in the polarity pair
# (`--success` / `--destructive`) rather than out of a category palette.
TRANSACTION_KINDS = ('expense', 'income')


class Transaction(TimeStampedModel):
    page = models.ForeignKey(Page, on_delete=models.CASCADE, related_name='transactions')

    # The one field that changes what a row MEANS, and the only carrier of the
    # amount's sign.
    kind = models.CharField(max_length=8, default='expense')

    name = models.CharField(max_length=300, blank=True)

    # A DAY, never a moment — money moves on a day. A timestamp would make two
    # transactions on one date sort by a clock nobody set, and the running
    # balance breaks that tie by creation order instead.
    date = models.DateField()

    # MINOR UNITS, ALWAYS POSITIVE. `BigInteger` because a lifetime ledger in a
    # currency with a small unit outgrows 32 bits, and widening a money column
    # after the fact is the migration nobody wants to run.
    amount_minor = models.BigIntegerField(default=0)

    # An id from the page's own category vocabulary, or null for
    # "uncategorised". See the module docstring for why this is not a FK.
    category = models.CharField(max_length=64, null=True, blank=True)

    notes = models.TextField(blank=True)

    properties = models.JSONField(default=dict, blank=True)

    is_sample = models.BooleanField(default=False)

    class Meta:
        # By date, then by insertion. THE SECOND HALF IS LOAD-BEARING: the
        # running balance breaks same-day ties by creation order, so two rows
        # on one day have to come back in the order they were written or the
        # balance column reads differently on every fetch.
        ordering = ['date', 'id']
        indexes = [
            models.Index(fields=['page', 'date']),
            models.Index(fields=['page', 'category']),
        ]

    def __str__(self):
        return self.name or f'(unnamed transaction {self.pk})'
