"""Asset ledger for the Bannerlord sovereign agent.

Tracks kingdom assets (denars, settlements, troop allocations) in an
in-memory dict backed by SQLite for persistence.  Follows the existing
SQLite migration pattern in this repo.

The King has exclusive write access to treasury and settlement ownership.
Vassals receive an allocated budget and cannot exceed it without King
re-authorization.  Companions hold only work-in-progress quotas.

Refs: #1097, #1099.
"""

from __future__ import annotations

import logging
import sqlite3
from contextlib import contextmanager
from datetime import datetime
from pathlib import Path
from typing import Iterator

logger = logging.getLogger(__name__)

_DEFAULT_DB = Path.home() / ".timmy" / "bannerlord" / "ledger.db"


class BudgetExceeded(ValueError):
    """Raised when a vassal attempts to exceed its allocated budget."""


class Ledger:
    """Sovereign asset ledger backed by SQLite.

    Tracks:
    - Kingdom treasury (denar balance)
    - Fief (settlement) ownership roster
    - Vassal denar budgets (delegated, revocable)
    - Campaign tick log (for long-horizon planning)

    Usage::

        ledger = Ledger()
        ledger.initialize()
        ledger.deposit(5000, "tax income — Epicrotea")
        ledger.allocate_budget("war_vassal", 2000)
    """

    def __init__(self, db_path: Path = _DEFAULT_DB) -> None:
        self._db_path = db_path
        self._db_path.parent.mkdir(parents=True, exist_ok=True)

    # ── Setup ─────────────────────────────────────────────────────────────

    def initialize(self) -> None:
        """Create tables if they don't exist."""
        with self._conn() as conn:
            conn.executescript(
                """
                CREATE TABLE IF NOT EXISTS treasury (
                    id      INTEGER PRIMARY KEY CHECK (id = 1),
                    balance REAL    NOT NULL DEFAULT 0
                );
                INSERT OR IGNORE INTO treasury (id, balance) VALUES (1, 0);

                CREATE TABLE IF NOT EXISTS fiefs (
                    name       TEXT PRIMARY KEY,
                    fief_type  TEXT NOT NULL,  -- town / castle / village
                    acquired_at TEXT NOT NULL
                );

                CREATE TABLE IF NOT EXISTS vassal_budgets (
                    agent       TEXT PRIMARY KEY,
                    allocated   REAL NOT NULL DEFAULT 0,
                    spent       REAL NOT NULL DEFAULT 0
                );

                CREATE TABLE IF NOT EXISTS tick_log (
                    tick        INTEGER PRIMARY KEY,
                    campaign_day INTEGER NOT NULL,
                    subgoal     TEXT,
                    reward_war  REAL,
                    reward_econ REAL,
                    reward_diplo REAL,
                    logged_at   TEXT NOT NULL
                );
                """
            )
        logger.debug("Ledger initialized at %s", self._db_path)

    # ── Treasury ──────────────────────────────────────────────────────────

    def balance(self) -> float:
        with self._conn() as conn:
            row = conn.execute("SELECT balance FROM treasury WHERE id = 1").fetchone()
            return float(row[0]) if row else 0.0

    def deposit(self, amount: float, reason: str = "") -> float:
        """Add *amount* denars to treasury.  Returns new balance."""
        if amount < 0:
            raise ValueError("Use withdraw() for negative amounts")
        with self._conn() as conn:
            conn.execute(
                "UPDATE treasury SET balance = balance + ? WHERE id = 1", (amount,)
            )
        bal = self.balance()
        logger.info("Treasury +%.0f denars (%s) → balance %.0f", amount, reason, bal)
        return bal

    def withdraw(self, amount: float, reason: str = "") -> float:
        """Remove *amount* denars from treasury.  Returns new balance."""
        if amount < 0:
            raise ValueError("Amount must be positive")
        bal = self.balance()
        if amount > bal:
            raise BudgetExceeded(
                f"Cannot withdraw {amount:.0f} denars — treasury balance is only {bal:.0f}"
            )
        with self._conn() as conn:
            conn.execute(
                "UPDATE treasury SET balance = balance - ? WHERE id = 1", (amount,)
            )
        new_bal = self.balance()
        logger.info("Treasury -%.0f denars (%s) → balance %.0f", amount, reason, new_bal)
        return new_bal

    # ── Fiefs ─────────────────────────────────────────────────────────────

    def add_fief(self, name: str, fief_type: str) -> None:
        with self._conn() as conn:
            conn.execute(
                "INSERT OR REPLACE INTO fiefs (name, fief_type, acquired_at) VALUES (?, ?, ?)",
                (name, fief_type, datetime.utcnow().isoformat()),
            )
        logger.info("Fief acquired: %s (%s)", name, fief_type)

    def remove_fief(self, name: str) -> None:
        with self._conn() as conn:
            conn.execute("DELETE FROM fiefs WHERE name = ?", (name,))
        logger.info("Fief lost: %s", name)

    def list_fiefs(self) -> list[dict[str, str]]:
        with self._conn() as conn:
            rows = conn.execute("SELECT name, fief_type, acquired_at FROM fiefs").fetchall()
        return [{"name": r[0], "fief_type": r[1], "acquired_at": r[2]} for r in rows]

    # ── Vassal budgets ────────────────────────────────────────────────────

    def allocate_budget(self, agent: str, amount: float) -> None:
        """Delegate *amount* denars to a vassal agent.

        Withdraws from treasury.  Raises :class:`BudgetExceeded` if
        the treasury cannot cover the allocation.
        """
        self.withdraw(amount, reason=f"budget → {agent}")
        with self._conn() as conn:
            conn.execute(
                """
                INSERT INTO vassal_budgets (agent, allocated, spent)
                VALUES (?, ?, 0)
                ON CONFLICT(agent) DO UPDATE SET allocated = allocated + excluded.allocated
                """,
                (agent, amount),
            )
        logger.info("Allocated %.0f denars to %s", amount, agent)

    def record_vassal_spend(self, agent: str, amount: float) -> None:
        """Record that a vassal spent *amount* from its budget."""
        with self._conn() as conn:
            row = conn.execute(
                "SELECT allocated, spent FROM vassal_budgets WHERE agent = ?", (agent,)
            ).fetchone()
        if row is None:
            raise BudgetExceeded(f"{agent} has no allocated budget")
        allocated, spent = row
        if spent + amount > allocated:
            raise BudgetExceeded(
                f"{agent} budget exhausted: {spent:.0f}/{allocated:.0f} spent, "
                f"requested {amount:.0f}"
            )
        with self._conn() as conn:
            conn.execute(
                "UPDATE vassal_budgets SET spent = spent + ? WHERE agent = ?",
                (amount, agent),
            )

    def vassal_remaining(self, agent: str) -> float:
        with self._conn() as conn:
            row = conn.execute(
                "SELECT allocated - spent FROM vassal_budgets WHERE agent = ?", (agent,)
            ).fetchone()
        return float(row[0]) if row else 0.0

    # ── Tick log ──────────────────────────────────────────────────────────

    def log_tick(
        self,
        tick: int,
        campaign_day: int,
        subgoal: str | None = None,
        reward_war: float | None = None,
        reward_econ: float | None = None,
        reward_diplo: float | None = None,
    ) -> None:
        with self._conn() as conn:
            conn.execute(
                """
                INSERT OR REPLACE INTO tick_log
                    (tick, campaign_day, subgoal, reward_war, reward_econ, reward_diplo, logged_at)
                VALUES (?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    tick,
                    campaign_day,
                    subgoal,
                    reward_war,
                    reward_econ,
                    reward_diplo,
                    datetime.utcnow().isoformat(),
                ),
            )

    def tick_history(self, last_n: int = 100) -> list[dict]:
        with self._conn() as conn:
            rows = conn.execute(
                """
                SELECT tick, campaign_day, subgoal, reward_war, reward_econ, reward_diplo, logged_at
                FROM tick_log
                ORDER BY tick DESC
                LIMIT ?
                """,
                (last_n,),
            ).fetchall()
        return [
            {
                "tick": r[0],
                "campaign_day": r[1],
                "subgoal": r[2],
                "reward_war": r[3],
                "reward_econ": r[4],
                "reward_diplo": r[5],
                "logged_at": r[6],
            }
            for r in rows
        ]

    # ── Internal ──────────────────────────────────────────────────────────

    @contextmanager
    def _conn(self) -> Iterator[sqlite3.Connection]:
        conn = sqlite3.connect(self._db_path)
        conn.execute("PRAGMA journal_mode=WAL")
        try:
            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()
