"""Kraken exchange adapter implementation using ccxt.

Kraken is one of the oldest and most reliable crypto exchanges.
This adapter provides unified access to Kraken's API via ccxt library.
"""

import asyncio
import ccxt
import logging
from typing import Optional, List, Dict, Any

from .interface import (
    ExchangeAdapter,
    OHLCV,
    Order,
    Balance,
    Ticker,
)
from exceptions import (
    AdapterException,
    AuthenticationException,
    RateLimitException,
    OrderException,
)


logger = logging.getLogger(__name__)


class KrakenAdapter(ExchangeAdapter):
    """Kraken exchange adapter.
    
    Features:
    - Spot trading
    - OHLCV data fetch
    - Order management
    - Balance queries
    - Rate limiting
    - Error handling with retry
    
    Configuration:
    - name: "kraken"
    - api_key: Your Kraken API key
    - api_secret: Your Kraken API secret
    - sandbox: Use test mode (Kraken doesn't have official testnet)
    - rate_limit: Rate limiting settings
    """
    
    # Kraken rate limits (conservative)
    DEFAULT_RATE_LIMIT = {
        "orders_per_second": 1,
        "requests_per_second": 10,
    }
    
    # Supported timeframes
    TIMEFRAMES = {
        "1m": "1m",
        "5m": "5m",
        "15m": "15m",
        "30m": "30m",
        "1h": "1h",
        "4h": "4h",
        "1d": "1d",
        "1w": "1w",
    }
    
    def __init__(self, config: Dict[str, Any]):
        """Initialize Kraken adapter.
        
        Args:
            config: Configuration dictionary with API credentials
        """
        super().__init__(config)
        self.name = "kraken"
        self._exchange: Optional[ccxt.kraken] = None
        
        # Rate limiting
        self.rate_limit = config.get('rate_limit', self.DEFAULT_RATE_LIMIT)
        self._order_semaphore = asyncio.Semaphore(
            int(self.rate_limit.get('orders_per_second', 1) * 10)
        )
        self._request_semaphore = asyncio.Semaphore(
            int(self.rate_limit.get('requests_per_second', 10) * 10)
        )
    
    async def connect(self) -> bool:
        """Connect to Kraken exchange.
        
        Returns:
            True if connection successful
        """
        try:
            # Initialize ccxt Kraken exchange
            self._exchange = ccxt.kraken({
                'enableRateLimit': True,
                'options': {
                    'defaultType': 'spot',
                    'fetchBalance': {
                        'type': 'wallet',  # or 'trade'
                    },
                },
            })
            
            # Add API credentials if provided
            if self.config.get('api_key') and self.config.get('api_secret'):
                self._exchange.apiKey = self.config['api_key']
                self._exchange.secret = self.config['api_secret']
            
            # Test connection
                # Try a simple public endpoint first
                if not self.config.get('api_key'):
                    await self._exchange.load_markets()
                    logger.info("Connected to Kraken (public API)")
                else:
                    # Test authenticated endpoint
                    await self._exchange.load_markets()
                    balance = await self._exchange.fetch_balance()
                    logger.info("Connected to Kraken (authenticated)")
                
                self._connected = True
                return True
                
        except ccxt.AuthenticationError as e:
            logger.error(f"Kraken authentication failed: {e}")
            raise AuthenticationException(
                f"Kraken authentication failed: {e}",
                exchange="kraken"
            )
        except ccxt.NetworkError as e:
            logger.error(f"Kraken network error: {e}")
            raise AdapterException(
                f"Kraken network error: {e}",
                exchange="kraken"
            )
        except Exception as e:
            logger.error(f"Failed to connect to Kraken: {e}")
            raise AdapterException(
                f"Failed to connect to Kraken: {e}",
                exchange="kraken"
            )
    
    async def disconnect(self) -> None:
        """Close connection to Kraken."""
        if self._exchange:
            await self._exchange.close()
            self._exchange = None
        self._connected = False
        logger.info("Disconnected from Kraken")
    
    async def fetch_ohlcv(
        self,
        symbol: str,
        timeframe: str = "1m",
        limit: int = 100
    ) -> List[OHLCV]:
        """Fetch OHLCV data from Kraken.
        
        Args:
            symbol: Trading pair (e.g., 'XBT/USD' or 'BTC/USD')
            timeframe: Candle timeframe
            limit: Number of candles
            
        Returns:
            List of OHLCV data points
        """
        async with self._request_semaphore:
            try:
                # Normalize symbol for Kraken
                kraken_symbol = self._normalize_symbol(symbol)
                
                # Map timeframe
                kraken_timeframe = self.TIMEFRAMES.get(timeframe, "1m")
                
                # Fetch OHLCV
                ohlcv_data = await self._exchange.fetch_ohlcv(
                    kraken_symbol,
                    kraken_timeframe,
                    limit=limit
                )
                
                # Convert to OHLCV objects
                return [
                    OHLCV(
                        timestamp=candle[0] / 1000,  # Convert ms to seconds
                        open=candle[1],
                        high=candle[2],
                        low=candle[3],
                        close=candle[4],
                        volume=candle[5],
                    )
                    for candle in ohlcv_data
                ]
                
            except ccxt.NotFound as e:
                raise AdapterException(
                    f"Symbol {symbol} not found on Kraken",
                    exchange="kraken"
                )
            except ccxt.RateLimitExceeded as e:
                logger.warning(f"Kraken rate limit exceeded, waiting...")
                await asyncio.sleep(2)
                raise RateLimitException(
                    "Kraken rate limit exceeded",
                    exchange="kraken"
                )
            except Exception as e:
                logger.error(f"Failed to fetch OHLCV: {e}")
                raise AdapterException(
                    f"Failed to fetch OHLCV: {e}",
                    exchange="kraken"
                )
    
    async def fetch_ticker(self, symbol: str) -> Ticker:
        """Fetch current ticker data.
        
        Args:
            symbol: Trading pair
            
        Returns:
            Current ticker information
        """
        async with self._request_semaphore:
            try:
                kraken_symbol = self._normalize_symbol(symbol)
                ticker = await self._exchange.fetch_ticker(kraken_symbol)
                
                return Ticker(
                    symbol=symbol,
                    bid=ticker['bid'],
                    ask=ticker['ask'],
                    last=ticker['last'],
                    high_24h=ticker['high'],
                    low_24h=ticker['low'],
                    volume_24h=ticker['quoteVolume'],
                    timestamp=ticker['timestamp'] / 1000,
                )
                
            except Exception as e:
                logger.error(f"Failed to fetch ticker: {e}")
                raise AdapterException(
                    f"Failed to fetch ticker: {e}",
                    exchange="kraken"
                )
    
    async def fetch_balance(self, currency: Optional[str] = None) -> List[Balance]:
        """Fetch account balances.
        
        Args:
            currency: Optional specific currency
            
        Returns:
            List of balances
        """
        async with self._request_semaphore:
            try:
                balance = await self._exchange.fetch_balance()
                
                # Filter to 'total' balances (ccxt format)
                balances = []
                for curr, data in balance.items():
                    if curr == 'info':
                        continue
                    if currency and curr != currency.upper():
                        continue
                    
                    total = data.get('total', 0)
                    free = data.get('free', 0)
                    used = data.get('used', 0)
                    
                    if total > 0:  # Only include non-zero balances
                        balances.append(Balance(
                            currency=curr,
                            total=total,
                            available=free,
                            used=used,
                        ))
                
                return balances
                
            except ccxt.AuthenticationError as e:
                raise AuthenticationException(
                    "Balance fetch requires authentication",
                    exchange="kraken"
                )
            except Exception as e:
                logger.error(f"Failed to fetch balance: {e}")
                raise AdapterException(
                    f"Failed to fetch balance: {e}",
                    exchange="kraken"
                )
    
    async def create_order(
        self,
        symbol: str,
        side: str,
        order_type: str,
        amount: float,
        price: Optional[float] = None
    ) -> Order:
        """Create a new order on Kraken.
        
        Args:
            symbol: Trading pair
            side: 'buy' or 'sell'
            order_type: 'market', 'limit', etc.
            amount: Amount to buy/sell
            price: Limit price (for limit orders)
            
        Returns:
            Created order
        """
        async with self._order_semaphore:
            try:
                kraken_symbol = self._normalize_symbol(symbol)
                
                # Prepare order params
                params = {}
                if order_type == 'limit' and price:
                    params['price'] = price
                
                # Create order
                order = await self._exchange.create_order(
                    kraken_symbol,
                    order_type,
                    side,
                    amount,
                    price,
                    params
                )
                
                return Order(
                    order_id=order['id'],
                    symbol=symbol,
                    side=side,
                    type=order_type,
                    amount=amount,
                    price=price,
                    status=order['status'],
                    timestamp=order['timestamp'] / 1000,
                )
                
            except ccxt.InvalidOrder as e:
                raise OrderException(
                    f"Invalid order: {e}",
                    exchange="kraken"
                )
            except ccxt.AuthenticationError as e:
                raise AuthenticationException(
                    "Order creation requires authentication",
                    exchange="kraken"
                )
            except Exception as e:
                logger.error(f"Failed to create order: {e}")
                raise OrderException(
                    f"Failed to create order: {e}",
                    exchange="kraken"
                )
    
    async def cancel_order(self, order_id: str) -> bool:
        """Cancel an open order.
        
        Args:
            order_id: Order ID to cancel
            
        Returns:
            True if cancelled successfully
        """
        async with self._order_semaphore:
            try:
                order = await self._exchange.cancel_order(order_id)
                return order['status'] in ['canceled', 'cancelled']
                
            except Exception as e:
                logger.error(f"Failed to cancel order: {e}")
                raise OrderException(
                    f"Failed to cancel order: {e}",
                    order_id=order_id,
                    exchange="kraken"
                )
    
    async def get_order(self, order_id: str) -> Optional[Order]:
        """Get order status.
        
        Args:
            order_id: Order ID
            
        Returns:
            Order if found
        """
        async with self._request_semaphore:
            try:
                order = await self._exchange.fetch_order(order_id)
                
                return Order(
                    order_id=order['id'],
                    symbol=order['symbol'],
                    side=order['side'],
                    type=order['type'],
                    amount=order['amount'],
                    price=order.get('price'),
                    filled_amount=order.get('filled', 0),
                    filled_price=order.get('average', 0),
                    status=order['status'],
                    timestamp=order['timestamp'] / 1000,
                )
                
            except ccxt.OrderNotFound:
                return None
            except Exception as e:
                logger.error(f"Failed to get order: {e}")
                raise OrderException(
                    f"Failed to get order: {e}",
                    order_id=order_id,
                    exchange="kraken"
                )
    
    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
        """
        async with self._request_semaphore:
            try:
                if symbol:
                    kraken_symbol = self._normalize_symbol(symbol)
                    orders = await self._exchange.fetch_open_orders(kraken_symbol)
                else:
                    orders = await self._exchange.fetch_open_orders()
                
                return [
                    Order(
                        order_id=order['id'],
                        symbol=order['symbol'],
                        side=order['side'],
                        type=order['type'],
                        amount=order['amount'],
                        price=order.get('price'),
                        filled_amount=order.get('filled', 0),
                        status=order['status'],
                        timestamp=order['timestamp'] / 1000,
                    )
                    for order in orders
                ]
                
            except Exception as e:
                logger.error(f"Failed to fetch open orders: {e}")
                raise AdapterException(
                    f"Failed to fetch open orders: {e}",
                    exchange="kraken"
                )
    
    async def fetch_trades(
        self,
        symbol: str,
        limit: int = 100
    ) -> List[Dict[str, Any]]:
        """Fetch recent trade history.
        
        Args:
            symbol: Trading pair
            limit: Number of trades
            
        Returns:
            List of trade records
        """
        async with self._request_semaphore:
            try:
                kraken_symbol = self._normalize_symbol(symbol)
                trades = await self._exchange.fetch_my_trades(kraken_symbol, limit=limit)
                
                return [
                    {
                        'id': trade['id'],
                        'timestamp': trade['timestamp'] / 1000,
                        'symbol': trade['symbol'],
                        'side': trade['side'],
                        'amount': trade['amount'],
                        'price': trade['price'],
                        'cost': trade['cost'],
                        'fee': trade.get('fee', {}),
                    }
                    for trade in trades
                ]
                
            except Exception as e:
                logger.error(f"Failed to fetch trades: {e}")
                raise AdapterException(
                    f"Failed to fetch trades: {e}",
                    exchange="kraken"
                )
    
    def _normalize_symbol(self, symbol: str) -> str:
        """Normalize trading pair symbol for Kraken.
        
        Kraken uses format like 'XBT/USD' or 'BTC/USD'.
        This method ensures consistent symbol handling.
        
        Args:
            symbol: Trading pair in various formats
            
        Returns:
            Kraken-compatible symbol
        """
        # Common aliases
        symbol = symbol.upper().replace('_', '/')
        
        # BTC -> XBT (Kraken's preferred format for Bitcoin)
        if symbol.startswith('BTC/'):
            symbol = 'XBT/' + symbol.split('/')[1]
        
        return symbol
    
    async def get_multiple_symbols(
        self,
        symbols: List[str],
        timeframe: str = "1m",
        limit: int = 100
    ) -> Dict[str, List[OHLCV]]:
        """Fetch OHLCV data for multiple symbols concurrently.
        
        This is essential for multi-asset variance reduction strategy
        from 0xRicker's article (5 assets reduces volatility by 55%).
        
        Args:
            symbols: List of trading pairs (e.g., ['XBT/USD', 'ETH/USD'])
            timeframe: Candle timeframe
            limit: Number of candles per symbol
            
        Returns:
            Dictionary mapping symbol -> list of OHLCV data
        """
        logger.info(f"Fetching OHLCV for {len(symbols)} symbols: {symbols}")
        
        # Fetch all symbols concurrently
        async def fetch_single(symbol: str) -> tuple:
            try:
                data = await self.fetch_ohlcv(symbol, timeframe, limit)
                return (symbol, data)
            except Exception as e:
                logger.error(f"Failed to fetch {symbol}: {e}")
                return (symbol, [])
        
        results = await asyncio.gather(*[fetch_single(s) for s in symbols])
        
        return dict(results)
    
    async def get_orderbook(self, symbol: str, limit: int = 10) -> Dict[str, Any]:
        """Fetch order book for spread and liquidity analysis.
        
        Used for opportunity detection - identifying when market is
        "not watching" (low liquidity, wide spreads).
        
        From article: "When human attention drops, the market still sets
        prices - but those prices are lazy, stale, and exploitable."
        
        Args:
            symbol: Trading pair
            limit: Number of levels to fetch
            
        Returns:
            Order book with bids, asks, and spread metrics
        """
        async with self._request_semaphore:
            try:
                kraken_symbol = self._normalize_symbol(symbol)
                orderbook = await self._exchange.fetch_order_book(kraken_symbol, limit=limit)
                
                # Calculate spread metrics
                bids = orderbook.get('bids', [])
                asks = orderbook.get('asks', [])
                
                if bids and asks:
                    best_bid = bids[0][0]  # price
                    best_ask = asks[0][0]  # price
                    spread = best_ask - best_bid
                    spread_pct = (spread / best_bid) * 100
                    
                    # Calculate depth (total volume at top levels)
                    bid_depth = sum(b[1] for b in bids[:5])  # Top 5 levels
                    ask_depth = sum(a[1] for a in asks[:5])
                else:
                    spread = 0
                    spread_pct = 0
                    bid_depth = 0
                    ask_depth = 0
                
                return {
                    'symbol': symbol,
                    'bids': bids,
                    'asks': asks,
                    'best_bid': bids[0][0] if bids else 0,
                    'best_ask': asks[0][0] if asks else 0,
                    'spread': spread,
                    'spread_pct': spread_pct,
                    'bid_depth': bid_depth,
                    'ask_depth': ask_depth,
                    'timestamp': orderbook.get('timestamp'),
                }
                
            except Exception as e:
                logger.error(f"Failed to fetch orderbook for {symbol}: {e}")
                raise AdapterException(
                    f"Failed to fetch orderbook: {e}",
                    exchange="kraken"
                )
    
    async def get_recent_volume(
        self,
        symbol: str,
        window_minutes: int = 60
    ) -> Dict[str, Any]:
        """Get recent trading volume for liquidity analysis.
        
        Used to detect low-volume periods when arbitrage opportunities
        are more likely (humans not watching).
        
        Args:
            symbol: Trading pair
            window_minutes: Time window to analyze
            
        Returns:
            Volume statistics including average, min, max, and trend
        """
        async with self._request_semaphore:
            try:
                kraken_symbol = self._normalize_symbol(symbol)
                
                # Calculate number of 1-minute candles needed
                num_candles = max(window_minutes, 10)
                
                # Fetch OHLCV data
                ohlcv = await self._exchange.fetch_ohlcv(
                    kraken_symbol,
                    '1m',
                    limit=num_candles
                )
                
                if not ohlcv:
                    return {
                        'symbol': symbol,
                        'total_volume': 0,
                        'avg_volume': 0,
                        'min_volume': 0,
                        'max_volume': 0,
                        'volume_trend': 0,
                        'is_low_volume': True,
                    }
                
                # Extract volumes
                volumes = [candle[5] for candle in ohlcv]
                
                # Calculate statistics
                total_volume = sum(volumes)
                avg_volume = total_volume / len(volumes)
                min_volume = min(volumes)
                max_volume = max(volumes)
                
                # Calculate volume trend (comparing recent vs older)
                mid_point = len(volumes) // 2
                recent_avg = sum(volumes[mid_point:]) / len(volumes[mid_point:])
                older_avg = sum(volumes[:mid_point]) / len(volumes[:mid_point])
                
                if older_avg > 0:
                    volume_trend = (recent_avg - older_avg) / older_avg
                else:
                    volume_trend = 0
                
                # Determine if currently in low volume period
                # Threshold: below 50% of average
                is_low_volume = recent_avg < (avg_volume * 0.5)
                
                return {
                    'symbol': symbol,
                    'total_volume': total_volume,
                    'avg_volume': avg_volume,
                    'min_volume': min_volume,
                    'max_volume': max_volume,
                    'recent_avg': recent_avg,
                    'volume_trend': volume_trend,
                    'is_low_volume': is_low_volume,
                    'timestamp': ohlcv[-1][0],
                }
                
            except Exception as e:
                logger.error(f"Failed to fetch volume for {symbol}: {e}")
                raise AdapterException(
                    f"Failed to fetch volume: {e}",
                    exchange="kraken"
                )
    
    async def get_markets(self) -> List[Dict[str, Any]]:
        """Get all available markets.
        
        Returns:
            List of market information
        """
        try:
            await self._exchange.load_markets()
            return list(self._exchange.markets.values())
        except Exception as e:
            logger.error(f"Failed to fetch markets: {e}")
            return []
