"""CCXT exchange adapter for OpenClaw trading system.

This module provides a unified interface for multiple cryptocurrency exchanges
using the CCXT library. It supports Binance, OKX, Bybit, KuCoin, and other
exchanges through a consistent API.
"""

import asyncio
import time
from abc import ABC
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Type, Union

from openclaw.exchange.base import (
    Exchange,
    ExchangeError,
    AuthenticationError,
    InsufficientFundsError,
    InvalidOrderError,
    OrderNotFoundError,
)
from openclaw.exchange.models import (
    Balance,
    Order,
    OrderSide,
    OrderStatus,
    OrderType,
    Position,
    Ticker,
)
from openclaw.utils.logging import get_logger

# Optional CCXT import
try:
    import ccxt
    import ccxt.async_support as ccxt_async
    CCXT_AVAILABLE = True
except ImportError:
    CCXT_AVAILABLE = False


class ExchangeFeature(Enum):
    """Exchange feature flags."""
    SPOT_TRADING = "spot"
    MARGIN_TRADING = "margin"
    FUTURES_TRADING = "futures"
    OPTIONS_TRADING = "options"
    LEVERAGE = "leverage"
    STOP_ORDERS = "stop_orders"
    TRAILING_ORDERS = "trailing_orders"
    WS_PUBLIC = "ws_public"
    WS_PRIVATE = "ws_private"


@dataclass
class RateLimitConfig:
    """Rate limit configuration for an exchange.

    Attributes:
        requests_per_second: Maximum requests per second
        requests_per_minute: Maximum requests per minute
        burst_limit: Maximum burst requests
        retry_after: Seconds to wait after rate limit hit
    """
    requests_per_second: float = 10.0
    requests_per_minute: float = 600.0
    burst_limit: int = 20
    retry_after: float = 1.0


@dataclass
class RetryConfig:
    """Retry configuration for API calls.

    Attributes:
        max_retries: Maximum number of retry attempts
        base_delay: Initial delay between retries (seconds)
        max_delay: Maximum delay between retries (seconds)
        exponential_base: Exponential backoff multiplier
        retry_on_status: HTTP status codes to retry
        retry_on_errors: Error types to retry
    """
    max_retries: int = 3
    base_delay: float = 1.0
    max_delay: float = 60.0
    exponential_base: float = 2.0
    retry_on_status: tuple = (429, 500, 502, 503, 504)
    retry_on_errors: tuple = ("NetworkError", "ExchangeNotAvailable", "RequestTimeout")


class RateLimiter:
    """Token bucket rate limiter for API requests."""

    def __init__(self, config: RateLimitConfig):
        self.config = config
        self.tokens = config.burst_limit
        self.last_update = time.time()
        self.lock = asyncio.Lock()
        self.logger = get_logger("exchange.rate_limiter")

    async def acquire(self) -> bool:
        """Acquire a rate limit token.

        Returns:
            True if token acquired, False if rate limited
        """
        async with self.lock:
            now = time.time()
            elapsed = now - self.last_update

            # Replenish tokens based on time elapsed
            tokens_to_add = elapsed * self.config.requests_per_second
            self.tokens = min(self.config.burst_limit, self.tokens + tokens_to_add)
            self.last_update = now

            if self.tokens >= 1:
                self.tokens -= 1
                return True

            # Calculate wait time
            wait_time = (1 - self.tokens) / self.config.requests_per_second
            self.logger.debug(f"Rate limit hit, waiting {wait_time:.2f}s")
            await asyncio.sleep(wait_time)

            # Try again after wait
            self.tokens -= 1
            return True


class CCXTAdapter(Exchange):
    """CCXT-based exchange adapter.

    Provides a unified interface for multiple exchanges using CCXT library.
    Supports both synchronous and asynchronous operations.

    Args:
        exchange_id: CCXT exchange identifier (e.g., 'binance', 'okx')
        api_key: API key for authentication
        api_secret: API secret for authentication
        passphrase: Additional passphrase (required for some exchanges)
        is_simulated: Whether to use sandbox/testnet
        testnet: Whether to use testnet endpoints
        rate_limit_config: Custom rate limit configuration
        retry_config: Custom retry configuration
        use_async: Whether to use async CCXT client
        options: Additional CCXT options
    """

    # Supported exchanges
    SUPPORTED_EXCHANGES = {
        "binance": "Binance",
        "binanceusdm": "Binance Futures USD-M",
        "binancecoinm": "Binance Futures COIN-M",
        "okx": "OKX",
        "bybit": "Bybit",
        "kucoin": "KuCoin",
        "kucoinfutures": "KuCoin Futures",
        "huobi": "HTX (Huobi)",
        "gate": "Gate.io",
        "mexc": "MEXC",
        "kraken": "Kraken",
        "coinbase": "Coinbase",
        "cryptocom": "Crypto.com",
    }

    def __init__(
        self,
        exchange_id: str,
        api_key: Optional[str] = None,
        api_secret: Optional[str] = None,
        passphrase: Optional[str] = None,
        is_simulated: bool = False,
        testnet: bool = False,
        rate_limit_config: Optional[RateLimitConfig] = None,
        retry_config: Optional[RetryConfig] = None,
        use_async: bool = True,
        options: Optional[Dict[str, Any]] = None,
    ):
        if not CCXT_AVAILABLE:
            raise ExchangeError(
                "CCXT is not installed. Install with: pip install ccxt"
            )

        self.exchange_id = exchange_id.lower()
        if self.exchange_id not in self.SUPPORTED_EXCHANGES:
            raise ExchangeError(
                f"Unsupported exchange: {exchange_id}. "
                f"Supported: {list(self.SUPPORTED_EXCHANGES.keys())}"
            )

        super().__init__(name=self.exchange_id, is_simulated=is_simulated)
        self.logger = get_logger(f"exchange.ccxt.{self.exchange_id}")

        # Authentication
        self.api_key = api_key
        self.api_secret = api_secret
        self.passphrase = passphrase

        # Configuration
        self.use_async = use_async
        self.testnet = testnet
        self.rate_limit_config = rate_limit_config or RateLimitConfig()
        self.retry_config = retry_config or RetryConfig()

        # Initialize CCXT client
        self._client: Optional[Any] = None
        self._rate_limiter = RateLimiter(self.rate_limit_config)
        self._options = options or {}

        # Cache
        self._markets: Optional[Dict[str, Any]] = None
        self._cached_balances: Dict[str, Balance] = {}
        self._cached_tickers: Dict[str, Ticker] = {}

        self.logger.info(
            f"CCXTAdapter initialized for {self.SUPPORTED_EXCHANGES[self.exchange_id]} "
            f"(async={use_async}, testnet={testnet}, simulated={is_simulated})"
        )

    def _create_client(self) -> Any:
        """Create CCXT client instance."""
        ccxt_module = ccxt_async if self.use_async else ccxt

        # Get exchange class
        exchange_class = getattr(ccxt_module, self.exchange_id)

        # Configuration
        config = {
            "apiKey": self.api_key,
            "secret": self.api_secret,
            "enableRateLimit": False,  # We handle rate limiting ourselves
            "sandbox": self.testnet or self.is_simulated,
            "options": self._options.copy(),
        }

        if self.passphrase:
            config["password"] = self.passphrase

        # Set specific options for certain exchanges
        if self.exchange_id in ["binance", "binanceusdm", "binancecoinm"]:
            config["options"]["defaultType"] = "future" if "usdm" in self.exchange_id else "spot"

        return exchange_class(config)

    async def connect(self) -> bool:
        """Establish connection to the exchange.

        Returns:
            True if connection successful

        Raises:
            AuthenticationError: If authentication fails
            ExchangeError: If connection fails
        """
        try:
            self._client = self._create_client()

            if self.use_async:
                await self._client.load_markets()
            else:
                self._client.load_markets()

            self._connected = True
            self._markets = self._client.markets

            self.logger.info(
                f"Connected to {self.SUPPORTED_EXCHANGES[self.exchange_id]} "
                f"with {len(self._markets)} markets"
            )
            return True

        except Exception as e:
            error_msg = str(e)
            if "authentication" in error_msg.lower() or "apikey" in error_msg.lower():
                raise AuthenticationError(f"Authentication failed: {e}")
            raise ExchangeError(f"Failed to connect: {e}")

    async def disconnect(self) -> None:
        """Close connection to the exchange."""
        if self._client and self.use_async:
            await self._client.close()
        self._connected = False
        self._client = None
        self.logger.info(f"Disconnected from {self.exchange_id}")

    async def _make_request(
        self,
        method: str,
        *args: Any,
        **kwargs: Any
    ) -> Any:
        """Make an API request with rate limiting and retry logic.

        Args:
            method: CCXT method name
            *args: Method arguments
            **kwargs: Method keyword arguments

        Returns:
            API response

        Raises:
            ExchangeError: If request fails after all retries
        """
        if not self._client:
            raise ExchangeError("Not connected to exchange")

        # Acquire rate limit token
        await self._rate_limiter.acquire()

        last_error: Optional[Exception] = None

        for attempt in range(self.retry_config.max_retries + 1):
            try:
                func = getattr(self._client, method)

                if self.use_async:
                    result = await func(*args, **kwargs)
                else:
                    # Run sync method in thread pool
                    loop = asyncio.get_event_loop()
                    result = await loop.run_in_executor(None, lambda: func(*args, **kwargs))

                return result

            except Exception as e:
                last_error = e
                error_type = type(e).__name__

                # Check if we should retry
                if attempt >= self.retry_config.max_retries:
                    break

                should_retry = (
                    error_type in self.retry_config.retry_on_errors or
                    (hasattr(e, "status") and e.status in self.retry_config.retry_on_status)
                )

                if not should_retry:
                    break

                # Calculate delay with exponential backoff
                delay = min(
                    self.retry_config.base_delay * (self.retry_config.exponential_base ** attempt),
                    self.retry_config.max_delay
                )

                self.logger.warning(
                    f"Request failed (attempt {attempt + 1}/{self.retry_config.max_retries + 1}): "
                    f"{e}. Retrying in {delay:.1f}s..."
                )
                await asyncio.sleep(delay)

        # All retries exhausted
        raise ExchangeError(f"Request failed after {self.retry_config.max_retries + 1} attempts: {last_error}")

    def _ccxt_symbol(self, symbol: str) -> str:
        """Convert internal symbol format to CCXT format.

        Args:
            symbol: Internal symbol (e.g., "BTC/USDT")

        Returns:
            CCXT symbol format
        """
        return symbol.upper()

    def _internal_symbol(self, ccxt_symbol: str) -> str:
        """Convert CCXT symbol format to internal format.

        Args:
            ccxt_symbol: CCXT symbol

        Returns:
            Internal symbol format
        """
        return ccxt_symbol.upper()

    def _map_ccxt_order_status(self, status: str) -> OrderStatus:
        """Map CCXT order status to internal status.

        Args:
            status: CCXT order status

        Returns:
            Internal OrderStatus
        """
        status_map = {
            "open": OrderStatus.OPEN,
            "closed": OrderStatus.FILLED,
            "canceled": OrderStatus.CANCELLED,
            "cancelled": OrderStatus.CANCELLED,
            "pending": OrderStatus.PENDING,
            "rejected": OrderStatus.REJECTED,
            "expired": OrderStatus.EXPIRED,
            "PARTIALLY_FILLED": OrderStatus.PARTIALLY_FILLED,
        }
        return status_map.get(status.lower(), OrderStatus.PENDING)

    def _map_ccxt_side(self, side: str) -> OrderSide:
        """Map CCXT side to internal side.

        Args:
            side: CCXT side ('buy' or 'sell')

        Returns:
            Internal OrderSide
        """
        return OrderSide.BUY if side.lower() == "buy" else OrderSide.SELL

    def _order_from_ccxt(self, ccxt_order: Dict[str, Any]) -> Order:
        """Convert CCXT order to internal Order model.

        Args:
            ccxt_order: CCXT order dictionary

        Returns:
            Internal Order object
        """
        return Order(
            order_id=ccxt_order.get("id", ""),
            symbol=self._internal_symbol(ccxt_order.get("symbol", "")),
            side=self._map_ccxt_side(ccxt_order.get("side", "buy")),
            order_type=OrderType(ccxt_order.get("type", "market")),
            amount=float(ccxt_order.get("amount", 0) or 0),
            price=float(ccxt_order.get("price", 0)) if ccxt_order.get("price") else None,
            status=self._map_ccxt_order_status(ccxt_order.get("status", "open")),
            filled_amount=float(ccxt_order.get("filled", 0) or 0),
            created_at=datetime.fromtimestamp(ccxt_order.get("timestamp", 0) / 1000)
            if ccxt_order.get("timestamp") else datetime.now(),
            updated_at=datetime.fromtimestamp(ccxt_order.get("lastTradeTimestamp", 0) / 1000)
            if ccxt_order.get("lastTradeTimestamp") else datetime.now(),
            exchange_id=ccxt_order.get("id"),
            client_order_id=ccxt_order.get("clientOrderId"),
        )

    def _ticker_from_ccxt(self, ccxt_ticker: Dict[str, Any]) -> Ticker:
        """Convert CCXT ticker to internal Ticker model.

        Args:
            ccxt_ticker: CCXT ticker dictionary

        Returns:
            Internal Ticker object
        """
        return Ticker(
            symbol=self._internal_symbol(ccxt_ticker.get("symbol", "")),
            bid=float(ccxt_ticker.get("bid", 0) or 0),
            ask=float(ccxt_ticker.get("ask", 0) or 0),
            last=float(ccxt_ticker.get("last", 0) or 0),
            high=float(ccxt_ticker.get("high", 0)) if ccxt_ticker.get("high") else None,
            low=float(ccxt_ticker.get("low", 0)) if ccxt_ticker.get("low") else None,
            volume=float(ccxt_ticker.get("baseVolume", 0)) if ccxt_ticker.get("baseVolume") else None,
            timestamp=datetime.fromtimestamp(ccxt_ticker.get("timestamp", 0) / 1000)
            if ccxt_ticker.get("timestamp") else datetime.now(),
        )

    async def place_order(
        self,
        symbol: str,
        side: OrderSide,
        amount: float,
        price: Optional[float] = None,
        order_type: str = "market",
    ) -> Order:
        """Place a new order on the exchange.

        Args:
            symbol: Trading symbol (e.g., "BTC/USDT")
            side: Buy or sell
            amount: Order quantity
            price: Order price (required for limit orders)
            order_type: Order type (market, limit, etc.)

        Returns:
            Order object with exchange-assigned order ID

        Raises:
            InsufficientFundsError: If account has insufficient balance
            InvalidOrderError: If order parameters are invalid
            ExchangeError: If order placement fails
        """
        try:
            ccxt_side = "buy" if side == OrderSide.BUY else "sell"
            ccxt_symbol = self._ccxt_symbol(symbol)

            params: Dict[str, Any] = {}

            # Handle different order types
            if order_type == "market":
                ccxt_order = await self._make_request(
                    "create_market_buy_order" if side == OrderSide.BUY else "create_market_sell_order",
                    ccxt_symbol,
                    amount,
                    params,
                )
            else:
                if price is None:
                    raise InvalidOrderError("Price required for limit orders")

                ccxt_order = await self._make_request(
                    "create_limit_buy_order" if side == OrderSide.BUY else "create_limit_sell_order",
                    ccxt_symbol,
                    amount,
                    price,
                    params,
                )

            order = self._order_from_ccxt(ccxt_order)
            self.logger.info(
                f"Order placed: {order.order_id} {side.value} {amount} {symbol} @ {price or 'market'}"
            )
            return order

        except Exception as e:
            error_msg = str(e).lower()
            if "insufficient" in error_msg or "balance" in error_msg:
                raise InsufficientFundsError(f"Insufficient funds: {e}")
            elif "invalid" in error_msg or "min amount" in error_msg:
                raise InvalidOrderError(f"Invalid order: {e}")
            raise ExchangeError(f"Failed to place order: {e}")

    async def cancel_order(self, order_id: str) -> bool:
        """Cancel an existing order.

        Args:
            order_id: Order ID to cancel

        Returns:
            True if cancellation successful

        Raises:
            OrderNotFoundError: If order not found
            ExchangeError: If cancellation fails
        """
        try:
            # Try to cancel without symbol first
            result = await self._make_request("cancel_order", order_id)
            self.logger.info(f"Order cancelled: {order_id}")
            return True

        except Exception as e:
            error_msg = str(e).lower()
            if "not found" in error_msg or "does not exist" in error_msg:
                raise OrderNotFoundError(f"Order {order_id} not found")
            raise ExchangeError(f"Failed to cancel order: {e}")

    async def get_order(self, order_id: str) -> Optional[Order]:
        """Get order details by ID.

        Args:
            order_id: Order ID to query

        Returns:
            Order object or None if not found
        """
        try:
            ccxt_order = await self._make_request("fetch_order", order_id)
            return self._order_from_ccxt(ccxt_order)
        except Exception as e:
            error_msg = str(e).lower()
            if "not found" in error_msg:
                return None
            raise ExchangeError(f"Failed to get order: {e}")

    async def get_open_orders(self, symbol: Optional[str] = None) -> List[Order]:
        """Get all open orders.

        Args:
            symbol: Optional symbol filter

        Returns:
            List of open orders
        """
        try:
            params: Dict[str, Any] = {}
            if symbol:
                params["symbol"] = self._ccxt_symbol(symbol)

            ccxt_orders = await self._make_request("fetch_open_orders", None, params)
            return [self._order_from_ccxt(o) for o in ccxt_orders]

        except Exception as e:
            raise ExchangeError(f"Failed to get open orders: {e}")

    async def get_balance(self, asset: Optional[str] = None) -> List[Balance]:
        """Get account balance.

        Args:
            asset: Optional asset filter (e.g., "BTC", "USDT")

        Returns:
            List of balance objects
        """
        try:
            ccxt_balance = await self._make_request("fetch_balance")

            balances: List[Balance] = []
            free_balances = ccxt_balance.get("free", {})
            used_balances = ccxt_balance.get("used", {})

            # Get all assets
            all_assets = set(free_balances.keys()) | set(used_balances.keys())

            for asset_code in all_assets:
                if asset and asset.upper() != asset_code.upper():
                    continue

                free = float(free_balances.get(asset_code, 0) or 0)
                locked = float(used_balances.get(asset_code, 0) or 0)

                if free > 0 or locked > 0 or not asset:
                    balances.append(Balance(
                        asset=asset_code.upper(),
                        free=free,
                        locked=locked,
                    ))

            return balances

        except Exception as e:
            raise ExchangeError(f"Failed to get balance: {e}")

    async def get_positions(self, symbol: Optional[str] = None) -> List[Position]:
        """Get current positions.

        Args:
            symbol: Optional symbol filter

        Returns:
            List of position objects
        """
        try:
            # For spot trading, positions are derived from balances
            # For futures, use fetch_positions if available
            if hasattr(self._client, "fetch_positions"):
                params: Dict[str, Any] = {}
                if symbol:
                    params["symbols"] = [self._ccxt_symbol(symbol)]

                ccxt_positions = await self._make_request("fetch_positions", None, params)
                positions: List[Position] = []

                for pos in ccxt_positions:
                    amount = float(pos.get("contracts", 0) or 0)
                    if amount == 0:
                        continue

                    side = OrderSide.BUY if pos.get("side") == "long" else OrderSide.SELL
                    positions.append(Position(
                        symbol=self._internal_symbol(pos.get("symbol", "")),
                        side=side,
                        amount=abs(amount),
                        entry_price=float(pos.get("entryPrice", 0) or 0),
                        current_price=float(pos.get("markPrice", 0) or 0),
                        leverage=float(pos.get("leverage", 1) or 1),
                    ))

                return positions
            else:
                # Spot: infer positions from non-zero balances
                balances = await self.get_balance()
                positions: List[Position] = []

                for balance in balances:
                    if balance.asset in ["USDT", "USD", "BUSD"]:
                        continue

                    if balance.total > 0:
                        ticker_symbol = f"{balance.asset}/USDT"
                        try:
                            ticker = await self.get_ticker(ticker_symbol)
                            positions.append(Position(
                                symbol=ticker_symbol,
                                side=OrderSide.BUY,
                                amount=balance.total,
                                entry_price=ticker.last,
                                current_price=ticker.last,
                                leverage=1.0,
                            ))
                        except ExchangeError:
                            pass

                if symbol:
                    positions = [p for p in positions if p.symbol == symbol.upper()]

                return positions

        except Exception as e:
            raise ExchangeError(f"Failed to get positions: {e}")

    async def get_ticker(self, symbol: str) -> Ticker:
        """Get current market ticker for a symbol.

        Args:
            symbol: Trading symbol

        Returns:
            Ticker object with current market data

        Raises:
            ExchangeError: If ticker data cannot be retrieved
        """
        try:
            ccxt_symbol = self._ccxt_symbol(symbol)
            ccxt_ticker = await self._make_request("fetch_ticker", ccxt_symbol)
            return self._ticker_from_ccxt(ccxt_ticker)

        except Exception as e:
            raise ExchangeError(f"Failed to get ticker for {symbol}: {e}")

    async def get_orderbook(
        self,
        symbol: str,
        limit: int = 100,
    ) -> Dict[str, Any]:
        """Get order book (market depth) for a symbol.

        Args:
            symbol: Trading symbol (e.g., "BTC/USDT")
            limit: Number of price levels to fetch (default: 100)

        Returns:
            Order book dictionary with 'bids', 'asks', 'timestamp', 'nonce'

        Raises:
            ExchangeError: If order book cannot be retrieved
        """
        try:
            ccxt_symbol = self._ccxt_symbol(symbol)
            orderbook = await self._make_request(
                "fetch_order_book",
                ccxt_symbol,
                limit,
            )

            return {
                "symbol": symbol,
                "bids": orderbook.get("bids", []),
                "asks": orderbook.get("asks", []),
                "timestamp": orderbook.get("timestamp"),
                "nonce": orderbook.get("nonce"),
                "datetime": orderbook.get("datetime"),
            }

        except Exception as e:
            raise ExchangeError(f"Failed to get orderbook for {symbol}: {e}")

    async def get_tickers(self, symbols: Optional[List[str]] = None) -> Dict[str, Ticker]:
        """Get tickers for multiple symbols.

        Args:
            symbols: List of symbols (None for all)

        Returns:
            Dictionary mapping symbols to Ticker objects
        """
        try:
            ccxt_symbols = [self._ccxt_symbol(s) for s in symbols] if symbols else None

            if ccxt_symbols and len(ccxt_symbols) == 1:
                ticker = await self.get_ticker(symbols[0])
                return {symbols[0]: ticker}

            ccxt_tickers = await self._make_request("fetch_tickers", ccxt_symbols)

            return {
                self._internal_symbol(symbol): self._ticker_from_ccxt(ticker)
                for symbol, ticker in ccxt_tickers.items()
            }

        except Exception as e:
            raise ExchangeError(f"Failed to get tickers: {e}")

    async def get_ohlcv(
        self,
        symbol: str,
        timeframe: str = "1h",
        limit: int = 100,
        since: Optional[int] = None,
    ) -> List[Dict[str, Any]]:
        """Get OHLCV (candlestick) data.

        Args:
            symbol: Trading symbol
            timeframe: Candle timeframe (1m, 5m, 1h, 1d, etc.)
            limit: Number of candles
            since: Start timestamp in milliseconds

        Returns:
            List of OHLCV data
        """
        try:
            ccxt_symbol = self._ccxt_symbol(symbol)
            ohlcv = await self._make_request(
                "fetch_ohlcv",
                ccxt_symbol,
                timeframe,
                since,
                limit,
            )

            return [
                {
                    "timestamp": candle[0],
                    "open": candle[1],
                    "high": candle[2],
                    "low": candle[3],
                    "close": candle[4],
                    "volume": candle[5],
                }
                for candle in ohlcv
            ]

        except Exception as e:
            raise ExchangeError(f"Failed to get OHLCV: {e}")

    def has_feature(self, feature: ExchangeFeature) -> bool:
        """Check if exchange supports a feature.

        Args:
            feature: Feature to check

        Returns:
            True if feature is supported
        """
        if not self._client:
            return False

        feature_map = {
            ExchangeFeature.SPOT_TRADING: "spot",
            ExchangeFeature.MARGIN_TRADING: "margin",
            ExchangeFeature.FUTURES_TRADING: "future",
            ExchangeFeature.OPTIONS_TRADING: "option",
        }

        ccxt_feature = feature_map.get(feature)
        if ccxt_feature:
            has = self._client.has
            return has.get(ccxt_feature, False) if isinstance(has, dict) else False

        return False


class CCXTExchangeFactory:
    """Factory for creating CCXT exchange adapters.

    Provides a centralized way to create and configure exchange instances
    with consistent settings and caching.
    """

    _instances: Dict[str, CCXTAdapter] = {}
    _default_configs: Dict[str, Dict[str, Any]] = {}

    @classmethod
    def configure_exchange(
        cls,
        exchange_id: str,
        api_key: Optional[str] = None,
        api_secret: Optional[str] = None,
        passphrase: Optional[str] = None,
        **kwargs: Any,
    ) -> None:
        """Configure default settings for an exchange.

        Args:
            exchange_id: Exchange identifier
            api_key: Default API key
            api_secret: Default API secret
            passphrase: Default passphrase
            **kwargs: Additional default configuration
        """
        cls._default_configs[exchange_id.lower()] = {
            "api_key": api_key,
            "api_secret": api_secret,
            "passphrase": passphrase,
            **kwargs,
        }

    @classmethod
    def create(
        cls,
        exchange_id: str,
        use_cached: bool = True,
        **kwargs: Any,
    ) -> CCXTAdapter:
        """Create or retrieve a CCXT exchange adapter.

        Args:
            exchange_id: Exchange identifier (e.g., 'binance', 'okx')
            use_cached: Whether to return cached instance if available
            **kwargs: Override configuration options

        Returns:
            CCXTAdapter instance
        """
        exchange_id = exchange_id.lower()
        cache_key = f"{exchange_id}:{hash(str(sorted(kwargs.items())))}"

        if use_cached and cache_key in cls._instances:
            return cls._instances[cache_key]

        # Merge with default config
        config = cls._default_configs.get(exchange_id, {}).copy()
        config.update(kwargs)

        adapter = CCXTAdapter(exchange_id=exchange_id, **config)

        if use_cached:
            cls._instances[cache_key] = adapter

        return adapter

    @classmethod
    def get_supported_exchanges(cls) -> List[str]:
        """Get list of supported exchange IDs.

        Returns:
            List of supported exchange identifiers
        """
        return list(CCXTAdapter.SUPPORTED_EXCHANGES.keys())

    @classmethod
    def get_exchange_info(cls, exchange_id: str) -> Dict[str, Any]:
        """Get information about an exchange.

        Args:
            exchange_id: Exchange identifier

        Returns:
            Dictionary with exchange information
        """
        exchange_id = exchange_id.lower()
        name = CCXTAdapter.SUPPORTED_EXCHANGES.get(exchange_id)

        if not name:
            return {"error": f"Unsupported exchange: {exchange_id}"}

        return {
            "id": exchange_id,
            "name": name,
            "configured": exchange_id in cls._default_configs,
        }

    @classmethod
    def clear_cache(cls, exchange_id: Optional[str] = None) -> None:
        """Clear cached exchange instances.

        Args:
            exchange_id: Specific exchange to clear (None for all)
        """
        if exchange_id:
            exchange_id = exchange_id.lower()
            cls._instances = {
                k: v for k, v in cls._instances.items()
                if not k.startswith(f"{exchange_id}:")
            }
        else:
            cls._instances.clear()


# Convenience functions for creating specific exchange adapters

def create_binance(
    api_key: Optional[str] = None,
    api_secret: Optional[str] = None,
    testnet: bool = False,
    **kwargs: Any,
) -> CCXTAdapter:
    """Create a Binance exchange adapter.

    Args:
        api_key: Binance API key
        api_secret: Binance API secret
        testnet: Use Binance testnet
        **kwargs: Additional configuration

    Returns:
        CCXTAdapter for Binance
    """
    return CCXTExchangeFactory.create(
        "binance",
        api_key=api_key,
        api_secret=api_secret,
        testnet=testnet,
        **kwargs,
    )


def create_okx(
    api_key: Optional[str] = None,
    api_secret: Optional[str] = None,
    passphrase: Optional[str] = None,
    testnet: bool = False,
    **kwargs: Any,
) -> CCXTAdapter:
    """Create an OKX exchange adapter.

    Args:
        api_key: OKX API key
        api_secret: OKX API secret
        passphrase: OKX API passphrase
        testnet: Use OKX testnet
        **kwargs: Additional configuration

    Returns:
        CCXTAdapter for OKX
    """
    return CCXTExchangeFactory.create(
        "okx",
        api_key=api_key,
        api_secret=api_secret,
        passphrase=passphrase,
        testnet=testnet,
        **kwargs,
    )


def create_bybit(
    api_key: Optional[str] = None,
    api_secret: Optional[str] = None,
    testnet: bool = False,
    **kwargs: Any,
) -> CCXTAdapter:
    """Create a Bybit exchange adapter.

    Args:
        api_key: Bybit API key
        api_secret: Bybit API secret
        testnet: Use Bybit testnet
        **kwargs: Additional configuration

    Returns:
        CCXTAdapter for Bybit
    """
    return CCXTExchangeFactory.create(
        "bybit",
        api_key=api_key,
        api_secret=api_secret,
        testnet=testnet,
        **kwargs,
    )


def create_kucoin(
    api_key: Optional[str] = None,
    api_secret: Optional[str] = None,
    passphrase: Optional[str] = None,
    testnet: bool = False,
    **kwargs: Any,
) -> CCXTAdapter:
    """Create a KuCoin exchange adapter.

    Args:
        api_key: KuCoin API key
        api_secret: KuCoin API secret
        passphrase: KuCoin API passphrase
        testnet: Use KuCoin sandbox
        **kwargs: Additional configuration

    Returns:
        CCXTAdapter for KuCoin
    """
    return CCXTExchangeFactory.create(
        "kucoin",
        api_key=api_key,
        api_secret=api_secret,
        passphrase=passphrase,
        testnet=testnet,
        **kwargs,
    )
