"""NAV (Net Asset Value) computation engine."""

from __future__ import annotations

import json
import logging
import math
import threading
import time
from collections import deque
from typing import Any

from horizon.fund._types import NAVSnapshot

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


class NAVEngine:
    """Computes fund NAV by aggregating across all strategy engines.

    NAV = initial_capital + sum(realized_pnl) + sum(unrealized_pnl)

    Thread-safe: the oversight thread calls compute_nav() while strategy
    threads are actively trading. Engine.positions() and Engine.status()
    are read-only operations safe for concurrent access.
    """

    def __init__(self, initial_capital: float) -> None:
        self._initial_capital = initial_capital
        self._hwm = initial_capital
        self._lock = threading.Lock()
        self._snapshots: deque[NAVSnapshot] = deque(maxlen=10_000)
        self._strategy_capitals: dict[str, float] = {}

    def set_allocation(self, name: str, capital: float) -> None:
        """Record capital allocated to a strategy."""
        with self._lock:
            self._strategy_capitals[name] = capital

    def remove_strategy(self, name: str) -> None:
        """Remove a strategy from NAV tracking."""
        with self._lock:
            self._strategy_capitals.pop(name, None)

    def compute_nav(self, engines: dict[str, Any]) -> NAVSnapshot:
        """Compute current fund NAV from all strategy engines.

        Args:
            engines: Mapping of strategy_name -> Engine instance.

        Returns:
            Frozen NAVSnapshot with current fund state.
        """
        now = time.time()
        total_realized = 0.0
        total_unrealized = 0.0
        total_positions_value = 0.0
        strategy_navs: dict[str, float] = {}

        for name, engine in engines.items():
            try:
                status = engine.status()
                realized = status.total_realized_pnl
                unrealized = status.total_unrealized_pnl
                total_realized += realized
                total_unrealized += unrealized

                # Strategy NAV = allocated capital + P&L
                allocated = self._strategy_capitals.get(name, 0.0)
                strategy_navs[name] = allocated + realized + unrealized

                # Sum position notional value
                try:
                    for pos in engine.positions():
                        total_positions_value += pos.size * pos.avg_entry_price
                except Exception:
                    pass

            except Exception as e:
                logger.debug("NAV computation error for %s: %s", name, e)
                # Use allocated capital as fallback
                allocated = self._strategy_capitals.get(name, 0.0)
                strategy_navs[name] = allocated

        total_nav = self._initial_capital + total_realized + total_unrealized

        # NaN/Inf validation
        if math.isnan(total_nav) or math.isinf(total_nav):
            logger.error("NAV computation produced invalid value: %s", total_nav)
            last = self.latest()
            if last is not None:
                return last
            total_nav = self._initial_capital

        cash = total_nav - total_positions_value

        with self._lock:
            if total_nav > self._hwm:
                self._hwm = total_nav
            hwm = self._hwm

        drawdown_pct = ((hwm - total_nav) / hwm * 100.0) if hwm > 0 else 0.0

        snapshot = NAVSnapshot(
            timestamp=now,
            total_nav=total_nav,
            cash=cash,
            positions_value=total_positions_value,
            unrealized_pnl=total_unrealized,
            realized_pnl=total_realized,
            high_water_mark=hwm,
            drawdown_pct=drawdown_pct,
            strategy_navs=strategy_navs,
        )

        with self._lock:
            self._snapshots.append(snapshot)

        return snapshot

    @property
    def high_water_mark(self) -> float:
        with self._lock:
            return self._hwm

    def history(self, limit: int = 100) -> list[NAVSnapshot]:
        """Return recent NAV snapshots."""
        with self._lock:
            return list(self._snapshots)[-limit:]

    def latest(self) -> NAVSnapshot | None:
        """Return the most recent NAV snapshot."""
        with self._lock:
            return self._snapshots[-1] if self._snapshots else None

    def persist_snapshot(self, path: str) -> None:
        """Append the latest snapshot to a JSON lines file.

        Args:
            path: File path for the JSONL output.
        """
        snap = self.latest()
        if snap is None:
            return
        record = {
            "timestamp": snap.timestamp,
            "total_nav": snap.total_nav,
            "cash": snap.cash,
            "positions_value": snap.positions_value,
            "unrealized_pnl": snap.unrealized_pnl,
            "realized_pnl": snap.realized_pnl,
            "high_water_mark": snap.high_water_mark,
            "drawdown_pct": snap.drawdown_pct,
            "strategy_navs": snap.strategy_navs,
        }
        try:
            with open(path, "a") as f:
                f.write(json.dumps(record, default=str) + "\n")
        except Exception as e:
            logger.error("Failed to persist NAV snapshot to %s: %s", path, e)
