"""
Paper Trading Strategy Instance for BrighterTrading.

Extends StrategyInstance with paper trading capabilities using
the PaperBroker for simulated order execution.
"""

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

from StrategyInstance import StrategyInstance
from brokers import PaperBroker, OrderSide, OrderType

logger = logging.getLogger(__name__)


class PaperStrategyInstance(StrategyInstance):
    """
    Strategy instance for paper trading mode.

    Uses PaperBroker for simulated order execution with live price data.
    Maintains full strategy execution context while simulating trades.
    """

    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,
        initial_balance: float = 10000.0,
        commission: float = 0.001,
        slippage: float = 0.0005,
        price_provider: Any = None,
        edm_client: Any = None,
        indicator_owner_id: int = None,
    ):
        """
        Initialize the PaperStrategyInstance.

        :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 initial_balance: Starting paper trading balance.
        :param commission: Commission rate for paper trades.
        :param slippage: Slippage rate for market orders.
        :param price_provider: Callable to get current prices.
        :param indicator_owner_id: For subscribed strategies, the creator's user ID for indicator lookup.
        """
        # Initialize the paper broker
        self.paper_broker = PaperBroker(
            initial_balance=initial_balance,
            commission=commission,
            slippage=slippage,
            price_provider=price_provider,
            data_cache=data_cache
        )

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

        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
        )

        # Initialize balance attributes from paper broker
        self.starting_balance = initial_balance
        self.current_balance = initial_balance
        self.available_balance = initial_balance
        self.available_strategy_balance = initial_balance

        # 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

        # Try to load persisted broker state
        if self.paper_broker.load_state(self.strategy_instance_id):
            # Update balance attributes from restored broker state
            self._update_balances()
            logger.info(f"PaperStrategyInstance restored with balance: {self.current_balance}")
        else:
            logger.info(f"PaperStrategyInstance created with balance: {initial_balance}")

    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 paper broker.

        This method translates the Blockly-generated order call to
        the PaperBroker interface.
        """
        logger.info(f"trade_order called: type={trade_type}, size={size}, order_type={order_type}")
        logger.info(f"  stop_loss={stop_loss}, take_profit={take_profit}, source={source}")

        # Extract symbol from source
        symbol = 'BTC/USDT'  # Default
        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

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

        # Extract stop loss and take profit
        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
        result = self.paper_broker.place_order(
            symbol=symbol,
            side=side,
            order_type=bt_order_type,
            size=size,
            price=price,
            stop_loss=stop_loss_price,
            take_profit=take_profit_price,
            time_in_force=tif
        )

        if result.success:
            message = f"{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,
                'timestamp': dt.datetime.now().isoformat()
            })
        else:
            logger.warning(f"Order failed: {result.message}")
            self.notify_user(f"Order failed: {result.message}")

        return result

    def update_prices(self, price_data: dict):
        """
        Update current prices in the paper broker.

        :param price_data: Dict mapping symbols to prices.
        """
        for symbol, price in price_data.items():
            self.paper_broker.update_price(symbol, price)

        # Process any pending orders
        events = self.paper_broker.update()
        for event in events:
            if event['type'] == 'fill':
                self.trade_history.append(event)
                logger.info(f"Order filled: {event}")

        # Update balance attributes
        self._update_balances()

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

        Overrides base tick to handle paper broker specifics.

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

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

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

        try:
            # Update prices first if candle data provided
            if candle_data:
                symbol = candle_data.get('symbol', 'BTC/USDT')
                price = float(candle_data.get('close', 0))
                if price > 0:
                    self.paper_broker.update_price(symbol, price)

                    # Process pending orders after price update
                    broker_events = self.paper_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'),
                                'symbol': event.get('symbol'),
                                'filled_qty': event.get('filled_qty', event.get('size')),
                                'filled_price': event.get('filled_price', event.get('price')),
                            })

                            # Accumulate strategy fees for paid strategies
                            # Fee is charged on profitable sells (closing a position)
                            if event.get('side') == 'sell':
                                trade_value = event.get('filled_qty', 0) * event.get('filled_price', 0)
                                commission_rate = self.paper_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 exec context with current data
                self.exec_context['current_candle'] = candle_data
                self.exec_context['current_price'] = price
                self.exec_context['current_symbol'] = symbol

            # Update balance attributes before execution
            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.paper_broker.get_all_positions()),
                    'trades': len(self.trade_history),
                })
            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 paper 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 paper broker."""
        self.current_balance = self.paper_broker.get_balance()
        self.available_balance = self.paper_broker.get_available_balance()

        self.exec_context['current_balance'] = self.current_balance
        self.exec_context['available_balance'] = self.available_balance

    def get_current_price(self, timeframe: str = '1h', exchange: str = 'binance',
                          symbol: str = 'BTC/USDT') -> float:
        """Get current price from paper broker."""
        return self.paper_broker.get_current_price(symbol)

    def get_available_balance(self) -> float:
        """Get available cash balance."""
        self.available_balance = self.paper_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 including unrealized P&L."""
        self.current_balance = self.paper_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.paper_broker.get_all_positions())

    def get_filled_orders(self) -> int:
        """Get number of filled orders."""
        return len([o for o in self.paper_broker._orders.values()
                   if o.status.value == 'filled'])

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

    def close_position(self, symbol: str):
        """Close a position."""
        return self.paper_broker.close_position(symbol)

    def close_all_positions(self):
        """Close all positions."""
        return self.paper_broker.close_all_positions()

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

    def reset(self):
        """Reset the paper trading state."""
        self.paper_broker.reset()
        self._update_balances()
        self.trade_history = []
        self.orders = []
        logger.info("PaperStrategyInstance reset")

    def save_context(self):
        """Save strategy context including paper trading state."""
        self._update_balances()
        # Save paper broker state
        self.paper_broker.save_state(self.strategy_instance_id)
        super().save_context()

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