"""
=========================================
CV6 Autonomous Engine — Capital Pool
Independent capital pools per strategy.
Each maintains its own balance, P&L,
exposure and risk tracking.
=========================================
"""

import time
import uuid
from typing import Dict, List, Optional


class AutonomousPosition:
    """A position managed by the autonomous engine."""

    def __init__(
        self, symbol: str, style: str, side: str, qty: int,
        entry_price: float, stop_loss: float, target_price: float,
        broker: str, order_id: str = "", exchange: str = "NSE",
    ):
        self.id          = str(uuid.uuid4())[:8]
        self.symbol      = symbol
        self.exchange    = exchange.upper()  # NSE | BSE | MCX | NFO — drives EOD close time
        self.style       = style          # INTRADAY | SWING | FNO | SCALPING | LONG_TERM
        self.side        = side.upper()   # BUY | SELL
        self.qty         = qty
        self.entry_price = entry_price
        self.ltp         = entry_price
        self.stop_loss   = stop_loss
        self.target_price= target_price
        self.broker      = broker
        self.order_id    = order_id
        self.opened_at   = time.time()
        self.closed_at   = None
        self.exit_price  = None
        self.status      = "OPEN"         # OPEN | CLOSED | STOPPED_OUT | TARGET_HIT
        self.trail_sl    = stop_loss       # trailing stop loss
        self.highest_ltp = entry_price
        self.lowest_ltp  = entry_price
        self.ai_confidence = 0.0
        self.strategy    = ""
        self.sl_order_id = ""              # TIER-1: broker-side stop-loss order id

    @property
    def pnl(self) -> float:
        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) -> float:
        if self.entry_price == 0: return 0.0
        return (self.pnl / (self.entry_price * self.qty)) * 100

    @property
    def value(self) -> float:
        return self.entry_price * self.qty

    def to_dict(self) -> dict:
        return {
            "id": self.id, "symbol": self.symbol, "exchange": self.exchange,
            "style": self.style, "side": self.side, "qty": self.qty,
            "entry_price": round(self.entry_price, 2),
            "ltp": round(self.ltp, 2),
            "stop_loss": round(self.trail_sl, 2),
            "target": round(self.target_price, 2),
            "broker": self.broker,
            "pnl": round(self.pnl, 2),
            "pnl_pct": round(self.pnl_pct, 2),
            "status": self.status,
            "value": round(self.value, 2),
            "ai_confidence": self.ai_confidence,
            "strategy": self.strategy,
            "opened_at": self.opened_at,
        }


class CapitalPool:
    """
    Independent capital allocation for one trading style.
    Tracks its own balance, exposure, P&L, and positions.
    """

    def __init__(self, style: str, allocated_capital: float, broker: str):
        self.style              = style
        self.allocated_capital  = allocated_capital
        self.available_capital  = allocated_capital
        self.used_capital       = 0.0
        self.realized_pnl       = 0.0
        self.unrealized_pnl     = 0.0
        self.daily_trades       = 0
        self.daily_loss         = 0.0
        self.broker             = broker
        self.positions: Dict[str, AutonomousPosition] = {}  # id → position
        self._closed_positions: List[AutonomousPosition] = []

    @property
    def total_pnl(self) -> float:
        return self.realized_pnl + self.unrealized_pnl

    @property
    def open_positions(self) -> List[AutonomousPosition]:
        return [p for p in self.positions.values() if p.status == "OPEN"]

    def can_trade(self, value: float, max_daily_loss: float, max_positions: int) -> tuple[bool, str]:
        if self.available_capital < value:
            return False, f"Insufficient capital (need ₹{value:.0f}, have ₹{self.available_capital:.0f})"
        if len(self.open_positions) >= max_positions:
            return False, f"Max positions reached ({max_positions})"
        if abs(self.daily_loss) >= max_daily_loss:
            return False, f"Daily loss limit hit (₹{self.daily_loss:.0f})"
        return True, "OK"

    def add_position(self, pos: AutonomousPosition) -> None:
        self.positions[pos.id] = pos
        self.used_capital      += pos.value
        self.available_capital -= pos.value
        self.daily_trades      += 1

    def update_ltp(self, symbol: str, ltp: float) -> None:
        self.unrealized_pnl = 0.0
        for pos in self.open_positions:
            if pos.symbol == symbol:
                pos.ltp = ltp
                if pos.side == "BUY":
                    pos.highest_ltp = max(pos.highest_ltp, ltp)
                else:
                    pos.lowest_ltp  = min(pos.lowest_ltp, ltp)
            self.unrealized_pnl += pos.pnl

    def close_position(self, pos_id: str, exit_price: float, reason: str = "MANUAL") -> Optional[float]:
        pos = self.positions.get(pos_id)
        if not pos or pos.status != "OPEN":
            return None
        pos.exit_price = exit_price
        pos.ltp        = exit_price
        pos.status     = reason        # STOPPED_OUT | TARGET_HIT | MANUAL | EOD
        pos.closed_at  = time.time()
        pnl = pos.pnl
        self.realized_pnl       += pnl
        self.available_capital  += pos.value + pnl
        self.used_capital       -= pos.value
        if pnl < 0:
            self.daily_loss += abs(pnl)
        self._closed_positions.append(pos)
        del self.positions[pos_id]
        return pnl

    def to_dict(self) -> dict:
        return {
            "style":             self.style,
            "broker":            self.broker,
            "allocated_capital": round(self.allocated_capital, 2),
            "available_capital": round(self.available_capital, 2),
            "used_capital":      round(self.used_capital, 2),
            "realized_pnl":      round(self.realized_pnl, 2),
            "unrealized_pnl":    round(self.unrealized_pnl, 2),
            "total_pnl":         round(self.total_pnl, 2),
            "daily_trades":      self.daily_trades,
            "daily_loss":        round(self.daily_loss, 2),
            "open_positions":    len(self.open_positions),
            "positions":         [p.to_dict() for p in self.open_positions],
        }


class CapitalPoolManager:
    """Creates and manages all capital pools from config."""

    STYLES = ["INTRADAY", "SWING", "FNO", "SCALPING", "LONG_TERM"]

    def __init__(self):
        self.pools: Dict[str, CapitalPool] = {}

    def initialize(
        self,
        total_capital: float,
        allocation_pct: Dict[str, float],
        broker_allocation: Dict[str, str],
    ) -> None:
        self.pools.clear()
        for style in self.STYLES:
            pct    = allocation_pct.get(style, 0.0)
            alloc  = total_capital * pct / 100.0
            broker = broker_allocation.get(style, "AngelOne")
            self.pools[style] = CapitalPool(style=style, allocated_capital=alloc, broker=broker)

    def get_pool(self, style: str) -> Optional[CapitalPool]:
        return self.pools.get(style)

    def all_positions(self) -> List[AutonomousPosition]:
        out = []
        for pool in self.pools.values():
            out.extend(pool.open_positions)
        return out

    def symbol_has_open_position(self, symbol: str) -> bool:
        """
        PHASE-2 FIX (Goal 2): true "one open position per symbol" check across
        every style pool — not just a time-based cooldown. Positions are keyed
        by a random id (not symbol) so this must scan open positions directly.
        """
        return any(p.symbol == symbol for p in self.all_positions())

    def total_unrealized_pnl(self) -> float:
        return sum(p.unrealized_pnl for p in self.pools.values())

    def total_realized_pnl(self) -> float:
        return sum(p.realized_pnl for p in self.pools.values())

    def summary(self) -> List[dict]:
        return [p.to_dict() for p in self.pools.values()]

    def reset_daily(self) -> None:
        for pool in self.pools.values():
            pool.daily_trades = 0
            pool.daily_loss   = 0.0
