"""
=========================================
CV6 Autonomous Engine — Position Monitor
Monitors open positions every cycle.
Moves stop loss. Trails profits.
Exits on SL hit, target hit, or EOD.

EOD close rules:
  - NSE/BSE/NFO intraday (MIS): close by trading_end (default 15:10)
  - MCX commodity futures:       close by commodity_trading_end (default 23:00)
=========================================
"""

import time
from datetime import datetime
from typing import List, Tuple

from app.autonomous.capital_pool import AutonomousPosition, CapitalPool


class PositionMonitor:
    """
    Monitors all open positions per pool.
    Applies:
      - Stop loss exit
      - Target price exit
      - Trailing stop loss (moves SL up as price rises for BUY)
      - EOD exit: NSE intraday by trading_end, MCX by commodity_trading_end
    """

    # Trail SL by this fraction of ATR-equivalent (% of price)
    TRAIL_PCT = 0.005   # 0.5% trail step

    def monitor_pool(
        self,
        pool: CapitalPool,
        price_map: dict,          # symbol → ltp
        trading_end_str: str,
        commodity_trading_end_str: str = "23:00",
    ) -> List[dict]:
        """
        Check all open positions in a pool.
        Returns list of exit actions taken.
        """
        actions = []
        now   = datetime.now()
        eod_nse = self._is_eod(now, trading_end_str)
        eod_mcx = self._is_eod(now, commodity_trading_end_str)
        intraday_styles = {"INTRADAY", "SCALPING"}

        to_close = []   # (pos, exit_price, reason)

        for pos in list(pool.open_positions):
            ltp = price_map.get(pos.symbol)
            if ltp is None:
                continue

            # Update pool LTP
            pool.update_ltp(pos.symbol, ltp)

            # ── EOD square-off ───────────────────────────────────────────
            # MCX commodity futures: close at commodity_trading_end (23:00)
            exchange = getattr(pos, "exchange", "NSE")
            if exchange == "MCX" and eod_mcx:
                to_close.append((pos, ltp, "EOD_MCX"))
                continue
            # NSE/BSE intraday (MIS): close at trading_end (15:10)
            if exchange != "MCX" and eod_nse and pool.style in intraday_styles:
                to_close.append((pos, ltp, "EOD"))
                continue

            # ── Stop Loss check ──────────────────────────────────────────
            if pos.side == "BUY" and ltp <= pos.trail_sl:
                to_close.append((pos, ltp, "STOPPED_OUT"))
                continue
            if pos.side == "SELL" and ltp >= pos.trail_sl:
                to_close.append((pos, ltp, "STOPPED_OUT"))
                continue

            # ── Target check ─────────────────────────────────────────────
            if pos.side == "BUY" and ltp >= pos.target_price:
                to_close.append((pos, ltp, "TARGET_HIT"))
                continue
            if pos.side == "SELL" and ltp <= pos.target_price:
                to_close.append((pos, ltp, "TARGET_HIT"))
                continue

            # ── Trail stop loss ──────────────────────────────────────────
            if pos.side == "BUY":
                trail_price = ltp * (1 - self.TRAIL_PCT)
                if trail_price > pos.trail_sl:
                    pos.trail_sl = round(trail_price, 2)
            else:
                trail_price = ltp * (1 + self.TRAIL_PCT)
                if trail_price < pos.trail_sl:
                    pos.trail_sl = round(trail_price, 2)

        # Execute closes — capture position fields BEFORE close_position() pops it
        for pos, exit_price, reason in to_close:
            entry_price = pos.entry_price
            action = {
                "action":      "CLOSE",
                "pos_id":      pos.id,
                "symbol":      pos.symbol,
                "reason":      reason,
                "exit_price":  exit_price,
                "entry_price": entry_price,
                "side":        pos.side,
                "qty":         pos.qty,
                "broker":      pos.broker,
                "exchange":    getattr(pos, "exchange", "NSE"),
                "style":       pool.style,
                "strategy":    getattr(pos, "strategy", ""),
                "sl_order_id": getattr(pos, "sl_order_id", ""),
                "pool":        pool.style,
            }
            pnl = pool.close_position(pos.id, exit_price, reason)
            action["pnl"]     = round(pnl, 2) if pnl is not None else 0
            action["pnl_pct"] = round((pnl / (entry_price * pos.qty) * 100), 2) if (pnl and entry_price and pos.qty) else 0.0
            actions.append(action)

        return actions

    def _is_eod(self, now: datetime, trading_end_str: str) -> bool:
        """Return True if within 5 minutes of EOD."""
        try:
            h, m = map(int, trading_end_str.split(":"))
            end = now.replace(hour=h, minute=m, second=0)
            diff = (end - now).total_seconds()
            return diff <= 300  # 5 minutes before close
        except Exception:
            return False


# Singleton
position_monitor = PositionMonitor()
