"""Double-entry fund accounting ledger."""

from __future__ import annotations

import logging
import math
import os
import sqlite3
import threading
import time
from dataclasses import dataclass
from enum import Enum

logger = logging.getLogger("horizon.fund.ledger")


class Account(str, Enum):
    """Ledger account types."""
    CASH = "cash"
    POSITIONS = "positions"
    FEES_RECEIVABLE = "fees_receivable"
    MANAGEMENT_FEES = "management_fees"
    PERFORMANCE_FEES = "performance_fees"
    REALIZED_PNL = "realized_pnl"
    UNREALIZED_PNL = "unrealized_pnl"


@dataclass(frozen=True)
class JournalEntry:
    """An immutable double-entry journal record."""
    entry_id: int
    timestamp: float
    debit_account: Account
    credit_account: Account
    amount: float
    reference: str
    description: str


class FundLedger:
    """SQLite-backed double-entry accounting ledger.

    Every capital movement is recorded as a journal entry with a debit
    and credit account. Account balances are derived from the sum of
    debits minus the sum of credits for that account.

    The ledger is append-only: entries cannot be modified or deleted.
    """

    _SCHEMA = """
    CREATE TABLE IF NOT EXISTS journal_entries (
        entry_id INTEGER PRIMARY KEY AUTOINCREMENT,
        timestamp REAL NOT NULL,
        debit_account TEXT NOT NULL,
        credit_account TEXT NOT NULL,
        amount REAL NOT NULL CHECK(amount > 0),
        reference TEXT NOT NULL DEFAULT '',
        description TEXT NOT NULL DEFAULT ''
    );
    CREATE INDEX IF NOT EXISTS idx_journal_ts ON journal_entries(timestamp);
    CREATE INDEX IF NOT EXISTS idx_journal_ref ON journal_entries(reference);
    CREATE INDEX IF NOT EXISTS idx_journal_debit ON journal_entries(debit_account);
    CREATE INDEX IF NOT EXISTS idx_journal_credit ON journal_entries(credit_account);
    """

    def __init__(self, db_path: str | None = None) -> None:
        self._db_path = db_path or os.path.join(
            os.path.expanduser("~"), ".horizon", "fund_ledger.db"
        )
        db_dir = os.path.dirname(self._db_path)
        if db_dir:
            os.makedirs(db_dir, exist_ok=True)
        self._conn = sqlite3.connect(self._db_path, check_same_thread=False)
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._conn.executescript(self._SCHEMA)
        self._conn.commit()
        if db_dir:
            try:
                os.chmod(self._db_path, 0o600)
            except OSError:
                pass
        self._lock = threading.Lock()

    def _record(
        self,
        debit: Account,
        credit: Account,
        amount: float,
        reference: str,
        description: str,
    ) -> JournalEntry:
        """Record a journal entry. Amount must be positive and finite."""
        if not math.isfinite(amount) or amount <= 0:
            raise ValueError(f"Amount must be finite and positive, got {amount}")

        now = time.time()
        with self._lock:
            cursor = self._conn.execute(
                """INSERT INTO journal_entries
                   (timestamp, debit_account, credit_account, amount, reference, description)
                   VALUES (?, ?, ?, ?, ?, ?)""",
                (now, debit.value, credit.value, amount, str(reference), str(description)),
            )
            self._conn.commit()
            entry_id = cursor.lastrowid or 0

        return JournalEntry(
            entry_id=entry_id,
            timestamp=now,
            debit_account=debit,
            credit_account=credit,
            amount=amount,
            reference=str(reference),
            description=str(description),
        )

    def deposit(self, amount: float, reference: str = "") -> JournalEntry:
        """Record a cash deposit into the fund.

        Debit CASH (increase asset), credit REALIZED_PNL (equity proxy).
        """
        return self._record(
            Account.CASH, Account.REALIZED_PNL, amount, reference,
            f"Deposit: {amount:.2f}",
        )

    def withdraw(self, amount: float, reference: str = "") -> JournalEntry:
        """Record a cash withdrawal from the fund.

        Debit REALIZED_PNL (reduce equity proxy), credit CASH (decrease asset).
        """
        return self._record(
            Account.REALIZED_PNL, Account.CASH, amount, reference,
            f"Withdrawal: {amount:.2f}",
        )

    def record_trade_pnl(
        self, strategy: str, amount: float, reference: str = "",
    ) -> JournalEntry:
        """Record realized trading P&L for a strategy."""
        abs_amount = abs(amount)
        if abs_amount < 1e-12:
            raise ValueError("PnL amount too small to record")
        if amount >= 0:
            return self._record(
                Account.POSITIONS, Account.REALIZED_PNL, abs_amount, reference,
                f"Realized gain: {strategy} +{abs_amount:.2f}",
            )
        return self._record(
            Account.REALIZED_PNL, Account.POSITIONS, abs_amount, reference,
            f"Realized loss: {strategy} -{abs_amount:.2f}",
        )

    def accrue_fee(
        self, fee_type: str, amount: float, reference: str = "",
    ) -> JournalEntry:
        """Accrue a fee (management or performance)."""
        credit_account = (
            Account.MANAGEMENT_FEES if fee_type == "management"
            else Account.PERFORMANCE_FEES
        )
        return self._record(
            Account.FEES_RECEIVABLE, credit_account, amount, reference,
            f"{fee_type.capitalize()} fee: {amount:.4f}",
        )

    def balance(self, account: Account) -> float:
        """Compute the current balance of an account.

        Balance = SUM(debits to this account) - SUM(credits to this account).
        """
        with self._lock:
            row = self._conn.execute(
                """SELECT
                    COALESCE(SUM(CASE WHEN debit_account = ? THEN amount ELSE 0 END), 0) -
                    COALESCE(SUM(CASE WHEN credit_account = ? THEN amount ELSE 0 END), 0)
                FROM journal_entries""",
                (account.value, account.value),
            ).fetchone()
        return row[0] if row else 0.0

    def balance_sheet(self) -> dict[str, float]:
        """Return all account balances."""
        return {account.value: self.balance(account) for account in Account}

    def income_statement(self, since: float = 0.0) -> dict[str, float]:
        """Return income statement since a timestamp.

        Returns realized PnL, fees collected, and net income.
        """
        with self._lock:
            row = self._conn.execute(
                """SELECT
                    COALESCE(SUM(CASE WHEN credit_account = 'realized_pnl' THEN amount ELSE 0 END), 0) -
                    COALESCE(SUM(CASE WHEN debit_account = 'realized_pnl' THEN amount ELSE 0 END), 0),
                    COALESCE(SUM(CASE WHEN credit_account = 'management_fees' THEN amount ELSE 0 END), 0),
                    COALESCE(SUM(CASE WHEN credit_account = 'performance_fees' THEN amount ELSE 0 END), 0)
                FROM journal_entries WHERE timestamp >= ?""",
                (since,),
            ).fetchone()

        realized_pnl = row[0] if row else 0.0
        mgmt_fees = row[1] if row else 0.0
        perf_fees = row[2] if row else 0.0
        total_fees = mgmt_fees + perf_fees

        return {
            "realized_pnl": realized_pnl,
            "management_fees": mgmt_fees,
            "performance_fees": perf_fees,
            "total_fees": total_fees,
            "net_income": realized_pnl - total_fees,
        }

    def recent_entries(self, limit: int = 50) -> list[JournalEntry]:
        """Return the most recent journal entries."""
        with self._lock:
            rows = self._conn.execute(
                """SELECT entry_id, timestamp, debit_account, credit_account,
                          amount, reference, description
                   FROM journal_entries ORDER BY entry_id DESC LIMIT ?""",
                (limit,),
            ).fetchall()

        return [
            JournalEntry(
                entry_id=r[0],
                timestamp=r[1],
                debit_account=Account(r[2]),
                credit_account=Account(r[3]),
                amount=r[4],
                reference=r[5],
                description=r[6],
            )
            for r in reversed(rows)
        ]

    def entry_count(self) -> int:
        """Total number of journal entries."""
        with self._lock:
            row = self._conn.execute("SELECT COUNT(*) FROM journal_entries").fetchone()
        return row[0] if row else 0

    def close(self) -> None:
        """Close the database connection."""
        with self._lock:
            self._conn.close()
