"""
=========================================
CV6 Autonomous Engine — Broker Allocator
Maps strategy style → correct broker.
Handles failover if primary broker fails.
=========================================
"""

from typing import Dict, Optional, Tuple

# Default fallback order if primary broker is unavailable.
# PHASE-2 FIX (Goals 5 & 6): previously included AliceBlue/Upstox/Zerodha/Groww —
# none of these have a real broker integration (AliceBlue/Upstox/Zerodha are
# unimplemented stubs; "Groww" has no broker class registered in BrokerFactory
# at all), so failover could silently "succeed" against a broker that never
# sends anything to any exchange. Only brokers with a real, working
# integration belong in this list.
_FALLBACK_ORDER = ["AngelOne", "MStock"]


class BrokerAllocator:
    """
    Routes trade execution to the correct broker based on strategy style.
    Supports automatic failover to next available broker.
    """

    def __init__(self, broker_map: Dict[str, str]):
        """
        broker_map: { style → broker_name }
        e.g. { "INTRADAY": "AngelOne", "SWING": "AliceBlue", ... }
        """
        self.broker_map = broker_map
        self._failed: set = set()     # brokers that have failed this session

    def get_broker(self, style: str) -> str:
        """Return broker name for given style."""
        primary = self.broker_map.get(style, "AngelOne")
        if primary not in self._failed:
            return primary
        # Failover
        for fallback in _FALLBACK_ORDER:
            if fallback != primary and fallback not in self._failed:
                return fallback
        # All failed — use primary anyway
        return primary

    def mark_failed(self, broker: str) -> None:
        """Mark broker as failed so failover kicks in."""
        self._failed.add(broker)

    def mark_recovered(self, broker: str) -> None:
        """Clear failure flag."""
        self._failed.discard(broker)

    def get_exchange(self, style: str) -> str:
        """Return default exchange for style."""
        if style == "FNO":
            return "NFO"
        return "NSE"

    def get_product_type(self, style: str) -> str:
        """Return product type for order based on style."""
        mapping = {
            "INTRADAY":  "INTRADAY",
            "SCALPING":  "INTRADAY",
            "SWING":     "CARRYFORWARD",
            "FNO":       "CARRYFORWARD",
            "LONG_TERM": "DELIVERY",
        }
        return mapping.get(style, "INTRADAY")

    def calculate_position_size(
        self,
        available_capital: float,
        ltp: float,
        risk_pct: float,
        stop_loss: float,
        style: str,
    ) -> Tuple[int, float]:
        """
        Calculate optimal position size based on:
        - Available capital in pool
        - Risk % per trade
        - Distance to stop loss
        Returns (qty, position_value)
        """
        if ltp <= 0 or stop_loss <= 0:
            return 1, ltp

        risk_amount  = available_capital * risk_pct / 100.0
        sl_distance  = abs(ltp - stop_loss)

        if sl_distance == 0:
            sl_distance = ltp * 0.01  # 1% fallback

        qty_by_risk = int(risk_amount / sl_distance)
        qty_by_cap  = int(available_capital * 0.3 / ltp)  # max 30% of pool per trade

        qty = max(1, min(qty_by_risk, qty_by_cap))
        value = qty * ltp

        # Cap value to 40% of available capital
        if value > available_capital * 0.4:
            qty = max(1, int(available_capital * 0.4 / ltp))
            value = qty * ltp

        return qty, value

    def calculate_targets(
        self,
        ltp: float,
        side: str,
        style: str,
        atr_pct: float = 1.5,
    ) -> Tuple[float, float]:
        """
        Calculate stop loss and target price.
        Returns (stop_loss, target_price)
        """
        # Risk/reward ratios per style
        rr_map = {
            "INTRADAY":  (1.5, 2.5),   # (sl_pct, target_pct)
            "SCALPING":  (0.5, 1.0),
            "SWING":     (2.5, 5.0),
            "FNO":       (1.5, 3.0),
            "LONG_TERM": (4.0, 10.0),
        }
        sl_pct, tp_pct = rr_map.get(style, (1.5, 3.0))

        if side == "BUY":
            sl = round(ltp * (1 - sl_pct / 100), 2)
            tp = round(ltp * (1 + tp_pct / 100), 2)
        else:
            sl = round(ltp * (1 + sl_pct / 100), 2)
            tp = round(ltp * (1 - tp_pct / 100), 2)

        return sl, tp

    def update_map(self, broker_map: Dict[str, str]) -> None:
        self.broker_map = broker_map
        self._failed.clear()
