"""
Lightweight paper trading account for strategy subprocesses.

Simulates order execution at bar-close prices without requiring Nautilus TradingNode.
Tracks positions, PnL, and trade history. All amounts are in the quote currency.
"""
from __future__ import annotations

import logging
import time
from dataclasses import dataclass, field
from typing import Optional

log = logging.getLogger(__name__)


@dataclass
class Position:
    """An open position."""
    instrument: str
    side: str          # "long" or "short"
    quantity: float
    entry_price: float
    entry_time: float  # Unix timestamp


@dataclass
class Trade:
    """A completed round-trip trade."""
    instrument: str
    side: str          # direction of the entry
    quantity: float
    entry_price: float
    exit_price: float
    entry_time: float
    exit_time: float
    pnl: float


class PaperAccount:
    """
    Simulates a cash paper account for a single strategy.

    Positions are opened/closed by calling buy(), sell(), and flatten().
    Fills execute at the provided price (e.g. bar close).
    """

    def __init__(self, initial_capital: float, feed_key: Optional[str] = None):
        self.initial_capital = initial_capital
        self.balance = initial_capital
        self._positions: dict[str, Position] = {}  # feed_key → Position
        self._trades: list[Trade] = []
        self._default_feed_key = feed_key

    # ------------------------------------------------------------------
    # Order API (mirrors PandasStrategy's order API)
    # ------------------------------------------------------------------

    def buy(self, quantity: float, price: float, feed_key: Optional[str] = None) -> None:
        """Open a long or close a short at price."""
        fk = feed_key or self._default_feed_key or "default"
        existing = self._positions.get(fk)

        if existing and existing.side == "short":
            # Close short
            pnl = (existing.entry_price - price) * existing.quantity
            self._close_position(fk, price, pnl)
        elif not existing:
            # Open long
            cost = price * quantity
            if cost > self.balance:
                quantity = self.balance / price  # size down to available capital
            if quantity > 0:
                self._positions[fk] = Position(
                    instrument=fk, side="long", quantity=quantity,
                    entry_price=price, entry_time=time.time(),
                )
                log.debug("Paper BUY %.6f @ %.2f (%s)", quantity, price, fk)

    def sell(self, quantity: float, price: float, feed_key: Optional[str] = None) -> None:
        """Open a short or close a long at price."""
        fk = feed_key or self._default_feed_key or "default"
        existing = self._positions.get(fk)

        if existing and existing.side == "long":
            # Close long
            pnl = (price - existing.entry_price) * existing.quantity
            self._close_position(fk, price, pnl)
        elif not existing:
            # Open short (using margin — simplified: require 2x capital)
            cost = price * quantity * 2
            if cost > self.balance:
                quantity = self.balance / (price * 2)
            if quantity > 0:
                self._positions[fk] = Position(
                    instrument=fk, side="short", quantity=quantity,
                    entry_price=price, entry_time=time.time(),
                )
                log.debug("Paper SELL %.6f @ %.2f (%s)", quantity, price, fk)

    def flatten(self, price: float, feed_key: Optional[str] = None) -> None:
        """Close any open position at price."""
        if feed_key:
            keys = [feed_key]
        else:
            keys = list(self._positions.keys())

        for fk in keys:
            pos = self._positions.get(fk)
            if pos is None:
                continue
            if pos.side == "long":
                pnl = (price - pos.entry_price) * pos.quantity
            else:
                pnl = (pos.entry_price - price) * pos.quantity
            self._close_position(fk, price, pnl)

    # ------------------------------------------------------------------
    # Reporting
    # ------------------------------------------------------------------

    def unrealized_pnl(self, current_prices: dict[str, float]) -> float:
        """Compute unrealized PnL using current prices."""
        total = 0.0
        for fk, pos in self._positions.items():
            price = current_prices.get(fk)
            if price is None:
                continue
            if pos.side == "long":
                total += (price - pos.entry_price) * pos.quantity
            else:
                total += (pos.entry_price - price) * pos.quantity
        return total

    def realized_pnl(self) -> float:
        return sum(t.pnl for t in self._trades)

    def total_pnl(self, current_prices: dict[str, float] | None = None) -> float:
        rpnl = self.realized_pnl()
        upnl = self.unrealized_pnl(current_prices) if current_prices else 0.0
        return rpnl + upnl

    def trade_count(self) -> int:
        return len(self._trades)

    def win_rate(self) -> float:
        if not self._trades:
            return 0.0
        wins = sum(1 for t in self._trades if t.pnl > 0)
        return wins / len(self._trades)

    def positions(self) -> dict[str, dict]:
        return {
            fk: {
                "side": p.side,
                "quantity": p.quantity,
                "entry_price": p.entry_price,
            }
            for fk, p in self._positions.items()
        }

    def recent_trades(self, n: int = 50) -> list[dict]:
        return [
            {
                "instrument": t.instrument,
                "side": t.side,
                "quantity": round(t.quantity, 8),
                "entry_price": round(t.entry_price, 8),
                "exit_price": round(t.exit_price, 8),
                "entry_time": t.entry_time,
                "exit_time": t.exit_time,
                "pnl": round(t.pnl, 6),
            }
            for t in self._trades[-n:]
        ]

    # ------------------------------------------------------------------
    # Internal
    # ------------------------------------------------------------------

    def _close_position(self, fk: str, price: float, pnl: float) -> None:
        pos = self._positions.pop(fk, None)
        if pos is None:
            return
        self.balance += pnl
        self._trades.append(Trade(
            instrument=fk,
            side=pos.side,
            quantity=pos.quantity,
            entry_price=pos.entry_price,
            exit_price=price,
            entry_time=pos.entry_time,
            exit_time=time.time(),
            pnl=pnl,
        ))
        log.debug("Paper trade closed: pnl=%.4f balance=%.2f (%s)", pnl, self.balance, fk)
