"""
bank.py — a mock core-banking "system of record" and the agent's TOOLS.

Two banking principles are baked in (Knowledge Module 03 §12, K09 §9):

  1. The LLM is NEVER the source of truth for a money fact. Balances and
     transactions come from THIS module (the system of record). The agent calls
     a tool; the tool returns the real number; the LLM only phrases it.

  2. LEAST-PRIVILEGE / on-behalf-of. Every tool enforces that the account
     belongs to the calling user. An injected "transfer from someone else's
     account" cannot succeed because the TOOL checks ownership — not the prompt.
"""
from __future__ import annotations

from dataclasses import dataclass, field


class AuthorizationError(Exception):
    pass


@dataclass
class Account:
    id: str
    owner: str
    balance: float          # in AED
    txns: list[dict] = field(default_factory=list)


class CoreBanking:
    """In-memory stand-in for Finacle/Temenos/Flexcube. In production each method
    is a call to the real core system over a private network; the ownership check
    becomes an on-behalf-of (OBO) token carrying the user's identity."""

    def __init__(self) -> None:
        self.accounts: dict[str, Account] = {
            "AE070331234567890123456": Account("AE070331234567890123456", "user_amal", 1250.00, [
                {"date": "2026-06-10", "desc": "Grocery POS", "amount": -85.50},
                {"date": "2026-06-09", "desc": "Salary", "amount": 9000.00},
            ]),
            "AE980332222333344445555": Account("AE980332222333344445555", "user_omar", 40.00, []),
        }

    def _owned(self, account_id: str, user: str) -> Account:
        acct = self.accounts.get(account_id)
        if acct is None:
            raise AuthorizationError(f"account {account_id} not found")
        if acct.owner != user:
            # The decisive control: the user cannot touch an account they don't own.
            raise AuthorizationError(f"user {user} is not authorized for {account_id}")
        return acct

    # --- TOOLS (each is a least-privilege, validated function) ---
    def get_balance(self, account_id: str, user: str) -> dict:
        a = self._owned(account_id, user)
        return {"account_id": a.id, "balance": a.balance, "currency": "AED"}

    def list_transactions(self, account_id: str, user: str, days: int = 30) -> dict:
        a = self._owned(account_id, user)
        return {"account_id": a.id, "transactions": a.txns[:50]}

    def initiate_transfer(self, account_id: str, user: str, to: str, amount: float) -> dict:
        """HIGH-RISK, state-mutating, IRREVERSIBLE. Must be human-approved before
        it runs (see agent.py human_review interrupt). Idempotent-friendly:
        validates funds and ownership; a real impl takes an idempotency key so a
        retried request can't double-execute."""
        a = self._owned(account_id, user)
        if amount <= 0:
            raise ValueError("amount must be positive")
        if amount > a.balance:
            raise ValueError("insufficient funds")
        a.balance -= amount
        a.txns.insert(0, {"date": "2026-06-14", "desc": f"Transfer to {to}", "amount": -amount})
        return {"status": "executed", "from": a.id, "to": to, "amount": amount,
                "new_balance": a.balance}
