"""
Live Trading Broker Implementation for BrighterTrading.

Executes real trades via exchange APIs (CCXT).

WARNING: This broker executes real trades with real money when not in testnet mode.
Ensure thorough testing on testnet before production use.
"""

import logging
import time
import json
from functools import wraps
from typing import Any, Dict, List, Optional
from datetime import datetime, timezone, timedelta
import uuid
import hashlib
import ccxt

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

logger = logging.getLogger(__name__)


class RateLimiter:
    """
    Simple rate limiter to prevent API throttling.

    Usage:
        limiter = RateLimiter(calls_per_second=2.0)
        limiter.wait()  # Call before each API request
    """

    def __init__(self, calls_per_second: float = 2.0):
        """
        Initialize the rate limiter.

        :param calls_per_second: Maximum API calls per second.
        """
        self.min_interval = 1.0 / calls_per_second
        self.last_call = 0.0

    def wait(self):
        """Wait if necessary to respect rate limits."""
        elapsed = time.time() - self.last_call
        if elapsed < self.min_interval:
            time.sleep(self.min_interval - elapsed)
        self.last_call = time.time()


def retry_on_network_error(max_retries: int = 3, delay: float = 1.0):
    """
    Decorator to retry a function on network errors.

    :param max_retries: Maximum number of retry attempts.
    :param delay: Base delay between retries (exponential backoff).
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except ccxt.NetworkError as e:
                    last_exception = e
                    if attempt == max_retries - 1:
                        logger.error(f"Network error after {max_retries} attempts: {e}")
                        raise
                    wait_time = delay * (2 ** attempt)
                    logger.warning(f"Network error, retrying in {wait_time}s: {e}")
                    time.sleep(wait_time)
            raise last_exception
        return wrapper
    return decorator


class LiveOrder:
    """Represents a live trading order."""

    def __init__(
        self,
        order_id: str,
        exchange_order_id: Optional[str],
        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',
        client_order_id: Optional[str] = None
    ):
        self.order_id = order_id
        self.exchange_order_id = exchange_order_id
        self.client_order_id = client_order_id
        self.symbol = symbol
        self.side = side
        self.order_type = order_type
        self.size = size
        self.price = price
        self.stop_loss = stop_loss
        self.take_profit = take_profit
        self.time_in_force = time_in_force
        self.status = OrderStatus.PENDING
        self.filled_qty = 0.0
        self.filled_price = 0.0
        self.commission = 0.0
        self.created_at = datetime.now(timezone.utc)
        self.filled_at: Optional[datetime] = None
        self.last_update: Optional[datetime] = None

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for persistence."""
        return {
            'order_id': self.order_id,
            'exchange_order_id': self.exchange_order_id,
            'client_order_id': self.client_order_id,
            'symbol': self.symbol,
            'side': self.side.value,
            'order_type': self.order_type.value,
            'size': self.size,
            'price': self.price,
            'stop_loss': self.stop_loss,
            'take_profit': self.take_profit,
            'time_in_force': self.time_in_force,
            'status': self.status.value,
            'filled_qty': self.filled_qty,
            'filled_price': self.filled_price,
            'commission': self.commission,
            'created_at': self.created_at.isoformat(),
            'filled_at': self.filled_at.isoformat() if self.filled_at else None,
            'last_update': self.last_update.isoformat() if self.last_update else None
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'LiveOrder':
        """Create LiveOrder from dictionary."""
        order = cls(
            order_id=data['order_id'],
            exchange_order_id=data.get('exchange_order_id'),
            symbol=data['symbol'],
            side=OrderSide(data['side']),
            order_type=OrderType(data['order_type']),
            size=data['size'],
            price=data.get('price'),
            stop_loss=data.get('stop_loss'),
            take_profit=data.get('take_profit'),
            time_in_force=data.get('time_in_force', 'GTC'),
            client_order_id=data.get('client_order_id')
        )
        order.status = OrderStatus(data['status'])
        order.filled_qty = data.get('filled_qty', 0.0)
        order.filled_price = data.get('filled_price', 0.0)
        order.commission = data.get('commission', 0.0)
        if data.get('created_at'):
            order.created_at = datetime.fromisoformat(data['created_at'])
        if data.get('filled_at'):
            order.filled_at = datetime.fromisoformat(data['filled_at'])
        if data.get('last_update'):
            order.last_update = datetime.fromisoformat(data['last_update'])
        return order


class LiveBroker(BaseBroker):
    """
    Live trading broker that executes real trades via CCXT.

    WARNING: This broker executes real trades with real money when not in testnet mode.
    Ensure thorough testing on testnet before production use.

    Features:
    - Real order execution via exchange APIs
    - Balance and position sync from exchange
    - Order lifecycle management (create, cancel, fill detection)
    - Restart-safe open order reconciliation
    - Rate limiting and retry logic for network errors
    """

    def __init__(
        self,
        exchange: Any = None,
        testnet: bool = True,
        initial_balance: float = 0.0,
        commission: float = 0.001,
        slippage: float = 0.0,
        data_cache: Any = None,
        rate_limit: float = 2.0
    ):
        """
        Initialize the LiveBroker.

        :param exchange: Exchange instance (from Exchange.py) 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.
        :param data_cache: DataCache instance for state persistence.
        :param rate_limit: API calls per second limit.
        """
        super().__init__(initial_balance, commission, slippage)

        self._exchange = exchange
        self._testnet = testnet
        self._data_cache = data_cache
        self._rate_limiter = RateLimiter(calls_per_second=rate_limit)

        # Warn if not using testnet
        if not testnet:
            logger.warning(
                "LiveBroker initialized for PRODUCTION trading. "
                "Real money will be used!"
            )
        else:
            logger.info("LiveBroker initialized in TESTNET mode.")

        # Connection state
        self._connected = False

        # Balance tracking - keyed by asset symbol
        self._balances: Dict[str, float] = {}
        self._locked_balances: Dict[str, float] = {}

        # Orders and positions
        self._orders: Dict[str, LiveOrder] = {}
        self._positions: Dict[str, Position] = {}

        # Price cache with expiration
        self._current_prices: Dict[str, float] = {}
        self._price_timestamps: Dict[str, datetime] = {}
        self._price_cache_ttl_seconds: float = 5.0  # Cache expires after 5 seconds
        # Auto-generated client IDs are reused briefly to make retries idempotent.
        self._auto_client_id_window_seconds: float = 5.0
        self._auto_client_ids: Dict[str, tuple[str, datetime]] = {}

        # Last sync timestamps
        self._last_balance_sync: Optional[datetime] = None
        self._last_position_sync: Optional[datetime] = None
        self._last_order_sync: Optional[datetime] = None

    def _ensure_connected(self):
        """Ensure exchange connection is established."""
        if not self._connected:
            raise RuntimeError(
                "LiveBroker not connected to exchange. "
                "Call connect() first."
            )
        if not self._exchange:
            raise RuntimeError(
                "LiveBroker has no exchange configured."
            )

    @retry_on_network_error()
    def connect(self) -> bool:
        """
        Connect to the exchange and sync initial state.

        :return: True if connection successful.
        """
        if not self._exchange:
            logger.error("Cannot connect: no exchange configured")
            return False

        try:
            logger.info("LiveBroker connecting to exchange...")

            # Verify exchange is configured with API keys
            if not self._exchange.configured:
                logger.error("Exchange is not configured with valid API keys")
                return False

            # Sync balance from exchange
            self.sync_balance()

            # Sync open orders
            self.sync_open_orders()

            # Sync positions
            self.sync_positions()

            self._connected = True
            logger.info("LiveBroker connected successfully")
            return True

        except ccxt.AuthenticationError as e:
            logger.error(f"Authentication failed: {e}")
            return False
        except ccxt.BaseError as e:
            logger.error(f"Exchange error during connect: {e}")
            return False

    def disconnect(self):
        """Disconnect from the exchange."""
        self._connected = False
        logger.info("LiveBroker disconnected")

    @retry_on_network_error()
    def sync_balance(self) -> Dict[str, float]:
        """
        Sync balance from exchange.

        :return: Dict of asset balances.
        """
        if not self._exchange:
            return {}

        self._rate_limiter.wait()

        try:
            balance_data = self._exchange.client.fetch_balance()

            # Update total balances
            self._balances.clear()
            self._locked_balances.clear()

            for asset, data in balance_data.items():
                if isinstance(data, dict):
                    total = float(data.get('total', 0) or 0)
                    free = float(data.get('free', 0) or 0)
                    used = float(data.get('used', 0) or 0)

                    if total > 0:
                        self._balances[asset] = total
                        self._locked_balances[asset] = used

            self._last_balance_sync = datetime.now(timezone.utc)
            logger.debug(f"Balance synced: {len(self._balances)} assets")
            return self._balances.copy()

        except ccxt.BaseError as e:
            logger.error(f"Error syncing balance: {e}")
            return {}

    @retry_on_network_error()
    def sync_positions(self) -> List[Position]:
        """
        Sync positions from exchange.

        :return: List of current positions.
        """
        if not self._exchange:
            return []

        self._rate_limiter.wait()

        try:
            # Get active trades/positions from exchange
            trades = self._exchange.get_active_trades()

            self._positions.clear()
            for trade in trades:
                symbol = trade['symbol']
                size = float(trade['quantity'])
                entry_price = float(trade['price'])

                # Get current price for P&L calculation
                current_price = self.get_current_price(symbol)
                if current_price <= 0:
                    current_price = entry_price

                side = trade.get('side', 'buy')
                if side == 'sell':
                    size = -size

                unrealized_pnl = (current_price - entry_price) * size

                self._positions[symbol] = Position(
                    symbol=symbol,
                    size=size,
                    entry_price=entry_price,
                    current_price=current_price,
                    unrealized_pnl=unrealized_pnl
                )

            self._last_position_sync = datetime.now(timezone.utc)
            logger.debug(f"Positions synced: {len(self._positions)} positions")
            return list(self._positions.values())

        except ccxt.BaseError as e:
            logger.error(f"Error syncing positions: {e}")
            return []

    @retry_on_network_error()
    def sync_open_orders(self) -> List[Dict[str, Any]]:
        """
        Sync open orders from exchange.

        Used for restart-safe order reconciliation.

        :return: List of open orders from exchange.
        """
        if not self._exchange:
            return []

        self._rate_limiter.wait()

        try:
            exchange_orders = self._exchange.get_open_orders()

            # Build set of existing exchange order IDs to avoid duplicates
            existing_exchange_ids = {
                order.exchange_order_id
                for order in self._orders.values()
                if order.exchange_order_id
            }

            synced_orders = []
            for ex_order in exchange_orders:
                exchange_order_id = ex_order.get('id')

                # Skip orders without ID (shouldn't happen with fixed Exchange.get_open_orders)
                if not exchange_order_id:
                    logger.warning(f"Skipping order without ID: {ex_order}")
                    continue

                exchange_order_id = str(exchange_order_id)

                # Skip if we already track this exchange order
                if exchange_order_id in existing_exchange_ids:
                    synced_orders.append(ex_order)
                    continue

                # Create local order tracking
                symbol = ex_order['symbol']
                side = OrderSide.BUY if ex_order['side'].lower() == 'buy' else OrderSide.SELL
                order_type = OrderType.LIMIT if ex_order.get('type', 'limit').lower() == 'limit' else OrderType.MARKET
                size = float(ex_order['quantity'])
                price = float(ex_order.get('price', 0) or 0)

                # Use local UUID for internal tracking, but key by exchange ID
                local_order_id = str(uuid.uuid4())[:8]

                order = LiveOrder(
                    order_id=local_order_id,
                    exchange_order_id=exchange_order_id,
                    symbol=symbol,
                    side=side,
                    order_type=order_type,
                    size=size,
                    price=price if price > 0 else None,
                    client_order_id=ex_order.get('clientOrderId')
                )
                order.status = OrderStatus.OPEN
                order.filled_qty = float(ex_order.get('filled', 0))

                self._orders[local_order_id] = order
                existing_exchange_ids.add(exchange_order_id)
                logger.info(f"Synced open order from exchange: {exchange_order_id} -> {local_order_id}")

                synced_orders.append(ex_order)

            self._last_order_sync = datetime.now(timezone.utc)
            logger.debug(f"Orders synced: {len(synced_orders)} open orders")
            return synced_orders

        except ccxt.BaseError as e:
            logger.error(f"Error syncing open orders: {e}")
            return []

    def _generate_client_order_id(
        self,
        symbol: str,
        side: OrderSide,
        order_type: OrderType,
        size: float,
        price: Optional[float],
        nonce: str = None
    ) -> str:
        """
        Generate a deterministic client order ID for idempotency.

        This ensures that retried orders can be deduplicated by the exchange
        if they support clientOrderId.

        IMPORTANT: The nonce MUST be provided by the caller and remain constant
        across retries for true idempotency. If not provided, a warning is logged
        and a timestamp-based fallback is used (which defeats idempotency).

        :param nonce: Unique nonce for this order placement attempt. MUST be constant across retries.
        :return: Client order ID string.
        """
        if nonce is None:
            # WARNING: Without a stable nonce, retries will generate different IDs!
            logger.warning("No nonce provided for client order ID - idempotency not guaranteed")
            nonce = str(int(time.time() * 1000000))

        # Create a hash of order parameters for idempotency
        # The hash ensures same params + same nonce = same clientOrderId
        order_data = f"{symbol}:{side.value}:{order_type.value}:{size}:{price}:{nonce}"
        order_hash = hashlib.sha256(order_data.encode()).hexdigest()[:16]
        return f"BT{order_hash}"

    def _get_or_create_auto_client_order_id(
        self,
        symbol: str,
        side: OrderSide,
        order_type: OrderType,
        size: float,
        price: Optional[float]
    ) -> str:
        """
        Create/reuse an auto client order ID for a short retry window.

        This keeps retries idempotent when callers do not provide a client_order_id.
        """
        now = datetime.now(timezone.utc)
        size_token = f"{float(size):.12g}"
        price_token = "market" if price is None else f"{float(price):.12g}"
        intent_key = f"{symbol}:{side.value}:{order_type.value}:{size_token}:{price_token}"

        # Drop stale intent keys.
        stale_keys = [
            key for key, (_, ts) in self._auto_client_ids.items()
            if (now - ts).total_seconds() > self._auto_client_id_window_seconds
        ]
        for key in stale_keys:
            self._auto_client_ids.pop(key, None)

        cached = self._auto_client_ids.get(intent_key)
        if cached is not None:
            cached_id, cached_ts = cached
            if (now - cached_ts).total_seconds() <= self._auto_client_id_window_seconds:
                return cached_id

        # Fresh ID for this intent. A UUID salt prevents long-lived collisions.
        nonce = f"{intent_key}:{uuid.uuid4().hex[:12]}"
        client_id = self._generate_client_order_id(
            symbol=symbol,
            side=side,
            order_type=order_type,
            size=size,
            price=price,
            nonce=nonce
        )
        self._auto_client_ids[intent_key] = (client_id, now)
        return client_id

    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',
        client_order_id: Optional[str] = None
    ) -> OrderResult:
        """
        Place an order on the exchange.

        NOTE: This method is NOT wrapped with @retry_on_network_error because
        retrying order placement can cause duplicate orders. Instead, we use
        clientOrderId for idempotency when supported by the exchange.
        """
        self._ensure_connected()

        # Generate local order ID
        order_id = str(uuid.uuid4())[:8]

        # Generate/reuse client order ID for idempotency.
        if client_order_id is None:
            client_order_id = self._get_or_create_auto_client_order_id(
                symbol, side, order_type, size, price
            )

        # Check if we already have an order with this client ID (duplicate detection)
        for existing_order in self._orders.values():
            if hasattr(existing_order, 'client_order_id') and existing_order.client_order_id == client_order_id:
                logger.warning(f"Duplicate order detected: {client_order_id}")
                return OrderResult(
                    success=True,
                    order_id=existing_order.order_id,
                    exchange_order_id=existing_order.exchange_order_id,
                    status=existing_order.status,
                    filled_qty=existing_order.filled_qty,
                    filled_price=existing_order.filled_price,
                    commission=existing_order.commission,
                    message="Order already exists (duplicate detection)"
                )

        # Validate order
        if size <= 0:
            return OrderResult(
                success=False,
                message="Order size must be positive"
            )

        if order_type == OrderType.LIMIT and (price is None or price <= 0):
            return OrderResult(
                success=False,
                message="Limit orders require a valid price"
            )

        self._rate_limiter.wait()

        try:
            # Map order type to exchange format
            ex_order_type = 'market' if order_type == OrderType.MARKET else 'limit'
            ex_side = side.value  # 'buy' or 'sell'

            logger.info(f"Placing {ex_order_type} order: {ex_side} {size} {symbol} @ {price or 'market'} (clientId: {client_order_id})")

            # Place order via exchange with client order ID for idempotency
            # Note: Not all exchanges support clientOrderId, so we pass it as optional param
            result, exchange_order = self._exchange.place_order(
                symbol=symbol,
                side=ex_side,
                type=ex_order_type,
                timeInForce=time_in_force,
                quantity=size,
                price=price,
                client_order_id=client_order_id
            )

            if result != 'Success' or exchange_order is None:
                logger.error(f"Order placement failed: {result}")
                return OrderResult(
                    success=False,
                    message=f"Order placement failed: {result}"
                )

            # Create local order record
            order = LiveOrder(
                order_id=order_id,
                exchange_order_id=str(exchange_order.get('id', '')),
                symbol=symbol,
                side=side,
                order_type=order_type,
                size=size,
                price=price,
                stop_loss=stop_loss,
                take_profit=take_profit,
                time_in_force=time_in_force,
                client_order_id=client_order_id
            )

            # Parse order status from exchange response
            ex_status = exchange_order.get('status', 'open').lower()
            if ex_status == 'closed' or ex_status == 'filled':
                order.status = OrderStatus.FILLED
                order.filled_qty = float(exchange_order.get('filled', size))
                order.filled_price = float(exchange_order.get('average', price or 0))
                order.filled_at = datetime.now(timezone.utc)

                # Calculate commission
                fee = exchange_order.get('fee', {})
                if fee:
                    order.commission = float(fee.get('cost', 0) or 0)
                else:
                    order.commission = order.filled_qty * order.filled_price * self.commission
            else:
                order.status = OrderStatus.OPEN

            self._orders[order_id] = order

            logger.info(f"Order placed: {order_id} (exchange: {order.exchange_order_id}) - {order.status.value}")

            return OrderResult(
                success=True,
                order_id=order_id,
                exchange_order_id=order.exchange_order_id,
                status=order.status,
                filled_qty=order.filled_qty,
                filled_price=order.filled_price,
                commission=order.commission,
                message=f"Order {order_id} placed successfully"
            )

        except ccxt.InsufficientFunds as e:
            logger.error(f"Insufficient funds: {e}")
            return OrderResult(
                success=False,
                message=f"Insufficient funds: {e}"
            )
        except ccxt.InvalidOrder as e:
            logger.error(f"Invalid order: {e}")
            return OrderResult(
                success=False,
                message=f"Invalid order: {e}"
            )
        except ccxt.BaseError as e:
            logger.error(f"Exchange error placing order: {e}")
            return OrderResult(
                success=False,
                message=f"Exchange error: {e}"
            )

    @retry_on_network_error()
    def cancel_order(self, order_id: str) -> bool:
        """Cancel an order on the exchange."""
        self._ensure_connected()

        if order_id not in self._orders:
            logger.warning(f"Order {order_id} not found")
            return False

        order = self._orders[order_id]

        if order.status not in [OrderStatus.OPEN, OrderStatus.PENDING]:
            logger.warning(f"Cannot cancel order {order_id}: status is {order.status.value}")
            return False

        self._rate_limiter.wait()

        try:
            # Cancel on exchange
            self._exchange.client.cancel_order(
                order.exchange_order_id,
                order.symbol
            )

            order.status = OrderStatus.CANCELLED
            order.last_update = datetime.now(timezone.utc)

            logger.info(f"Order {order_id} cancelled")
            return True

        except ccxt.OrderNotFound:
            logger.warning(f"Order {order_id} not found on exchange (may already be filled/cancelled)")
            order.status = OrderStatus.CANCELLED
            return True
        except ccxt.BaseError as e:
            logger.error(f"Error cancelling order {order_id}: {e}")
            return False

    @retry_on_network_error()
    def get_order(self, order_id: str) -> Optional[Dict[str, Any]]:
        """Get order details from exchange."""
        self._ensure_connected()

        if order_id in self._orders:
            order = self._orders[order_id]

            # If order is still open, refresh from exchange
            if order.status == OrderStatus.OPEN and order.exchange_order_id:
                self._rate_limiter.wait()
                try:
                    ex_order = self._exchange.get_order(order.symbol, order.exchange_order_id)
                    if ex_order:
                        self._update_order_from_exchange(order, ex_order)
                except ccxt.BaseError as e:
                    logger.warning(f"Could not refresh order {order_id}: {e}")

            return order.to_dict()

        return None

    def _update_order_from_exchange(self, order: LiveOrder, ex_order: Dict[str, Any]):
        """Update local order with exchange data."""
        ex_status = ex_order.get('status', 'open').lower()

        if ex_status == 'closed' or ex_status == 'filled':
            order.status = OrderStatus.FILLED
            order.filled_qty = float(ex_order.get('filled', order.size))
            order.filled_price = float(ex_order.get('average', order.price or 0))
            if not order.filled_at:
                order.filled_at = datetime.now(timezone.utc)

            # Update commission
            fee = ex_order.get('fee', {})
            if fee:
                order.commission = float(fee.get('cost', 0) or 0)
        elif ex_status == 'canceled' or ex_status == 'cancelled':
            order.status = OrderStatus.CANCELLED
        elif ex_status == 'partially_filled':
            order.status = OrderStatus.PARTIALLY_FILLED
            order.filled_qty = float(ex_order.get('filled', 0))
            order.filled_price = float(ex_order.get('average', 0))

        order.last_update = datetime.now(timezone.utc)

    def get_open_orders(self, symbol: Optional[str] = None) -> List[Dict[str, Any]]:
        """Get all open orders from local cache."""
        self._ensure_connected()

        open_orders = []
        for order in self._orders.values():
            if order.status in [OrderStatus.OPEN, OrderStatus.PENDING]:
                if symbol is None or order.symbol == symbol:
                    open_orders.append(order.to_dict())

        return open_orders

    def get_balance(self, asset: Optional[str] = None) -> float:
        """Get balance from cached balances."""
        self._ensure_connected()

        if asset:
            return self._balances.get(asset, 0.0)

        # Return total balance (sum of all assets in quote currency)
        # For simplicity, return USDT balance if available
        for quote in ['USDT', 'USD', 'BUSD', 'USDC']:
            if quote in self._balances:
                return self._balances[quote]

        # Return first balance if no quote currency found
        if self._balances:
            return list(self._balances.values())[0]

        return 0.0

    def get_available_balance(self, asset: Optional[str] = None) -> float:
        """Get available balance (total minus locked in orders)."""
        self._ensure_connected()

        if asset:
            total = self._balances.get(asset, 0.0)
            locked = self._locked_balances.get(asset, 0.0)
            return total - locked

        # Return available USDT balance if available
        for quote in ['USDT', 'USD', 'BUSD', 'USDC']:
            if quote in self._balances:
                total = self._balances[quote]
                locked = self._locked_balances.get(quote, 0.0)
                return total - locked

        return 0.0

    def get_total_equity(self, quote_asset: str = 'USDT', max_assets: int = 10) -> float:
        """
        Get total equity by converting holdings to quote currency value.

        For performance, only fetches prices for the top N assets by balance.
        Stablecoins are counted 1:1, fiat currencies are skipped.

        :param quote_asset: Quote currency to use for valuation (default USDT).
        :param max_assets: Maximum number of non-stablecoin assets to price (default 10).
        :return: Total equity in quote currency.
        """
        self._ensure_connected()

        if not self._balances:
            return 0.0

        total_equity = 0.0

        # Quote currencies that don't need conversion (stablecoins)
        stablecoins = {'USDT', 'USD', 'BUSD', 'USDC', 'DAI', 'TUSD'}

        # Fiat currencies and other assets that don't trade on crypto exchanges
        skip_assets = {
            'TRY', 'ZAR', 'UAH', 'BRL', 'PLN', 'ARS', 'JPY', 'MXN', 'COP', 'IDR',
            'CZK', 'EUR', 'GBP', 'AUD', 'CAD', 'CHF', 'CNY', 'HKD', 'INR', 'KRW',
            'NGN', 'PHP', 'RUB', 'SGD', 'THB', 'TWD', 'VND', 'NZD', 'SEK', 'NOK',
            'DKK', 'ILS', 'MYR', 'PKR', 'KES', 'EGP', 'CLP', 'PEN', 'AED', 'SAR',
            '456', '这是测试币',
        }

        # Separate stablecoins from assets needing price lookup
        assets_to_price = []
        for asset, balance in self._balances.items():
            if balance <= 0:
                continue
            if asset in stablecoins:
                total_equity += balance
            elif asset not in skip_assets:
                assets_to_price.append((asset, balance))

        # Sort by balance descending and take top N
        assets_to_price.sort(key=lambda x: x[1], reverse=True)
        assets_to_price = assets_to_price[:max_assets]

        logger.info(f"get_total_equity: pricing top {len(assets_to_price)} of {len(self._balances)} assets")

        for asset, balance in assets_to_price:
            try:
                symbol = f"{asset}/{quote_asset}"
                price = self.get_current_price(symbol)
                if price > 0:
                    total_equity += balance * price
                    logger.debug(f"  {asset}: {balance} * {price} = {balance * price}")
            except Exception as e:
                logger.warning(f"Error getting price for {asset}: {e}")

        logger.info(f"get_total_equity: total = {total_equity}")
        return total_equity

    def get_position(self, symbol: str) -> Optional[Position]:
        """Get position from cache."""
        self._ensure_connected()
        return self._positions.get(symbol)

    def get_all_positions(self) -> List[Position]:
        """Get all positions from cache."""
        self._ensure_connected()
        return list(self._positions.values())

    @retry_on_network_error()
    def get_current_price(self, symbol: str) -> float:
        """Get current price from exchange with cache expiration."""
        if not self._exchange:
            return self._current_prices.get(symbol, 0.0)

        # Check cache first - return cached price only if not expired
        if symbol in self._current_prices and symbol in self._price_timestamps:
            cache_age = (datetime.now(timezone.utc) - self._price_timestamps[symbol]).total_seconds()
            if cache_age < self._price_cache_ttl_seconds:
                return self._current_prices[symbol]
            # Cache expired, will fetch fresh price below

        self._rate_limiter.wait()

        try:
            price = self._exchange.get_price(symbol)
            if price > 0:
                self._current_prices[symbol] = price
                self._price_timestamps[symbol] = datetime.now(timezone.utc)
            return price
        except ccxt.BaseError as e:
            logger.warning(f"Error getting price for {symbol}: {e}")
            # Return stale price as fallback if fetch fails
            return self._current_prices.get(symbol, 0.0)

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

        Checks for order fills, updates positions, and emits events.

        :return: List of events (fills, updates, etc.).
        """
        self._ensure_connected()

        events = []

        # Update prices for tracked symbols
        for symbol in list(self._positions.keys()) + [o.symbol for o in self._orders.values() if o.status == OrderStatus.OPEN]:
            try:
                price = self.get_current_price(symbol)
                if price > 0:
                    self._current_prices[symbol] = price
            except Exception:
                pass

        # Check for fills on open orders
        for order_id, order in list(self._orders.items()):
            if order.status not in [OrderStatus.OPEN, OrderStatus.PENDING]:
                continue

            if not order.exchange_order_id:
                continue

            try:
                self._rate_limiter.wait()
                ex_order = self._exchange.get_order(order.symbol, order.exchange_order_id)

                if ex_order:
                    old_status = order.status
                    self._update_order_from_exchange(order, ex_order)

                    # Emit fill event if order was filled
                    if order.status == OrderStatus.FILLED and old_status != OrderStatus.FILLED:
                        # Calculate profitability for sell orders (before position update)
                        is_profitable = False
                        realized_pnl = 0.0
                        entry_price = 0.0
                        if order.side == OrderSide.SELL and order.symbol in self._positions:
                            pos = self._positions[order.symbol]
                            entry_price = pos.entry_price
                            realized_pnl = (order.filled_price - pos.entry_price) * order.filled_qty - order.commission
                            is_profitable = realized_pnl > 0

                        events.append({
                            'type': 'fill',
                            'order_id': order_id,
                            'exchange_order_id': order.exchange_order_id,
                            'symbol': order.symbol,
                            'side': order.side.value,
                            'size': order.filled_qty,
                            'filled_qty': order.filled_qty,
                            'price': order.filled_price,
                            'filled_price': order.filled_price,
                            'commission': order.commission,
                            'is_profitable': is_profitable,
                            'realized_pnl': realized_pnl,
                            'entry_price': entry_price
                        })
                        logger.info(f"Order filled: {order_id} - {order.side.value} {order.filled_qty} {order.symbol} @ {order.filled_price}")

                        # Update position after fill
                        self._update_position_from_fill(order)

            except ccxt.BaseError as e:
                logger.warning(f"Error checking order {order_id}: {e}")

        # Update position P&L
        for symbol, position in self._positions.items():
            current_price = self._current_prices.get(symbol, position.current_price)
            if current_price > 0:
                position.current_price = current_price
                position.unrealized_pnl = (current_price - position.entry_price) * position.size

        return events

    def _update_position_from_fill(self, order: LiveOrder):
        """Update position based on a filled order."""
        symbol = order.symbol
        filled_size = order.filled_qty
        fill_price = order.filled_price

        if order.side == OrderSide.BUY:
            if symbol in self._positions:
                # Average in to existing position
                pos = self._positions[symbol]
                new_size = pos.size + filled_size
                new_entry = (pos.entry_price * pos.size + fill_price * filled_size) / new_size
                pos.size = new_size
                pos.entry_price = new_entry
            else:
                # New position
                self._positions[symbol] = Position(
                    symbol=symbol,
                    size=filled_size,
                    entry_price=fill_price,
                    current_price=fill_price,
                    unrealized_pnl=0.0
                )
        else:
            # Sell order - reduce or close position
            if symbol in self._positions:
                pos = self._positions[symbol]
                realized_pnl = (fill_price - pos.entry_price) * filled_size - order.commission
                pos.realized_pnl += realized_pnl
                pos.size -= filled_size

                if pos.size <= 0:
                    del self._positions[symbol]

    # ==================== State Persistence Methods ====================

    def _ensure_persistence_cache(self) -> bool:
        """Ensure the persistence table/cache exists."""
        if not self._data_cache:
            return False

        try:
            # Create backing DB table
            if hasattr(self._data_cache, 'db') and hasattr(self._data_cache.db, 'execute_sql'):
                self._data_cache.db.execute_sql(
                    'CREATE TABLE IF NOT EXISTS "live_broker_states" ('
                    'id INTEGER PRIMARY KEY AUTOINCREMENT, '
                    'tbl_key TEXT UNIQUE, '
                    'strategy_instance_id TEXT UNIQUE, '
                    'broker_state TEXT, '
                    'updated_at TEXT)',
                    []
                )

            self._data_cache.create_cache(
                name='live_broker_states',
                cache_type='table',
                size_limit=5000,
                eviction_policy='deny',
                default_expiration=timedelta(days=7),
                columns=['id', 'tbl_key', 'strategy_instance_id', 'broker_state', 'updated_at']
            )
            return True
        except Exception as e:
            logger.error(f"LiveBroker: Error ensuring persistence cache: {e}", exc_info=True)
            return False

    def to_state_dict(self) -> Dict[str, Any]:
        """Serialize broker state to dictionary."""
        orders_data = {oid: o.to_dict() for oid, o in self._orders.items()}
        positions_data = {sym: p.to_dict() for sym, p in self._positions.items()}

        return {
            'testnet': self._testnet,
            'balances': self._balances.copy(),
            'locked_balances': self._locked_balances.copy(),
            'orders': orders_data,
            'positions': positions_data,
            'current_prices': self._current_prices.copy(),
            'last_balance_sync': self._last_balance_sync.isoformat() if self._last_balance_sync else None,
            'last_position_sync': self._last_position_sync.isoformat() if self._last_position_sync else None,
            'last_order_sync': self._last_order_sync.isoformat() if self._last_order_sync else None,
        }

    def from_state_dict(self, state: Dict[str, Any]):
        """Restore broker state from dictionary."""
        if not state:
            return

        # Restore balances
        self._balances = state.get('balances', {})
        self._locked_balances = state.get('locked_balances', {})

        # Restore orders
        self._orders.clear()
        for order_id, order_data in state.get('orders', {}).items():
            self._orders[order_id] = LiveOrder.from_dict(order_data)

        # Restore positions
        self._positions.clear()
        for symbol, pos_data in state.get('positions', {}).items():
            self._positions[symbol] = Position.from_dict(pos_data)

        # Restore price cache
        self._current_prices = state.get('current_prices', {})

        # Restore timestamps
        if state.get('last_balance_sync'):
            self._last_balance_sync = datetime.fromisoformat(state['last_balance_sync'])
        if state.get('last_position_sync'):
            self._last_position_sync = datetime.fromisoformat(state['last_position_sync'])
        if state.get('last_order_sync'):
            self._last_order_sync = datetime.fromisoformat(state['last_order_sync'])

        logger.info(f"LiveBroker: State restored - {len(self._orders)} orders, {len(self._positions)} positions")

    def save_state(self, strategy_instance_id: str) -> bool:
        """Save broker state to data cache."""
        if not self._data_cache:
            logger.warning("LiveBroker: No data cache available for persistence")
            return False

        try:
            if not self._ensure_persistence_cache():
                return False

            state_dict = self.to_state_dict()
            state_json = json.dumps(state_dict)

            existing = self._data_cache.get_rows_from_datacache(
                cache_name='live_broker_states',
                filter_vals=[('strategy_instance_id', strategy_instance_id)]
            )

            columns = ('tbl_key', 'strategy_instance_id', 'broker_state', 'updated_at')
            values = (strategy_instance_id, strategy_instance_id, state_json, datetime.now(timezone.utc).isoformat())

            if existing.empty:
                self._data_cache.insert_row_into_datacache(
                    cache_name='live_broker_states',
                    columns=columns,
                    values=values
                )
            else:
                self._data_cache.modify_datacache_item(
                    cache_name='live_broker_states',
                    filter_vals=[('strategy_instance_id', strategy_instance_id)],
                    field_names=columns,
                    new_values=values,
                    overwrite='strategy_instance_id'
                )

            logger.debug(f"LiveBroker: State saved for {strategy_instance_id}")
            return True

        except Exception as e:
            logger.error(f"LiveBroker: Error saving state: {e}", exc_info=True)
            return False

    def load_state(self, strategy_instance_id: str) -> bool:
        """Load broker state from data cache."""
        if not self._data_cache:
            logger.warning("LiveBroker: No data cache available for persistence")
            return False

        try:
            if not self._ensure_persistence_cache():
                return False

            existing = self._data_cache.get_rows_from_datacache(
                cache_name='live_broker_states',
                filter_vals=[('strategy_instance_id', strategy_instance_id)]
            )

            if existing.empty:
                logger.debug(f"LiveBroker: No saved state for {strategy_instance_id}")
                return False

            state_json = existing.iloc[0].get('broker_state', '{}')
            state_dict = json.loads(state_json)
            self.from_state_dict(state_dict)

            logger.info(f"LiveBroker: State loaded for {strategy_instance_id}")
            return True

        except Exception as e:
            logger.error(f"LiveBroker: Error loading state: {e}", exc_info=True)
            return False

    def reconcile_with_exchange(self) -> Dict[str, Any]:
        """
        Compare persisted state with exchange reality and log discrepancies.

        Call this after load_state to ensure consistency.

        :return: Dict with reconciliation results.
        """
        if not self._connected:
            logger.warning("Cannot reconcile: not connected")
            return {'success': False, 'error': 'Not connected'}

        results = {
            'success': True,
            'balance_changes': [],
            'order_changes': [],
            'position_changes': []
        }

        try:
            # Reconcile balances
            old_balances = self._balances.copy()
            self.sync_balance()

            for asset, new_bal in self._balances.items():
                old_bal = old_balances.get(asset, 0)
                if abs(new_bal - old_bal) > 0.00001:
                    results['balance_changes'].append({
                        'asset': asset,
                        'old': old_bal,
                        'new': new_bal,
                        'diff': new_bal - old_bal
                    })
                    logger.info(f"Balance reconciled: {asset} {old_bal} -> {new_bal}")

            # Reconcile open orders
            old_open_orders = {oid: o for oid, o in self._orders.items()
                             if o.status in [OrderStatus.OPEN, OrderStatus.PENDING]}

            exchange_orders = self.sync_open_orders()
            exchange_order_ids = {str(o.get('id')) for o in exchange_orders}

            for order_id, order in old_open_orders.items():
                if order.exchange_order_id not in exchange_order_ids:
                    # Order no longer on exchange - might be filled or cancelled
                    try:
                        ex_order = self._exchange.get_order(order.symbol, order.exchange_order_id)
                        if ex_order:
                            self._update_order_from_exchange(order, ex_order)
                            results['order_changes'].append({
                                'order_id': order_id,
                                'old_status': 'open',
                                'new_status': order.status.value
                            })
                    except Exception:
                        order.status = OrderStatus.CANCELLED
                        results['order_changes'].append({
                            'order_id': order_id,
                            'old_status': 'open',
                            'new_status': 'cancelled (assumed)'
                        })

            # Reconcile positions
            old_positions = {sym: p.size for sym, p in self._positions.items()}
            self.sync_positions()

            for symbol, pos in self._positions.items():
                old_size = old_positions.get(symbol, 0)
                if abs(pos.size - old_size) > 0.00001:
                    results['position_changes'].append({
                        'symbol': symbol,
                        'old_size': old_size,
                        'new_size': pos.size
                    })
                    logger.info(f"Position reconciled: {symbol} {old_size} -> {pos.size}")

            if results['balance_changes'] or results['order_changes'] or results['position_changes']:
                logger.info(f"Reconciliation complete: {len(results['balance_changes'])} balance changes, "
                           f"{len(results['order_changes'])} order changes, "
                           f"{len(results['position_changes'])} position changes")
            else:
                logger.info("Reconciliation complete: no discrepancies found")

            return results

        except Exception as e:
            logger.error(f"Error during reconciliation: {e}", exc_info=True)
            return {'success': False, 'error': str(e)}
