"""
CV6 AI Trading OS — Paper Trading Engine
Full stateful paper broker: BUY, SELL, positions, PnL, cancel, history.

Fixes applied:
  BUG-1: paper_engine singleton exported (autonomous_engine.py needs it)
  BUG-2: place_order() method added (maps to execute_trade)
  BUG-3: Real LTP from live_feed / Angel One REST / yfinance fallback
  BUG-4: SL/Target auto-monitor — positions auto-close when hit
"""
import threading
import time
from typing import Dict, List, Optional

from loguru import logger

_DEFAULT_CAPITAL = 100_000.0


# ── Price fetch — real NSE prices ────────────────────────────────────────────

def _get_real_ltp(symbol: str, fallback: float = 0.0) -> float:
    """
    Get real LTP in priority order:
      1. Angel One WebSocket tick cache (live_feed)
      2. Angel One REST ltpData
      3. yfinance (delayed)
      4. Last known price (fallback)
    """
    try:
        from app.market.live_feed import live_feed
        ltp = live_feed.get_ltp(symbol)
        if ltp and ltp > 0:
            return ltp
    except Exception:
        pass

    # yfinance last resort
    try:
        import yfinance as yf
        info = yf.Ticker(f"{symbol}.NS").fast_info
        ltp = float(getattr(info, "last_price", 0) or 0)
        if ltp > 0:
            return ltp
    except Exception:
        pass

    return fallback if fallback > 0 else 1000.0


# ── Internal data classes ─────────────────────────────────────────────────────

class _Position:
    def __init__(self, symbol, side, qty, price, sl=None, target=None):
        self.symbol       = symbol
        self.side         = side.upper()
        self.qty          = qty
        self.entry_price  = price
        self.ltp          = price
        self.stop_loss    = sl
        self.target_price = target
        self.opened_at    = time.time()

    @property
    def pnl(self):
        if self.side == "BUY":
            return (self.ltp - self.entry_price) * self.qty
        return (self.entry_price - self.ltp) * self.qty

    @property
    def pnl_pct(self):
        if self.entry_price == 0:
            return 0.0
        return (self.pnl / (self.entry_price * self.qty)) * 100


class _TradeRecord:
    def __init__(self, symbol, side, qty, entry, exit_price, pnl, reason="MANUAL"):
        self.symbol = symbol
        self.side   = side
        self.qty    = qty
        self.entry  = entry
        self.exit   = exit_price
        self.pnl    = pnl
        self.reason = reason
        self.time   = time.strftime("%Y-%m-%d %H:%M:%S")


# ── Main engine ───────────────────────────────────────────────────────────────

class PaperTradingEngine:
    """
    In-memory paper trading account — singleton, persists across requests.

    BUG-1 fixed: exported as module-level `paper_engine`
    BUG-2 fixed: place_order() added
    BUG-3 fixed: real LTP from live_feed/REST/yfinance
    BUG-4 fixed: background SL/target monitor thread
    """

    def __init__(self, initial_capital: float = _DEFAULT_CAPITAL):
        self._total_capital = initial_capital
        self._available     = initial_capital
        self._positions:    Dict[str, _Position]   = {}
        self._history:      List[_TradeRecord]      = []
        self._win = self._loss = 0
        self._lock = threading.Lock()   # thread-safe (sync, not asyncio)

        # Start SL/target background monitor
        self._monitor_thread = threading.Thread(
            target=self._sl_target_monitor,
            name="paper-sl-monitor",
            daemon=True,
        )
        self._monitor_thread.start()

    # ── BUG-3 FIX: Real LTP ──────────────────────────────────────────────────

    def _refresh_ltps(self):
        """Update all open positions with real market prices."""
        for pos in list(self._positions.values()):
            real = _get_real_ltp(pos.symbol, pos.ltp)
            pos.ltp = real

    # ── BUG-4 FIX: SL / Target auto-monitor ─────────────────────────────────

    def _sl_target_monitor(self):
        """
        Background thread — runs every 15 seconds.
        Checks every open position against its SL and target.
        Auto-closes if hit.
        """
        while True:
            try:
                time.sleep(15)
                with self._lock:
                    to_close = []
                    for sym, pos in self._positions.items():
                        real = _get_real_ltp(sym, pos.ltp)
                        pos.ltp = real
                        reason = None

                        if pos.side == "BUY":
                            if pos.stop_loss and real <= pos.stop_loss:
                                reason = "SL_HIT"
                            elif pos.target_price and real >= pos.target_price:
                                reason = "TARGET_HIT"
                        else:  # SELL (short)
                            if pos.stop_loss and real >= pos.stop_loss:
                                reason = "SL_HIT"
                            elif pos.target_price and real <= pos.target_price:
                                reason = "TARGET_HIT"

                        if reason:
                            to_close.append((sym, real, reason))

                    for sym, exit_price, reason in to_close:
                        self._close_position(sym, exit_price, reason)

            except Exception as exc:
                logger.debug(f"[PaperEngine] SL monitor error: {exc}")

    def _close_position(self, symbol: str, exit_price: float, reason: str = "MANUAL"):
        """Internal: close position and record trade. Must be called under lock."""
        pos = self._positions.pop(symbol, None)
        if not pos:
            return None

        pnl = round((exit_price - pos.entry_price) * pos.qty, 2) \
              if pos.side == "BUY" \
              else round((pos.entry_price - exit_price) * pos.qty, 2)

        self._available += exit_price * pos.qty
        self._history.append(_TradeRecord(
            symbol, "BUY→SELL", pos.qty, pos.entry_price, exit_price, pnl, reason
        ))
        if pnl >= 0:
            self._win += 1
        else:
            self._loss += 1

        logger.info(
            f"[PaperEngine] {reason}: {symbol} "
            f"entry=₹{pos.entry_price:.2f} exit=₹{exit_price:.2f} "
            f"P&L=₹{pnl:+.2f}"
        )
        return pnl

    # ── Capital setup ─────────────────────────────────────────────────────────

    def set_capital(self, capital: float):
        with self._lock:
            self._total_capital = capital
            self._available     = capital
            self._positions.clear()
            self._history.clear()
            self._win = self._loss = 0

    # ── execute_trade (used by paper_router) ──────────────────────────────────

    def execute_trade(
        self,
        capital:      Optional[float],
        symbol:       str,
        action:       str,
        quantity:     int,
        entry_price:  float,
        stop_loss:    Optional[float] = None,
        target_price: Optional[float] = None,
    ) -> dict:
        action = action.upper()
        symbol = symbol.upper()

        with self._lock:
            # One-time capital setup
            if capital and not self._positions and not self._history:
                self._total_capital = capital
                self._available     = capital

            # Use real LTP if entry_price not supplied
            if not entry_price or entry_price <= 0:
                entry_price = _get_real_ltp(symbol)

            invested = round(quantity * entry_price, 2)
            pnl = 0.0
            buy_price = exit_price = exit_qty = 0.0

            if action == "BUY":
                if invested > self._available:
                    return {
                        "success": False,
                        "message": f"Insufficient capital (need ₹{invested:,.0f}, have ₹{self._available:,.0f})",
                        "symbol": symbol, "action": action, "quantity": quantity,
                        "entry_price": entry_price, "invested_amount": invested,
                        "remaining_capital": self._available,
                        "total_capital": self._total_capital,
                        "open_positions": len(self._positions),
                        "realized_pnl": self._realized_pnl(),
                        "unrealized_pnl": self._unrealized_pnl(),
                    }
                if symbol in self._positions:
                    pos = self._positions[symbol]
                    total_qty = pos.qty + quantity
                    pos.entry_price = (pos.entry_price * pos.qty + entry_price * quantity) / total_qty
                    pos.qty = total_qty
                    if stop_loss:    pos.stop_loss    = stop_loss
                    if target_price: pos.target_price = target_price
                else:
                    self._positions[symbol] = _Position(
                        symbol, "BUY", quantity, entry_price, stop_loss, target_price
                    )
                self._available -= invested

            elif action == "SELL":
                if symbol not in self._positions:
                    return {
                        "success": False,
                        "message": f"No open BUY position for {symbol}. Cannot SELL.",
                        "symbol": symbol, "action": action, "quantity": quantity,
                        "entry_price": entry_price, "invested_amount": 0,
                        "remaining_capital": round(self._available, 2),
                        "total_capital": self._total_capital,
                        "open_positions": len(self._positions),
                        "realized_pnl": self._realized_pnl(),
                        "unrealized_pnl": self._unrealized_pnl(),
                    }
                pos      = self._positions[symbol]
                exit_qty = min(quantity, pos.qty)
                exit_price = entry_price or _get_real_ltp(symbol, pos.ltp)
                buy_price  = pos.entry_price
                pnl        = round((exit_price - pos.entry_price) * exit_qty, 2)
                self._available += exit_qty * exit_price
                self._history.append(_TradeRecord(
                    symbol, "BUY→SELL", exit_qty, pos.entry_price, exit_price, pnl
                ))
                if pnl >= 0: self._win += 1
                else:        self._loss += 1
                if exit_qty >= pos.qty:
                    del self._positions[symbol]
                else:
                    pos.qty -= exit_qty

            # Refresh LTPs after trade
            self._refresh_ltps()

            result = {
                "success": True,
                "message": f"Paper {action}: {quantity} {symbol} @ ₹{entry_price:.2f}",
                "symbol": symbol, "action": action, "quantity": quantity,
                "entry_price": entry_price, "invested_amount": invested,
                "remaining_capital": round(self._available, 2),
                "total_capital": self._total_capital,
                "open_positions": len(self._positions),
                "realized_pnl": self._realized_pnl(),
                "unrealized_pnl": self._unrealized_pnl(),
            }
            if action == "SELL":
                result["_sell_pnl"]      = pnl
                result["_buy_price"]     = buy_price
                result["_exit_price"]    = exit_price
                result["_exit_quantity"] = exit_qty
            return result

    # ── BUG-2 FIX: place_order() — used by autonomous_engine ─────────────────

    def place_order(
        self,
        symbol:     str,
        side:       str,
        qty:        int,
        price:      float,
        order_type: str = "MARKET",
        stop_loss:  Optional[float] = None,
        target:     Optional[float] = None,
    ) -> dict:
        """
        Drop-in replacement for broker.place_order().
        Called by autonomous_engine._place_order() in PAPER mode.
        Returns dict with order_id on success.
        """
        result = self.execute_trade(
            capital      = None,
            symbol       = symbol,
            action       = side,          # BUY or SELL
            quantity     = qty,
            entry_price  = price,
            stop_loss    = stop_loss,
            target_price = target,
        )
        order_id = f"PAPER-{symbol}-{int(time.time())}"
        return {
            "success":  result.get("success", False),
            "order_id": order_id if result.get("success") else "",
            "message":  result.get("message", ""),
        }

    # ── cancel ────────────────────────────────────────────────────────────────

    def cancel_position(self, symbol: str) -> dict:
        symbol = symbol.upper()
        with self._lock:
            if symbol not in self._positions:
                return {"success": False, "message": f"No open position for {symbol}"}
            pos = self._positions.pop(symbol)
            refund = pos.entry_price * pos.qty
            self._available += refund
        return {"success": True, "message": f"Position {symbol} cancelled at entry price"}

    # ── account snapshot ──────────────────────────────────────────────────────

    def account(self) -> dict:
        with self._lock:
            self._refresh_ltps()
            positions = [
                {
                    "symbol":       p.symbol,
                    "side":         p.side,
                    "quantity":     p.qty,
                    "entry_price":  round(p.entry_price, 2),
                    "ltp":          round(p.ltp, 2),
                    "pnl":          round(p.pnl, 2),
                    "pnl_pct":      round(p.pnl_pct, 2),
                    "stop_loss":    p.stop_loss,
                    "target_price": p.target_price,
                }
                for p in self._positions.values()
            ]
            invested     = sum(p.entry_price * p.qty for p in self._positions.values())
            total_trades = self._win + self._loss
            return {
                "total_capital":     self._total_capital,
                "available_capital": round(self._available, 2),
                "invested_amount":   round(invested, 2),
                "realized_pnl":      self._realized_pnl(),
                "unrealized_pnl":    self._unrealized_pnl(),
                "total_pnl":         round(self._realized_pnl() + self._unrealized_pnl(), 2),
                "open_positions":    positions,
                "trade_count":       total_trades,
                "win_count":         self._win,
                "loss_count":        self._loss,
                "win_rate":          round(self._win / total_trades * 100, 1) if total_trades else 0.0,
            }

    def history(self) -> list:
        with self._lock:
            return [
                {
                    "symbol": t.symbol, "side": t.side, "qty": t.qty,
                    "entry":  t.entry,  "exit": t.exit,
                    "pnl":    round(t.pnl, 2),
                    "reason": t.reason,
                    "time":   t.time,
                }
                for t in reversed(self._history)
            ]

    def reset(self, capital: float = _DEFAULT_CAPITAL):
        self.set_capital(capital)
        return {"success": True, "message": f"Paper account reset. Capital: ₹{capital:,.0f}"}

    def _realized_pnl(self) -> float:
        return round(sum(t.pnl for t in self._history), 2)

    def _unrealized_pnl(self) -> float:
        return round(sum(p.pnl for p in self._positions.values()), 2)


# ── BUG-1 FIX: Singleton exported for autonomous_engine.py ───────────────────
paper_engine = PaperTradingEngine(initial_capital=_DEFAULT_CAPITAL)
