-- Balance sheet schema (Bilan) — Migration v9
-- Created: 2026-04-25
-- Issue: #138 (Bilan #1a — Schema migration + balance.service skeleton + AccountsPage)
--
-- Adds 5 tables, 7 indexes, and seeds 7 standard categories (5 simple + 2 priced).
-- Conventions aligned with consolidated_schema.sql:
--   - INTEGER PRIMARY KEY AUTOINCREMENT
--   - REAL for monetary amounts (matches transactions.amount)
--   - snake_case
--   - FK with explicit ON DELETE policies
--   - is_* INTEGER NOT NULL DEFAULT for booleans
--   - DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP for timestamps
--
-- MVP constraints (decisions-log + spec-decisions-bilan.md):
--   - balance_accounts.currency hardcoded to 'CAD' via CHECK — v2 will lift this
--   - balance_account_transfers.transaction_id ON DELETE RESTRICT (preserves
--     reproducibility of Modified Dietz returns calculated on past periods)
--   - balance_snapshot_lines kind invariants: (quantity, unit_price) both NULL
--     (simple kind) OR both NOT NULL (priced kind)


-- =========================================================================
-- balance_categories — taxonomy of asset types
-- =========================================================================
-- Seeded with 7 standard categories (is_seed = 1). Users can add custom
-- categories with their own kind ('simple' or 'priced'). Seeded categories
-- can be renamed but never deleted.
CREATE TABLE IF NOT EXISTS balance_categories (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    key TEXT NOT NULL UNIQUE,                    -- 'cash', 'tfsa', 'rrsp', 'fund', 'stock', 'crypto', 'other'
    i18n_key TEXT NOT NULL,                      -- 'balance.category.cash', etc.
    kind TEXT NOT NULL CHECK(kind IN ('simple','priced')),
    sort_order INTEGER NOT NULL DEFAULT 0,
    is_active INTEGER NOT NULL DEFAULT 1,
    is_seed INTEGER NOT NULL DEFAULT 0
);


-- =========================================================================
-- balance_accounts — user's specific holdings
-- =========================================================================
-- A "TFSA at Wealthsimple", a "BTC in Ledger", etc.
-- For priced categories, `symbol` identifies the security/coin.
-- For simple categories, `symbol` is NULL.
-- MVP: currency hardcoded to 'CAD' — v2 lifts the CHECK and adds a rate table.
CREATE TABLE IF NOT EXISTS balance_accounts (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    balance_category_id INTEGER NOT NULL,
    name TEXT NOT NULL,
    symbol TEXT,
    currency TEXT NOT NULL DEFAULT 'CAD' CHECK(currency = 'CAD'),
    notes TEXT,
    is_active INTEGER NOT NULL DEFAULT 1,
    archived_at DATETIME,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (balance_category_id) REFERENCES balance_categories(id) ON DELETE RESTRICT
);


-- =========================================================================
-- balance_snapshots — point-in-time captures
-- =========================================================================
-- One snapshot per `snapshot_date` (UNIQUE). Editing a snapshot = updating
-- its lines, not creating a duplicate.
CREATE TABLE IF NOT EXISTS balance_snapshots (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    snapshot_date DATE NOT NULL UNIQUE,
    notes TEXT,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);


-- =========================================================================
-- balance_snapshot_lines — one row per (snapshot, account)
-- =========================================================================
-- Storage shape:
--   - simple kind: value is set, quantity/unit_price are NULL
--   - priced kind: quantity + unit_price are set, value = quantity * unit_price
-- Stored denormalized (value always set, even for priced rows) so reports
-- are reproducible without re-fetching prices and the user can override a
-- fetched price.
-- The CHECK enforces kind invariants at SQL level for direct-write safety.
CREATE TABLE IF NOT EXISTS balance_snapshot_lines (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    snapshot_id INTEGER NOT NULL,
    account_id INTEGER NOT NULL,
    quantity REAL,
    unit_price REAL,
    value REAL NOT NULL,
    price_source TEXT,                           -- 'manual' | 'maximus-api' | NULL for simple
    price_fetched_at DATETIME,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (snapshot_id) REFERENCES balance_snapshots(id) ON DELETE CASCADE,
    FOREIGN KEY (account_id) REFERENCES balance_accounts(id) ON DELETE RESTRICT,
    UNIQUE(snapshot_id, account_id),
    CHECK (
        (quantity IS NULL AND unit_price IS NULL)
        OR (quantity IS NOT NULL AND unit_price IS NOT NULL)
    )
);


-- =========================================================================
-- balance_account_transfers — links transactions to accounts (capital flows)
-- =========================================================================
-- Used by the Modified Dietz return calculator (Issue #142 / Bilan #4) to
-- separate contributions from gains. Direction follows the account's
-- perspective: 'in' = capital added (deposit/buy), 'out' = capital removed
-- (withdrawal/sell). The amount is taken from the linked transaction (no
-- duplication).
--
-- transaction_id ON DELETE RESTRICT: preserves reproducibility of past
-- Modified Dietz returns. The UI must force unlink before allowing the
-- transaction to be deleted.
CREATE TABLE IF NOT EXISTS balance_account_transfers (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    account_id INTEGER NOT NULL,
    transaction_id INTEGER NOT NULL,
    direction TEXT NOT NULL CHECK(direction IN ('in','out')),
    notes TEXT,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (account_id) REFERENCES balance_accounts(id) ON DELETE CASCADE,
    FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE RESTRICT,
    UNIQUE(transaction_id, account_id)
);


-- =========================================================================
-- Indexes (7 total)
-- =========================================================================
CREATE INDEX IF NOT EXISTS idx_balance_accounts_category ON balance_accounts(balance_category_id);
CREATE INDEX IF NOT EXISTS idx_balance_accounts_active ON balance_accounts(is_active) WHERE is_active = 1;
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_lines_snapshot ON balance_snapshot_lines(snapshot_id);
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_lines_account ON balance_snapshot_lines(account_id);
CREATE INDEX IF NOT EXISTS idx_balance_account_transfers_account ON balance_account_transfers(account_id);
CREATE INDEX IF NOT EXISTS idx_balance_account_transfers_transaction ON balance_account_transfers(transaction_id);
CREATE INDEX IF NOT EXISTS idx_balance_snapshots_date ON balance_snapshots(snapshot_date);


-- =========================================================================
-- Seed (7 standard categories — idempotent via INSERT OR IGNORE on `key`)
-- =========================================================================
INSERT OR IGNORE INTO balance_categories (key, i18n_key, kind, sort_order, is_seed) VALUES
    ('cash',   'balance.category.cash',   'simple', 10, 1),
    ('tfsa',   'balance.category.tfsa',   'simple', 20, 1),
    ('rrsp',   'balance.category.rrsp',   'simple', 30, 1),
    ('fund',   'balance.category.fund',   'simple', 40, 1),
    ('other',  'balance.category.other',  'simple', 50, 1),
    ('stock',  'balance.category.stock',  'priced', 60, 1),
    ('crypto', 'balance.category.crypto', 'priced', 70, 1);
