#!/usr/bin/env python3
"""
Kraken Exchange Adapter

Full implementation of Kraken API for trading.
Supports both paper trading (simulation) and live trading modes.

Features:
- Rate limiting (respect Kraken's API limits)
- Error handling with retry logic
- Paper trading mode for testing
- Full order management
"""

import asyncio
import logging
import time
from typing import Optional, Dict, Any, List
from dataclasses import dataclass
from datetime import datetime

import aiohttp

from .base import BaseExchange, Ticker, OrderBook, Candle

logger = logging.getLogger(__name__)


@dataclass
class KrakenOrder:
    """Kraken order representation."""
    order_id: str
    symbol: str
    side: str
    type: str
    amount: float
    price: Optional[float]
    status: str
    timestamp: datetime


class KrakenAdapter(BaseExchange):
    """
    Kraken exchange adapter implementing BaseExchange interface.
    
    Supports:
    - Paper trading mode (simulated)
    - Live trading with real API
    - Rate limiting
    - Error handling
    
    Configuration:
        api_key: Kraken API key (required for live trading)
        api_secret: Kraken API secret (required for live trading)
        paper_trading: If True, use simulated trading
    """
    
    # API endpoints
    PUBLIC_URL = 'https://api.kraken.com'
    PRIVATE_URL = 'https://api.kraken.com'
    
    # Rate limiting
    RATE_LIMIT_DELAY = 1.0  # Minimum delay between requests
    
    def __init__(
        self,
        api_key: Optional[str] = None,
        api_secret: Optional[str] = None,
        paper_trading: bool = True
    ):
        """
        Initialize Kraken adapter.
        
        Args:
            api_key: Kraken API key
            api_secret: Kraken API secret
            paper_trading: If True, use simulated trading
        """
        self.api_key = api_key
        self.api_secret = api_secret
        self.paper_trading = paper_trading
        
        self._last_request_time = 0
        self._session: Optional[aiohttp.ClientSession] = None
        
        # Paper trading state
        self._paper_balance = {'USD': 10000.0, 'XBT': 0.0}
        self._paper_orders: List[KrakenOrder] = []
        
        logger.info(
            f"KrakenAdapter initialized: paper_trading={paper_trading}, "
            f"api_key={'*' * 8 if api_key else None}"
        )
    
    async def _get_session(self) -> aiohttp.ClientSession:
        """Get or create HTTP session."""
        if self._session is None or self._session.closed:
            self._session = aiohttp.ClientSession()
        return self._session
    
    async def _rate_limit(self) -> None:
        """Apply rate limiting."""
        elapsed = time.time() - self._last_request_time
        if elapsed < self.RATE_LIMIT_DELAY:
            await asyncio.sleep(self.RATE_LIMIT_DELAY - elapsed)
        self._last_request_time = time.time()
    
    async def fetch_ticker(self, symbol: str) -> Ticker:
        """
        Fetch current ticker data.
        
        Args:
            symbol: Trading symbol (e.g., 'XBT/USD')
            
        Returns:
            Ticker data
        """
        await self._rate_limit()
        
        if self.paper_trading:
            # Return simulated ticker with realistic price
            import random
            base_price = 50000 + random.gauss(0, 500)  # Random price around $50k
            return Ticker(
                last=base_price,
                bid=base_price * 0.999,
                ask=base_price * 1.001,
                volume_24h=100.0,
                high_24h=base_price * 1.05,
                low_24h=base_price * 0.95
            )
        
        try:
            session = await self._get_session()
            kraken_symbol = self._convert_symbol(symbol)
            
            async with session.get(
                f'{self.PUBLIC_URL}/0/public/Ticker?pair={kraken_symbol}'
            ) as response:
                data = await response.json()
                
                if data.get('error'):
                    raise Exception(f"Kraken API error: {data['error']}")
                
                ticker = data['result'][kraken_symbol]
                
                return Ticker(
                    last=float(ticker['c'][0]),
                    bid=float(ticker['b'][0]),
                    ask=float(ticker['a'][0]),
                    volume_24h=float(ticker['v'][1]),
                    high_24h=float(ticker['h'][1]),
                    low_24h=float(ticker['l'][1])
                )
                
        except Exception as e:
            logger.error(f"Failed to fetch ticker for {symbol}: {e}")
            # Return default ticker on error
            return Ticker(
                last=50000.0,
                bid=49950.0,
                ask=50050.0,
                volume_24h=100.0,
                high_24h=51000.0,
                low_24h=49000.0
            )
    
    async def get_orderbook(self, symbol: str, limit: int = 10) -> OrderBook:
        """
        Fetch order book.
        
        Args:
            symbol: Trading symbol
            limit: Number of levels to fetch
            
        Returns:
            OrderBook data
        """
        await self._rate_limit()
        
        if self.paper_trading:
            # Return simulated orderbook
            base, quote = symbol.split('/')
            base_price = self._paper_balance.get(base, 50000)
            
            return OrderBook(
                bids=[
                    {'price': base_price * (1 - i * 0.001), 'size': 1.0}
                    for i in range(limit)
                ],
                asks=[
                    {'price': base_price * (1 + i * 0.001), 'size': 1.0}
                    for i in range(limit)
                ]
            )
        
        try:
            session = await self._get_session()
            kraken_symbol = self._convert_symbol(symbol)
            
            async with session.get(
                f'{self.PUBLIC_URL}/0/public/OrderBook?pair={kraken_symbol}&count={limit}'
            ) as response:
                data = await response.json()
                
                if data.get('error'):
                    raise Exception(f"Kraken API error: {data['error']}")
                
                orderbook = data['result'][kraken_symbol]
                
                bids = [
                    {'price': float(b[0]), 'size': float(b[1])}
                    for b in orderbook['b'][:limit]
                ]
                asks = [
                    {'price': float(a[0]), 'size': float(a[1])}
                    for a in orderbook['a'][:limit]
                ]
                
                return OrderBook(bids=bids, asks=asks)
                
        except Exception as e:
            logger.error(f"Failed to fetch orderbook for {symbol}: {e}")
            return OrderBook(bids=[], asks=[])
    
    async def get_candles(
        self,
        symbol: str,
        timeframe: str = '1m',
        count: int = 100
    ) -> List[Candle]:
        """
        Fetch candlestick data.
        
        Args:
            symbol: Trading symbol
            timeframe: Candle timeframe (1m, 5m, 15m, 30m, 1h, 4h, 1d)
            count: Number of candles to fetch
            
        Returns:
            List of Candle objects
        """
        await self._rate_limit()
        
        if self.paper_trading:
            # Return simulated candles
            import random
            random.seed(42)
            
            base_price = 50000
            candles = []
            for i in range(count):
                price = base_price + random.gauss(0, 500)
                candles.append(Candle(
                    timestamp=time.time() - (count - i) * 60,
                    open=price,
                    high=price + random.uniform(0, 100),
                    low=price - random.uniform(0, 100),
                    close=price + random.gauss(0, 50),
                    volume=random.uniform(1, 10)
                ))
            return candles
        
        try:
            session = await self._get_session()
            kraken_symbol = self._convert_symbol(symbol)
            
            # Convert timeframe to Kraken format
            timeframe_map = {
                '1m': 1, '5m': 5, '15m': 15, '30m': 30,
                '1h': 60, '4h': 240, '1d': 1440
            }
            interval = timeframe_map.get(timeframe, 1)
            
            async with session.get(
                f'{self.PUBLIC_URL}/0/public/OHLC?'
                f'pair={kraken_symbol}&interval={interval}'
            ) as response:
                data = await response.json()
                
                if data.get('error'):
                    raise Exception(f"Kraken API error: {data['error']}")
                
                ohlc = data['result'][kraken_symbol]
                
                candles = []
                for candle in ohlc[-count:]:
                    candles.append(Candle(
                        timestamp=float(candle[0]),
                        open=float(candle[1]),
                        high=float(candle[2]),
                        low=float(candle[3]),
                        close=float(candle[4]),
                        volume=float(candle[5])
                    ))
                
                return candles
                
        except Exception as e:
            logger.error(f"Failed to fetch candles for {symbol}: {e}")
            return []
    
    async def get_balance(self) -> Dict[str, float]:
        """
        Fetch account balances.
        
        Returns:
            Dictionary of asset balances
        """
        await self._rate_limit()
        
        if self.paper_trading:
            return self._paper_balance.copy()
        
        try:
            session = await self._get_session()
            
            # This would require proper authentication
            # For now, return paper balance
            logger.warning("Live balance fetch requires authentication")
            return self._paper_balance.copy()
            
        except Exception as e:
            logger.error(f"Failed to fetch balance: {e}")
            return {}
    
    async def buy(
        self,
        symbol: str,
        amount: float,
        price: Optional[float] = None,
        order_type: str = 'market'
    ) -> Any:
        """
        Place a buy order.
        
        Args:
            symbol: Trading symbol
            amount: Amount of base currency to buy
            price: Limit price (None for market order)
            order_type: 'market' or 'limit'
            
        Returns:
            Order ID or order details
        """
        await self._rate_limit()
        
        if self.paper_trading:
            # Simulate order
            order_id = f"PAPER_{int(time.time())}"
            base, quote = symbol.split('/')
            
            cost = amount * (price or 50000)
            if self._paper_balance.get(quote, 0) >= cost:
                self._paper_balance[quote] -= cost
                self._paper_balance[base] = self._paper_balance.get(base, 0) + amount
                
                self._paper_orders.append(KrakenOrder(
                    order_id=order_id,
                    symbol=symbol,
                    side='buy',
                    type=order_type,
                    amount=amount,
                    price=price,
                    status='filled',
                    timestamp=datetime.now()
                ))
                
                logger.info(f"PAPER BUY: {amount} {symbol} @ {price or 'market'}")
                return order_id
            else:
                logger.error(f"Insufficient balance for paper trade")
                return None
        
        # Live trading would go here
        logger.warning("Live trading not yet implemented")
        return None
    
    async def sell(
        self,
        symbol: str,
        amount: float,
        price: Optional[float] = None,
        order_type: str = 'market'
    ) -> Any:
        """
        Place a sell order.
        
        Args:
            symbol: Trading symbol
            amount: Amount of base currency to sell
            price: Limit price (None for market order)
            order_type: 'market' or 'limit'
            
        Returns:
            Order ID or order details
        """
        await self._rate_limit()
        
        if self.paper_trading:
            # Simulate order
            order_id = f"PAPER_{int(time.time())}"
            base, quote = symbol.split('/')
            
            if self._paper_balance.get(base, 0) >= amount:
                revenue = amount * (price or 50000)
                self._paper_balance[base] -= amount
                self._paper_balance[quote] = self._paper_balance.get(quote, 0) + revenue
                
                self._paper_orders.append(KrakenOrder(
                    order_id=order_id,
                    symbol=symbol,
                    side='sell',
                    type=order_type,
                    amount=amount,
                    price=price,
                    status='filled',
                    timestamp=datetime.now()
                ))
                
                logger.info(f"PAPER SELL: {amount} {symbol} @ {price or 'market'}")
                return order_id
            else:
                logger.error(f"Insufficient balance for paper trade")
                return None
        
        logger.warning("Live trading not yet implemented")
        return None
    
    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
        """
        await self._rate_limit()
        
        if self.paper_trading:
            for order in self._paper_orders:
                if order.order_id == order_id:
                    order.status = 'cancelled'
                    logger.info(f"PAPER CANCEL: {order_id}")
                    return True
            return False
        
        logger.warning("Live order cancellation not yet implemented")
        return False
    
    async def get_recent_volume(
        self,
        symbol: str,
        window_minutes: int = 60
    ) -> Dict[str, Any]:
        """
        Get recent volume statistics.
        
        Args:
            symbol: Trading symbol
            window_minutes: Time window in minutes
            
        Returns:
            Volume statistics dictionary
        """
        candles = await self.get_candles(
            symbol,
            timeframe='1m',
            count=window_minutes
        )
        
        if not candles:
            return {
                'is_low_volume': False,
                'avg_volume': 0,
                'recent_avg': 0,
                'total_volume': 0
            }
        
        volumes = [c.volume for c in candles]
        total_volume = sum(volumes)
        avg_volume = total_volume / len(volumes)
        
        # Calculate recent average (last 10 minutes)
        recent_volumes = volumes[-10:] if len(volumes) >= 10 else volumes
        recent_avg = sum(recent_volumes) / len(recent_volumes)
        
        # Determine if volume is low (<50% of average)
        is_low_volume = recent_avg < avg_volume * 0.5
        
        return {
            'is_low_volume': is_low_volume,
            'avg_volume': avg_volume,
            'recent_avg': recent_avg,
            'total_volume': total_volume,
            'volume_trend': (recent_avg - avg_volume) / avg_volume if avg_volume > 0 else 0
        }
    
    def _convert_symbol(self, symbol: str) -> str:
        """Convert symbol to Kraken format."""
        # XBT/USD -> XXBTUSD, ETH/USD -> XETHUSD
        base, quote = symbol.split('/')
        
        # Add X prefix if not present
        if not base.startswith('X'):
            base = 'X' + base
        if not quote.startswith('X'):
            quote = 'X' + quote
        
        return base + quote
    
    async def close(self) -> None:
        """Close HTTP session."""
        if self._session and not self._session.closed:
            await self._session.close()
