"""Base exchange interface for OpenClaw trading system.

This module provides the abstract Exchange base class that defines the interface
for all exchange implementations.
"""

from abc import ABC, abstractmethod
from typing import List, Optional

from openclaw.exchange.models import Balance, Order, OrderSide, Position, Ticker


class ExchangeError(Exception):
    """Base exception for exchange-related errors."""

    def __init__(self, message: str, error_code: Optional[str] = None):
        super().__init__(message)
        self.message = message
        self.error_code = error_code

    def __str__(self) -> str:
        if self.error_code:
            return f"[{self.error_code}] {self.message}"
        return self.message


class AuthenticationError(ExchangeError):
    """Raised when exchange authentication fails."""

    pass


class InsufficientFundsError(ExchangeError):
    """Raised when account has insufficient funds for an operation."""

    pass


class InvalidOrderError(ExchangeError):
    """Raised when an order is invalid."""

    pass


class OrderNotFoundError(ExchangeError):
    """Raised when an order is not found."""

    pass


class Exchange(ABC):
    """Abstract base class for exchange implementations.

    This class defines the interface that all exchange implementations must
    follow, including methods for placing orders, querying balances, and
    fetching market data.

    Args:
        name: Exchange name identifier
        is_simulated: Whether this is a simulated/paper trading exchange
    """

    def __init__(self, name: str, is_simulated: bool = True):
        self.name = name
        self.is_simulated = is_simulated
        self._connected = False

    @property
    def is_connected(self) -> bool:
        """Check if exchange connection is active."""
        return self._connected

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

        Returns:
            True if connection successful

        Raises:
            AuthenticationError: If authentication fails
            ExchangeError: If connection fails
        """
        pass

    @abstractmethod
    async def disconnect(self) -> None:
        """Close connection to the exchange."""
        pass

    @abstractmethod
    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", "AAPL")
            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
        """
        pass

    @abstractmethod
    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
        """
        pass

    @abstractmethod
    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
        """
        pass

    @abstractmethod
    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
        """
        pass

    @abstractmethod
    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 (or single asset if specified)
        """
        pass

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

        Args:
            symbol: Optional symbol filter

        Returns:
            List of position objects
        """
        pass

    @abstractmethod
    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
        """
        pass

    async def get_balance_by_asset(self, asset: str) -> Optional[Balance]:
        """Get balance for a specific asset.

        Convenience method that filters get_balance results.

        Args:
            asset: Asset symbol (e.g., "BTC", "USDT")

        Returns:
            Balance object or None if not found
        """
        balances = await self.get_balance(asset)
        if asset:
            for balance in balances:
                if balance.asset.upper() == asset.upper():
                    return balance
        return None

    def __repr__(self) -> str:
        """String representation of the exchange."""
        mode = "simulated" if self.is_simulated else "live"
        return f"{self.__class__.__name__}(name='{self.name}', mode={mode})"
