"""
OKX Exchange Client for Live Trading.

Handles connection to OKX API, order execution, and account management.
Supports demo/sandbox mode for paper trading.
"""
import logging
from typing import Optional
from datetime import datetime, timezone

import ccxt

from .config import OKXConfig, TradingConfig

logger = logging.getLogger(__name__)


class OKXClient:
    """
    OKX Exchange client wrapper using CCXT.
    
    Supports both live and demo (sandbox) trading modes.
    Demo mode uses OKX's official sandbox environment.
    """
    
    def __init__(self, okx_config: OKXConfig, trading_config: TradingConfig):
        self.okx_config = okx_config
        self.trading_config = trading_config
        self.exchange: Optional[ccxt.okx] = None
        self._setup_exchange()
    
    def _setup_exchange(self) -> None:
        """Initialize CCXT OKX exchange instance."""
        self.okx_config.validate()
        
        config = {
            'apiKey': self.okx_config.api_key,
            'secret': self.okx_config.secret,
            'password': self.okx_config.password,
            'sandbox': self.okx_config.demo_mode,
            'options': {
                'defaultType': 'swap',  # Perpetual futures
            },
            'timeout': 30000,
            'enableRateLimit': True,
        }
        
        self.exchange = ccxt.okx(config)
        
        mode_str = "DEMO/SANDBOX" if self.okx_config.demo_mode else "LIVE"
        logger.info(f"OKX Exchange initialized in {mode_str} mode")
        
        # Configure trading settings
        self._configure_trading_settings()
    
    def _configure_trading_settings(self) -> None:
        """Configure leverage and margin mode."""
        symbol = self.trading_config.eth_symbol
        leverage = self.trading_config.leverage
        margin_mode = self.trading_config.margin_mode
        
        try:
            # Set position mode to one-way (net) first
            self.exchange.set_position_mode(False)  # False = one-way mode
            logger.info("Position mode set to One-Way (Net)")
        except Exception as e:
            # Position mode might already be set
            logger.debug(f"Position mode setting: {e}")
        
        try:
            # Set margin mode with leverage parameter (required by OKX)
            self.exchange.set_margin_mode(
                margin_mode,
                symbol,
                params={'lever': leverage}
            )
            logger.info(
                f"Margin mode set to {margin_mode} with {leverage}x leverage "
                f"for {symbol}"
            )
        except Exception as e:
            logger.warning(f"Could not set margin mode: {e}")
            # Try setting leverage separately
            try:
                self.exchange.set_leverage(leverage, symbol)
                logger.info(f"Leverage set to {leverage}x for {symbol}")
            except Exception as e2:
                logger.warning(f"Could not set leverage: {e2}")
    
    def fetch_ohlcv(
        self, 
        symbol: str, 
        timeframe: str = "1h", 
        limit: int = 500
    ) -> list:
        """
        Fetch OHLCV candle data.
        
        Args:
            symbol: Trading pair symbol (e.g., "ETH/USDT:USDT")
            timeframe: Candle timeframe (e.g., "1h")
            limit: Number of candles to fetch
            
        Returns:
            List of OHLCV data
        """
        return self.exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
    
    def get_balance(self) -> dict:
        """
        Get account balance.
        
        Returns:
            Balance dictionary with 'total' and 'free' USDT amounts
        """
        balance = self.exchange.fetch_balance()
        return {
            'total': balance.get('USDT', {}).get('total', 0),
            'free': balance.get('USDT', {}).get('free', 0),
        }
    
    def get_positions(self) -> list:
        """
        Get open positions.
        
        Returns:
            List of open position dictionaries
        """
        positions = self.exchange.fetch_positions()
        return [p for p in positions if float(p.get('contracts', 0)) != 0]
    
    def get_position(self, symbol: str) -> Optional[dict]:
        """
        Get position for a specific symbol.
        
        Args:
            symbol: Trading pair symbol
            
        Returns:
            Position dictionary or None if no position
        """
        positions = self.get_positions()
        for pos in positions:
            if pos.get('symbol') == symbol:
                return pos
        return None
    
    def place_market_order(
        self, 
        symbol: str, 
        side: str, 
        amount: float,
        reduce_only: bool = False
    ) -> dict:
        """
        Place a market order.
        
        Args:
            symbol: Trading pair symbol
            side: "buy" or "sell"
            amount: Order amount in base currency
            reduce_only: If True, only reduce existing position
            
        Returns:
            Order result dictionary
        """
        params = {
            'tdMode': self.trading_config.margin_mode,
        }
        if reduce_only:
            params['reduceOnly'] = True
        
        order = self.exchange.create_market_order(
            symbol, side, amount, params=params
        )
        logger.info(
            f"Market {side.upper()} order placed: {amount} {symbol} "
            f"@ market price, order_id={order['id']}"
        )
        return order
    
    def place_limit_order(
        self, 
        symbol: str, 
        side: str, 
        amount: float,
        price: float,
        reduce_only: bool = False
    ) -> dict:
        """
        Place a limit order.
        
        Args:
            symbol: Trading pair symbol
            side: "buy" or "sell"
            amount: Order amount in base currency
            price: Limit price
            reduce_only: If True, only reduce existing position
            
        Returns:
            Order result dictionary
        """
        params = {
            'tdMode': self.trading_config.margin_mode,
        }
        if reduce_only:
            params['reduceOnly'] = True
        
        order = self.exchange.create_limit_order(
            symbol, side, amount, price, params=params
        )
        logger.info(
            f"Limit {side.upper()} order placed: {amount} {symbol} "
            f"@ {price}, order_id={order['id']}"
        )
        return order
    
    def set_stop_loss_take_profit(
        self,
        symbol: str,
        side: str,
        amount: float,
        stop_loss_price: float,
        take_profit_price: float
    ) -> tuple:
        """
        Set stop-loss and take-profit orders.
        
        Args:
            symbol: Trading pair symbol
            side: Position side ("long" or "short")
            amount: Position size
            stop_loss_price: Stop-loss trigger price
            take_profit_price: Take-profit trigger price
            
        Returns:
            Tuple of (sl_order, tp_order)
        """
        # For long position: SL sells, TP sells
        # For short position: SL buys, TP buys
        close_side = "sell" if side == "long" else "buy"
        
        # Stop-loss order
        sl_params = {
            'tdMode': self.trading_config.margin_mode,
            'reduceOnly': True,
            'stopLossPrice': stop_loss_price,
        }
        
        try:
            sl_order = self.exchange.create_order(
                symbol, 'market', close_side, amount,
                params={
                    'tdMode': self.trading_config.margin_mode,
                    'reduceOnly': True,
                    'slTriggerPx': str(stop_loss_price),
                    'slOrdPx': '-1',  # Market price
                }
            )
            logger.info(f"Stop-loss set at {stop_loss_price}")
        except Exception as e:
            logger.warning(f"Could not set stop-loss: {e}")
            sl_order = None
        
        # Take-profit order
        try:
            tp_order = self.exchange.create_order(
                symbol, 'market', close_side, amount,
                params={
                    'tdMode': self.trading_config.margin_mode,
                    'reduceOnly': True,
                    'tpTriggerPx': str(take_profit_price),
                    'tpOrdPx': '-1',  # Market price
                }
            )
            logger.info(f"Take-profit set at {take_profit_price}")
        except Exception as e:
            logger.warning(f"Could not set take-profit: {e}")
            tp_order = None
        
        return sl_order, tp_order
    
    def close_position(self, symbol: str) -> Optional[dict]:
        """
        Close an open position.
        
        Args:
            symbol: Trading pair symbol
            
        Returns:
            Order result or None if no position
        """
        position = self.get_position(symbol)
        if not position:
            logger.info(f"No open position for {symbol}")
            return None
        
        contracts = abs(float(position.get('contracts', 0)))
        if contracts == 0:
            return None
        
        side = position.get('side', 'long')
        close_side = "sell" if side == "long" else "buy"
        
        order = self.place_market_order(
            symbol, close_side, contracts, reduce_only=True
        )
        logger.info(f"Position closed for {symbol}")
        return order
    
    def get_ticker(self, symbol: str) -> dict:
        """
        Get current ticker/price for a symbol.
        
        Args:
            symbol: Trading pair symbol
            
        Returns:
            Ticker dictionary with 'last', 'bid', 'ask' prices
        """
        return self.exchange.fetch_ticker(symbol)
    
    def get_funding_rate(self, symbol: str) -> float:
        """
        Get current funding rate for a perpetual symbol.
        
        Args:
            symbol: Trading pair symbol
            
        Returns:
            Current funding rate as decimal
        """
        try:
            funding = self.exchange.fetch_funding_rate(symbol)
            return float(funding.get('fundingRate', 0))
        except Exception as e:
            logger.warning(f"Could not fetch funding rate: {e}")
            return 0.0
