"""Base exchange adapter interface."""

from abc import ABC, abstractmethod
from decimal import Decimal
from typing import Dict, List, Optional, Any
from datetime import datetime
from src.core.logger import get_logger

logger = get_logger(__name__)


class Order:
    """Order representation."""
    
    def __init__(
        self,
        symbol: str,
        side: str,  # 'buy' or 'sell'
        order_type: str,  # 'market', 'limit', etc.
        quantity: Decimal,
        price: Optional[Decimal] = None,
        **kwargs
    ):
        self.symbol = symbol
        self.side = side
        self.order_type = order_type
        self.quantity = quantity
        self.price = price
        self.extra = kwargs


class BaseExchangeAdapter(ABC):
    """Base class for exchange adapters."""
    
    def __init__(self, api_key: str, api_secret: str, sandbox: bool = False, read_only: bool = True):
        """Initialize exchange adapter.
        
        Args:
            api_key: API key
            api_secret: API secret
            sandbox: Use sandbox/testnet
            read_only: Read-only mode (no trading)
        """
        self.api_key = api_key
        self.api_secret = api_secret
        self.sandbox = sandbox
        self.read_only = read_only
        self.logger = get_logger(f"exchange.{self.__class__.__name__}")
        self._connected = False
    
    @property
    @abstractmethod
    def name(self) -> str:
        """Exchange name."""
        pass
    
    @abstractmethod
    async def connect(self) -> bool:
        """Connect to exchange.
        
        Returns:
            True if connection successful
        """
        pass
    
    @abstractmethod
    async def disconnect(self):
        """Disconnect from exchange."""
        pass
    
    @abstractmethod
    async def get_balance(self, currency: Optional[str] = None) -> Dict[str, Decimal]:
        """Get account balance.
        
        Args:
            currency: Specific currency to get balance for, or None for all
            
        Returns:
            Dictionary of currency -> balance
        """
        pass
    
    @abstractmethod
    async def get_ticker(self, symbol: str) -> Dict[str, Any]:
        """Get current ticker for symbol.
        
        Args:
            symbol: Trading pair symbol (e.g., 'BTC/USD')
            
        Returns:
            Ticker data with 'bid', 'ask', 'last', 'volume', etc.
        """
        pass
    
    @abstractmethod
    async def get_orderbook(self, symbol: str, limit: int = 20) -> Dict[str, List]:
        """Get order book for symbol.
        
        Args:
            symbol: Trading pair symbol
            limit: Number of orders per side
            
        Returns:
            Dictionary with 'bids' and 'asks' lists
        """
        pass
    
    @abstractmethod
    async def place_order(self, order: Order) -> Dict[str, Any]:
        """Place an order.
        
        Args:
            order: Order object
            
        Returns:
            Order response with order ID and status
        """
        pass
    
    @abstractmethod
    async def cancel_order(self, order_id: str, symbol: str) -> bool:
        """Cancel an order.
        
        Args:
            order_id: Order ID to cancel
            symbol: Trading pair symbol
            
        Returns:
            True if cancellation successful
        """
        pass
    
    @abstractmethod
    async def get_order_status(self, order_id: str, symbol: str) -> Dict[str, Any]:
        """Get order status.
        
        Args:
            order_id: Order ID
            symbol: Trading pair symbol
            
        Returns:
            Order status information
        """
        pass
    
    @abstractmethod
    async def get_open_orders(self, symbol: Optional[str] = None) -> List[Dict[str, Any]]:
        """Get open orders.
        
        Args:
            symbol: Optional symbol to filter by
            
        Returns:
            List of open orders
        """
        pass
    
    @abstractmethod
    async def get_positions(self, symbol: Optional[str] = None) -> List[Dict[str, Any]]:
        """Get open positions.
        
        Args:
            symbol: Optional symbol to filter by
            
        Returns:
            List of positions
        """
        pass
    
    @abstractmethod
    async def get_ohlcv(
        self,
        symbol: str,
        timeframe: str = '1h',
        since: Optional[datetime] = None,
        limit: int = 100
    ) -> List[List]:
        """Get OHLCV (candlestick) data.
        
        Args:
            symbol: Trading pair symbol
            timeframe: Timeframe (1m, 5m, 15m, 1h, 1d, etc.)
            since: Start datetime
            limit: Number of candles
            
        Returns:
            List of [timestamp, open, high, low, close, volume]
        """
        pass
    
    @abstractmethod
    async def subscribe_ticker(self, symbol: str, callback):
        """Subscribe to ticker updates via WebSocket.
        
        Args:
            symbol: Trading pair symbol
            callback: Callback function(ticker_data)
        """
        pass
    
    @abstractmethod
    async def subscribe_orderbook(self, symbol: str, callback):
        """Subscribe to order book updates via WebSocket.
        
        Args:
            symbol: Trading pair symbol
            callback: Callback function(orderbook_data)
        """
        pass
    
    @abstractmethod
    async def subscribe_trades(self, symbol: str, callback):
        """Subscribe to trade updates via WebSocket.
        
        Args:
            symbol: Trading pair symbol
            callback: Callback function(trade_data)
        """
        pass
    
    def validate_order(self, order: Order) -> bool:
        """Validate order before placing.
        
        Args:
            order: Order to validate
            
        Returns:
            True if order is valid
        """
        if self.read_only:
            self.logger.warning("Exchange is in read-only mode")
            return False
        
        if not order.symbol:
            self.logger.error("Order symbol is required")
            return False
        
        if order.quantity <= 0:
            self.logger.error("Order quantity must be positive")
            return False
        
        if order.order_type == 'limit' and not order.price:
            self.logger.error("Limit orders require a price")
            return False
        
        return True
    
    def normalize_symbol(self, symbol: str) -> str:
        """Normalize symbol format.
        
        Args:
            symbol: Symbol to normalize
            
        Returns:
            Normalized symbol
        """
        # Default implementation - override in subclasses if needed
        return symbol.upper().replace('-', '/')
    
    def get_fee_structure(self) -> Dict[str, float]:
        """Get fee structure (maker/taker fees).
        
        Returns:
            Dictionary with 'maker' and 'taker' fee percentages, and optional 'minimum'
        """
        # Default fees - override in subclasses
        return {
            'maker': 0.001,  # 0.1%
            'taker': 0.001,  # 0.1%
            'minimum': 0.0,  # Minimum fee amount
        }
    
    def extract_fee_from_order_response(self, order_response: Dict[str, Any]) -> Optional[Decimal]:
        """Extract actual fee from order response.
        
        Args:
            order_response: Order response from exchange API
            
        Returns:
            Fee amount as Decimal, or None if not available
        """
        # Default implementation - override in subclasses
        # Try common fee fields
        if 'fee' in order_response:
            try:
                return Decimal(str(order_response['fee']))
            except (ValueError, TypeError):
                pass
        
        if 'fees' in order_response:
            # Some exchanges return fees as a list
            fees = order_response['fees']
            if isinstance(fees, list) and len(fees) > 0:
                try:
                    # Sum all fees
                    total_fee = sum(Decimal(str(f.get('cost', 0) if isinstance(f, dict) else f)) for f in fees)
                    return total_fee
                except (ValueError, TypeError):
                    pass
        
        # Try 'cost' field (some exchanges use this)
        if 'cost' in order_response:
            try:
                return Decimal(str(order_response['cost']))
            except (ValueError, TypeError):
                pass
        
        return None

