"""
Live Trading Strategy Instance for BrighterTrading.

Extends StrategyInstance with live trading capabilities using
the LiveBroker for real order execution.

WARNING: This module executes real trades with real money when not in testnet mode.
"""

import logging
from typing import Any, Optional
import datetime as dt

from StrategyInstance import StrategyInstance
from brokers import LiveBroker, OrderSide, OrderType

logger = logging.getLogger(__name__)


class LiveStrategyInstance(StrategyInstance):
    """
    Strategy instance for live trading mode.

    Uses LiveBroker for real order execution via exchange APIs.
    Includes safety features:
    - Circuit breaker to halt trading on excessive drawdown
    - Position limits to prevent over-exposure
    - Restart-safe order reconciliation
    """

    def __init__(
        self,
        strategy_instance_id: str,
        strategy_id: str,
        strategy_name: str,
        user_id: int,
        generated_code: str,
        data_cache: Any,
        indicators: Any | None,
        trades: Any | None,
        exchange: Any,
        testnet: bool = True,
        initial_balance: float = 0.0,
        commission: float = 0.001,
        slippage: float = 0.0,
        max_position_pct: float = 0.5,
        circuit_breaker_pct: float = -0.10,
        rate_limit: float = 2.0,
        edm_client: Any = None,
        indicator_owner_id: int = None,
    ):
        """
        Initialize the LiveStrategyInstance.

        :param strategy_instance_id: Unique identifier for this instance.
        :param strategy_id: Strategy identifier.
        :param strategy_name: Strategy name.
        :param user_id: User identifier.
        :param generated_code: Python code generated from Blockly.
        :param data_cache: DataCache instance.
        :param indicators: Indicators manager.
        :param trades: Trades manager (not used directly, kept for compatibility).
        :param exchange: Exchange instance for API access.
        :param testnet: Use testnet (default True for safety).
        :param initial_balance: Starting balance (will be synced from exchange).
        :param commission: Commission rate.
        :param slippage: Slippage rate (not typically used for live).
        :param max_position_pct: Maximum position size as percentage of balance (0.5 = 50%).
        :param circuit_breaker_pct: Drawdown percentage to trigger circuit breaker (-0.10 = -10%).
        :param rate_limit: API calls per second limit.
        :param indicator_owner_id: For subscribed strategies, the creator's user ID for indicator lookup.
        """
        # Safety checks
        if not testnet:
            logger.warning(
                "LiveStrategyInstance initialized for PRODUCTION trading! "
                "Real money will be used. Ensure thorough testing first."
            )
        else:
            logger.info("LiveStrategyInstance initialized in TESTNET mode.")

        # Initialize the live broker
        self.live_broker = LiveBroker(
            exchange=exchange,
            testnet=testnet,
            initial_balance=initial_balance,
            commission=commission,
            slippage=slippage,
            data_cache=data_cache,
            rate_limit=rate_limit
        )

        # Set broker before calling parent __init__
        self.broker = self.live_broker

        # Safety parameters
        self._testnet = testnet
        self._exchange = exchange
        self._max_position_pct = max_position_pct
        self._circuit_breaker_pct = circuit_breaker_pct
        self._circuit_breaker_tripped = False
        self._circuit_breaker_reason = ""

        # Initialize parent (will call _initialize_or_load_context)
        super().__init__(
            strategy_instance_id, strategy_id, strategy_name, user_id,
            generated_code, data_cache, indicators, trades, edm_client,
            indicator_owner_id=indicator_owner_id
        )

        # Connect to exchange and sync state
        if not self.live_broker.connect():
            logger.error("Failed to connect to exchange!")
            raise RuntimeError("Failed to connect to exchange")

        # Get starting balance from exchange
        self.starting_balance = self.live_broker.get_balance()
        self.current_balance = self.starting_balance
        self.available_balance = self.live_broker.get_available_balance()
        self.available_strategy_balance = self.starting_balance

        # Get total equity for circuit breaker (includes spot holdings value)
        self.starting_equity = self.live_broker.get_total_equity()
        self.current_equity = self.starting_equity

        # Update exec_context with balance attributes
        self.exec_context['starting_balance'] = self.starting_balance
        self.exec_context['current_balance'] = self.current_balance
        self.exec_context['available_balance'] = self.available_balance
        self.exec_context['available_strategy_balance'] = self.available_strategy_balance
        self.exec_context['starting_equity'] = self.starting_equity
        self.exec_context['current_equity'] = self.current_equity

        # Load persisted broker state and reconcile with exchange
        if self.live_broker.load_state(self.strategy_instance_id):
            reconcile_result = self.live_broker.reconcile_with_exchange()
            if reconcile_result.get('success'):
                logger.info("LiveStrategyInstance state reconciled with exchange")
            self._update_balances()
        else:
            logger.info(f"LiveStrategyInstance created with balance: {self.starting_balance}")

    def _check_circuit_breaker(self) -> bool:
        """
        Check if circuit breaker should be tripped.

        Halts trading if drawdown exceeds the configured threshold.
        Uses total equity (including spot holdings) for accurate drawdown calculation.

        :return: True if circuit breaker is tripped, False otherwise.
        """
        if self._circuit_breaker_tripped:
            return True

        if self.starting_equity <= 0:
            return False

        self._update_balances()
        drawdown_pct = (self.current_equity - self.starting_equity) / self.starting_equity

        if drawdown_pct < self._circuit_breaker_pct:
            self._circuit_breaker_tripped = True
            self._circuit_breaker_reason = (
                f"Drawdown {drawdown_pct:.2%} exceeded threshold {self._circuit_breaker_pct:.2%}"
            )
            logger.warning(
                f"CIRCUIT BREAKER TRIPPED for strategy {self.strategy_id}: "
                f"{self._circuit_breaker_reason}"
            )
            self.notify_user(f"Trading halted: {self._circuit_breaker_reason}")
            return True

        return False

    def _check_position_limit(self, size: float, price: float, symbol: str) -> bool:
        """
        Check if order would exceed position limits.

        :param size: Order size.
        :param price: Order price.
        :param symbol: Trading symbol.
        :return: True if order is within limits, False otherwise.
        """
        if self.starting_balance <= 0:
            return True

        order_value = size * price
        max_order_value = self.starting_balance * self._max_position_pct

        if order_value > max_order_value:
            logger.warning(
                f"Order rejected: value {order_value:.2f} exceeds position limit "
                f"{max_order_value:.2f} ({self._max_position_pct:.0%} of {self.starting_balance:.2f})"
            )
            self.notify_user(
                f"Order rejected: size exceeds {self._max_position_pct:.0%} position limit"
            )
            return False

        # Also check against available balance
        available = self.live_broker.get_available_balance()
        if order_value > available:
            logger.warning(
                f"Order rejected: value {order_value:.2f} exceeds available balance {available:.2f}"
            )
            self.notify_user("Order rejected: insufficient available balance")
            return False

        return True

    def trade_order(
        self,
        trade_type: str,
        size: float,
        order_type: str,
        source: dict = None,
        tif: str = 'GTC',
        stop_loss: dict = None,
        trailing_stop: dict = None,
        take_profit: dict = None,
        limit: dict = None,
        trailing_limit: dict = None,
        target_market: dict = None,
        name_order: dict = None
    ):
        """
        Place an order via the live broker with safety checks.

        :param trade_type: 'buy' or 'sell'.
        :param size: Order size.
        :param order_type: 'MARKET' or 'LIMIT'.
        :param source: Dict with 'symbol' key.
        :param tif: Time in force ('GTC', 'IOC', 'FOK').
        :param stop_loss: Stop loss configuration.
        :param take_profit: Take profit configuration.
        :param limit: Limit price configuration.
        :return: OrderResult or None if rejected.
        """
        # Check circuit breaker first
        if self._check_circuit_breaker():
            logger.warning("Order rejected: circuit breaker is tripped")
            return None

        # Extract symbol
        symbol = 'BTC/USDT'
        if source:
            symbol = source.get('symbol') or source.get('market', 'BTC/USDT')

        # Map trade_type to OrderSide
        if trade_type.lower() == 'buy':
            side = OrderSide.BUY
        elif trade_type.lower() == 'sell':
            side = OrderSide.SELL
        else:
            logger.error(f"Invalid trade_type '{trade_type}'. Order not executed.")
            return None

        # Map order_type to OrderType and get price
        order_type_upper = order_type.upper()
        if order_type_upper == 'MARKET':
            bt_order_type = OrderType.MARKET
            price = self.get_current_price(symbol=symbol)
        elif order_type_upper == 'LIMIT':
            bt_order_type = OrderType.LIMIT
            price = limit.get('value') if limit else self.get_current_price(symbol=symbol)
        else:
            bt_order_type = OrderType.MARKET
            price = self.get_current_price(symbol=symbol)

        if price is None or price <= 0:
            logger.error(f"Cannot get price for {symbol}")
            return None

        # Check position limit (for buy orders)
        if side == OrderSide.BUY:
            if not self._check_position_limit(size, price, symbol):
                return None

        # Extract stop loss and take profit prices
        stop_loss_price = stop_loss.get('value') if stop_loss else None
        take_profit_price = take_profit.get('value') if take_profit else None

        # Place the order
        logger.info(f"Placing LIVE order: {trade_type.upper()} {size} {symbol} @ {order_type_upper}")

        result = self.live_broker.place_order(
            symbol=symbol,
            side=side,
            order_type=bt_order_type,
            size=size,
            price=price if bt_order_type == OrderType.LIMIT else None,
            stop_loss=stop_loss_price,
            take_profit=take_profit_price,
            time_in_force=tif
        )

        if result.success:
            message = (
                f"LIVE {trade_type.upper()} order placed: {size} {symbol} @ {order_type_upper}"
            )
            self.notify_user(message)
            logger.info(message)

            # Track order in history
            self.orders.append({
                'order_id': result.order_id,
                'symbol': symbol,
                'side': trade_type,
                'size': size,
                'type': order_type,
                'status': result.status.value,
                'filled_qty': result.filled_qty,
                'filled_price': result.filled_price,
                'timestamp': dt.datetime.now().isoformat()
            })

            # Update balances after order
            self._update_balances()
        else:
            logger.warning(f"Order failed: {result.message}")
            self.notify_user(f"Order failed: {result.message}")

        return result

    def tick(self, candle_data: dict = None) -> list:
        """
        Process one iteration of the live trading strategy.

        :param candle_data: Optional candle data dict.
        :return: List of events.
        """
        events = []

        # Check circuit breaker first
        if self._check_circuit_breaker():
            return [{
                'type': 'circuit_breaker',
                'strategy_id': self.strategy_id,
                'reason': self._circuit_breaker_reason
            }]

        # Skip if paused or exiting
        if self.paused:
            return [{'type': 'skipped', 'reason': 'paused'}]

        if self.exit:
            return [{'type': 'skipped', 'reason': 'exiting'}]

        try:
            # Update prices and check for fills
            if candle_data:
                symbol = candle_data.get('symbol', 'BTC/USDT')
                price = float(candle_data.get('close', 0))

                if price > 0:
                    # Update exec context with current data
                    self.exec_context['current_candle'] = candle_data
                    self.exec_context['current_price'] = price
                    self.exec_context['current_symbol'] = symbol

            # Process broker updates (check for fills, update positions)
            broker_events = self.live_broker.update()
            for event in broker_events:
                if event['type'] == 'fill':
                    self.trade_history.append(event)
                    events.append({
                        'type': 'order_filled',
                        'order_id': event.get('order_id'),
                        'exchange_order_id': event.get('exchange_order_id'),
                        'symbol': event.get('symbol'),
                        'side': event.get('side'),
                        'filled_qty': event.get('filled_qty'),
                        'filled_price': event.get('filled_price'),
                        'commission': event.get('commission'),
                    })
                    logger.info(f"Order filled: {event}")

                    # Accumulate strategy fees for paid strategies
                    # Fee is charged on profitable sell orders (closing positions)
                    if event.get('side') == 'sell':
                        filled_qty = event.get('filled_qty', 0)
                        filled_price = event.get('filled_price', 0)
                        trade_value = filled_qty * filled_price
                        commission_rate = self.live_broker._commission
                        # Use actual profitability from broker (based on realized PnL)
                        is_profitable = event.get('is_profitable', False)
                        self.accumulate_trade_fee(
                            trade_value_usd=trade_value,
                            commission_rate=commission_rate,
                            is_profitable=is_profitable
                        )

            # Update balance attributes
            self._update_balances()

            # Execute strategy logic
            result = self.execute()

            if result.get('success'):
                events.append({
                    'type': 'tick_complete',
                    'strategy_id': self.strategy_id,
                    'balance': self.current_balance,
                    'available_balance': self.available_balance,
                    'positions': len(self.live_broker.get_all_positions()),
                    'open_orders': len(self.live_broker.get_open_orders()),
                    'trades': len(self.trade_history),
                    'testnet': self._testnet,
                })
            else:
                events.append({
                    'type': 'error',
                    'strategy_id': self.strategy_id,
                    'message': result.get('message', 'Unknown error'),
                })

        except Exception as e:
            logger.error(f"Error in live strategy tick: {e}", exc_info=True)
            events.append({
                'type': 'error',
                'strategy_id': self.strategy_id,
                'message': str(e),
            })

        return events

    def _update_balances(self):
        """Update balance attributes from live broker."""
        try:
            self.current_balance = self.live_broker.get_balance()
            self.available_balance = self.live_broker.get_available_balance()
            self.current_equity = self.live_broker.get_total_equity()

            self.exec_context['current_balance'] = self.current_balance
            self.exec_context['available_balance'] = self.available_balance
            self.exec_context['current_equity'] = self.current_equity
        except Exception as e:
            logger.warning(f"Error updating balances: {e}")

    def get_current_price(self, timeframe: str = '1h', exchange: str = 'binance',
                          symbol: str = 'BTC/USDT') -> float:
        """Get current price from live broker."""
        try:
            return self.live_broker.get_current_price(symbol)
        except Exception as e:
            logger.warning(f"Error getting price for {symbol}: {e}")
            return 0.0

    def get_available_balance(self) -> float:
        """Get available balance from live broker."""
        self.available_balance = self.live_broker.get_available_balance()
        self.exec_context['available_balance'] = self.available_balance
        return self.available_balance

    def get_current_balance(self) -> float:
        """Get current total balance from live broker."""
        self.current_balance = self.live_broker.get_balance()
        self.exec_context['current_balance'] = self.current_balance
        return self.current_balance

    def get_starting_balance(self) -> float:
        """Get starting balance."""
        return self.starting_balance

    def get_active_trades(self) -> int:
        """Get number of active positions."""
        return len(self.live_broker.get_all_positions())

    def get_filled_orders(self) -> int:
        """Get number of filled orders."""
        from brokers.base_broker import OrderStatus
        return len([o for o in self.live_broker._orders.values()
                   if o.status == OrderStatus.FILLED])

    def get_position(self, symbol: str):
        """Get position for a symbol."""
        return self.live_broker.get_position(symbol)

    def close_position(self, symbol: str):
        """Close a position."""
        if self._circuit_breaker_tripped:
            logger.warning("Cannot close position: circuit breaker is tripped")
            return None
        return self.live_broker.close_position(symbol)

    def close_all_positions(self):
        """Close all positions (allowed even with circuit breaker)."""
        return self.live_broker.close_all_positions()

    def get_trade_history(self):
        """Get all executed trades."""
        return self.trade_history.copy()

    def reset_circuit_breaker(self):
        """
        Reset the circuit breaker (should be called manually after investigation).

        WARNING: Use with caution - ensure the cause of the drawdown is understood.
        """
        if self._circuit_breaker_tripped:
            logger.warning(
                f"Circuit breaker reset for strategy {self.strategy_id}. "
                f"Previous reason: {self._circuit_breaker_reason}"
            )
            self._circuit_breaker_tripped = False
            self._circuit_breaker_reason = ""
            self.notify_user("Circuit breaker reset - trading can resume")

    def save_context(self):
        """Save strategy context including live broker state."""
        self._update_balances()
        # Save live broker state
        self.live_broker.save_state(self.strategy_instance_id)
        super().save_context()

    def disconnect(self):
        """Disconnect from the exchange."""
        self.live_broker.disconnect()
        logger.info(f"LiveStrategyInstance {self.strategy_id} disconnected")

    def notify_user(self, message: str):
        """Send notification to user."""
        mode = "TESTNET" if self._testnet else "LIVE"
        logger.info(f"[{mode}] {message}")
        # Could emit via SocketIO if available

    @property
    def is_testnet(self) -> bool:
        """Return whether running in testnet mode."""
        return self._testnet

    @property
    def circuit_breaker_status(self) -> dict:
        """Get circuit breaker status."""
        return {
            'tripped': self._circuit_breaker_tripped,
            'reason': self._circuit_breaker_reason,
            'threshold': self._circuit_breaker_pct,
            'current_drawdown': (
                (self.current_equity - self.starting_equity) / self.starting_equity
                if self.starting_equity > 0 else 0
            )
        }
