"""
Backtest Broker Implementation for BrighterTrading.

Wraps Backtrader's broker functionality to provide a unified interface
for backtesting strategies.
"""

import logging
from typing import Any, Dict, List, Optional
import uuid

try:
    import backtrader as bt
except ImportError:
    bt = None

from .base_broker import (
    BaseBroker, OrderResult, OrderSide, OrderType, OrderStatus, Position
)

logger = logging.getLogger(__name__)


class BacktestBroker(BaseBroker):
    """
    Broker implementation for backtesting using Backtrader.

    This broker delegates order execution to Backtrader's internal broker,
    while providing the unified BaseBroker interface.
    """

    def __init__(
        self,
        backtrader_strategy: 'bt.Strategy' = None,
        initial_balance: float = 10000.0,
        commission: float = 0.001,
        slippage: float = 0.0
    ):
        """
        Initialize the BacktestBroker.

        :param backtrader_strategy: Reference to the Backtrader strategy instance.
        :param initial_balance: Starting balance (set in Cerebro, not here).
        :param commission: Commission rate (set in Cerebro, not here).
        :param slippage: Slippage rate.
        """
        super().__init__(initial_balance, commission, slippage)
        self._bt_strategy = backtrader_strategy
        self._pending_orders: Dict[str, Any] = {}

    def set_backtrader_strategy(self, strategy: 'bt.Strategy'):
        """Set the Backtrader strategy reference after initialization."""
        self._bt_strategy = strategy

    @property
    def _bt_broker(self):
        """Get the Backtrader broker instance."""
        if self._bt_strategy is None:
            raise RuntimeError("Backtrader strategy not set")
        return self._bt_strategy.broker

    @property
    def _bt_data(self):
        """Get the Backtrader data feed."""
        if self._bt_strategy is None:
            raise RuntimeError("Backtrader strategy not set")
        return self._bt_strategy.data

    def place_order(
        self,
        symbol: str,
        side: OrderSide,
        order_type: OrderType,
        size: float,
        price: Optional[float] = None,
        stop_loss: Optional[float] = None,
        take_profit: Optional[float] = None,
        time_in_force: str = 'GTC'
    ) -> OrderResult:
        """Place an order via Backtrader."""
        if self._bt_strategy is None:
            return OrderResult(
                success=False,
                message="Backtrader strategy not initialized"
            )

        try:
            # Map order type to Backtrader execution type
            if order_type == OrderType.MARKET:
                exectype = bt.Order.Market
                bt_price = None
            elif order_type == OrderType.LIMIT:
                exectype = bt.Order.Limit
                bt_price = price
            elif order_type == OrderType.STOP:
                exectype = bt.Order.Stop
                bt_price = price
            else:
                exectype = bt.Order.Market
                bt_price = None

            # Place the order via Backtrader
            if side == OrderSide.BUY:
                if bt_price is not None:
                    bt_order = self._bt_strategy.buy(size=size, exectype=exectype, price=bt_price)
                else:
                    bt_order = self._bt_strategy.buy(size=size, exectype=exectype)
            else:
                if bt_price is not None:
                    bt_order = self._bt_strategy.sell(size=size, exectype=exectype, price=bt_price)
                else:
                    bt_order = self._bt_strategy.sell(size=size, exectype=exectype)

            # Generate order ID
            order_id = str(bt_order.ref) if bt_order else str(uuid.uuid4())

            # Store order reference
            self._pending_orders[order_id] = bt_order

            logger.info(f"BacktestBroker: Placed {side.value} order for {size} {symbol}")

            return OrderResult(
                success=True,
                order_id=order_id,
                status=OrderStatus.PENDING,
                message=f"Order placed: {side.value} {size} {symbol}"
            )

        except Exception as e:
            logger.error(f"BacktestBroker: Failed to place order: {e}")
            return OrderResult(
                success=False,
                message=str(e)
            )

    def cancel_order(self, order_id: str) -> bool:
        """Cancel an order."""
        if order_id in self._pending_orders:
            bt_order = self._pending_orders[order_id]
            if bt_order is not None:
                self._bt_broker.cancel(bt_order)
                del self._pending_orders[order_id]
                return True
        return False

    def get_order(self, order_id: str) -> Optional[Dict[str, Any]]:
        """Get order details."""
        if order_id in self._pending_orders:
            bt_order = self._pending_orders[order_id]
            if bt_order is not None:
                return {
                    'order_id': order_id,
                    'status': self._map_bt_status(bt_order.status),
                    'size': bt_order.size,
                    'price': bt_order.price,
                    'executed_size': bt_order.executed.size if bt_order.executed else 0,
                    'executed_price': bt_order.executed.price if bt_order.executed else 0
                }
        return None

    def get_open_orders(self, symbol: Optional[str] = None) -> List[Dict[str, Any]]:
        """Get all open orders."""
        orders = []
        for order_id, bt_order in self._pending_orders.items():
            if bt_order is not None and bt_order.status in [bt.Order.Submitted, bt.Order.Accepted]:
                orders.append(self.get_order(order_id))
        return [o for o in orders if o is not None]

    def get_balance(self, asset: Optional[str] = None) -> float:
        """Get total portfolio value."""
        return self._bt_broker.getvalue()

    def get_available_balance(self, asset: Optional[str] = None) -> float:
        """Get available cash."""
        return self._bt_broker.getcash()

    def get_position(self, symbol: str) -> Optional[Position]:
        """Get position for a symbol."""
        if self._bt_strategy is None:
            return None

        bt_position = self._bt_broker.getposition(self._bt_data)
        if bt_position.size == 0:
            return None

        current_price = self.get_current_price(symbol)
        entry_price = bt_position.price
        unrealized_pnl = (current_price - entry_price) * bt_position.size

        return Position(
            symbol=symbol,
            size=bt_position.size,
            entry_price=entry_price,
            current_price=current_price,
            unrealized_pnl=unrealized_pnl
        )

    def get_all_positions(self) -> List[Position]:
        """Get all open positions."""
        positions = []
        if self._bt_strategy is None:
            return positions

        # For single-data backtests, just check the main data
        position = self.get_position("backtest_symbol")
        if position is not None:
            positions.append(position)

        return positions

    def get_current_price(self, symbol: str) -> float:
        """Get current price from Backtrader data feed."""
        if self._bt_strategy is None:
            return 0.0
        return self._bt_data.close[0]

    def update(self) -> List[Dict[str, Any]]:
        """
        Process pending orders.

        In Backtrader, order processing is handled by the engine.
        This method checks for completed orders and updates state.
        """
        events = []

        # Check for completed orders
        completed_orders = []
        for order_id, bt_order in self._pending_orders.items():
            if bt_order is None:
                continue

            if bt_order.status == bt.Order.Completed:
                events.append({
                    'type': 'fill',
                    'order_id': order_id,
                    'size': bt_order.executed.size,
                    'price': bt_order.executed.price,
                    'commission': bt_order.executed.comm
                })
                completed_orders.append(order_id)
            elif bt_order.status in [bt.Order.Canceled, bt.Order.Margin, bt.Order.Rejected]:
                events.append({
                    'type': 'cancelled',
                    'order_id': order_id,
                    'reason': self._map_bt_status(bt_order.status)
                })
                completed_orders.append(order_id)

        # Remove completed orders
        for order_id in completed_orders:
            del self._pending_orders[order_id]

        return events

    @staticmethod
    def _map_bt_status(bt_status: int) -> str:
        """Map Backtrader order status to string."""
        status_map = {
            bt.Order.Created: 'created',
            bt.Order.Submitted: 'submitted',
            bt.Order.Accepted: 'accepted',
            bt.Order.Partial: 'partial',
            bt.Order.Completed: 'completed',
            bt.Order.Canceled: 'cancelled',
            bt.Order.Expired: 'expired',
            bt.Order.Margin: 'margin_call',
            bt.Order.Rejected: 'rejected'
        }
        return status_map.get(bt_status, 'unknown')
