"""
Exchange API interface for crypto trading.

This module provides an abstract interface for exchange APIs
and a simulated implementation for testing.

Future implementations will support:
- Binance
- Bybit
- Kraken
- etc.
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from typing import Dict, List, Optional, Any
import time
import random

from normalization import normalize_price, normalize_lot_size


@dataclass
class Ticker:
    """Market ticker data."""
    symbol: str
    bid: float
    ask: float
    last: float
    volume: float
    timestamp: datetime


@dataclass
class OrderResult:
    """Result of an order placement."""
    order_id: str
    symbol: str
    side: str  # 'buy' or 'sell'
    order_type: str  # 'market', 'limit'
    quantity: float
    price: float
    status: str  # 'pending', 'filled', 'cancelled', 'rejected'
    filled_quantity: float = 0.0
    average_price: float = 0.0
    timestamp: Optional[datetime] = None
    error: Optional[str] = None


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

    Provides a unified interface for interacting with different
    cryptocurrency exchanges.
    """

    def __init__(
        self,
        api_key: str = "",
        api_secret: str = "",
        testnet: bool = True
    ):
        """
        Initialize exchange API.

        Args:
            api_key: API key for authentication
            api_secret: API secret for authentication
            testnet: Use testnet/sandbox if True
        """
        self.api_key = api_key
        self.api_secret = api_secret
        self.testnet = testnet
        self._connected = False

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

        Returns:
            True if connection successful
        """
        pass

    @abstractmethod
    def disconnect(self) -> None:
        """Disconnect from the exchange."""
        pass

    @abstractmethod
    def get_ticker(self, symbol: str) -> Optional[Dict[str, Any]]:
        """
        Get current ticker data for a symbol.

        Args:
            symbol: Trading pair symbol

        Returns:
            Dictionary with bid, ask, last, volume, or None on error
        """
        pass

    @abstractmethod
    def get_balance(self) -> Dict[str, float]:
        """
        Get account balances.

        Returns:
            Dictionary of {asset: balance}
        """
        pass

    @abstractmethod
    def place_market_buy(
        self,
        symbol: str,
        quantity: float
    ) -> Optional[Dict[str, Any]]:
        """
        Place a market buy order.

        Args:
            symbol: Trading pair symbol
            quantity: Amount to buy

        Returns:
            Order result dictionary or None on error
        """
        pass

    @abstractmethod
    def place_market_sell(
        self,
        symbol: str,
        quantity: float
    ) -> Optional[Dict[str, Any]]:
        """
        Place a market sell order.

        Args:
            symbol: Trading pair symbol
            quantity: Amount to sell

        Returns:
            Order result dictionary or None on error
        """
        pass

    @abstractmethod
    def place_limit_buy(
        self,
        symbol: str,
        quantity: float,
        price: float
    ) -> Optional[Dict[str, Any]]:
        """
        Place a limit buy order.

        Args:
            symbol: Trading pair symbol
            quantity: Amount to buy
            price: Limit price

        Returns:
            Order result dictionary or None on error
        """
        pass

    @abstractmethod
    def place_limit_sell(
        self,
        symbol: str,
        quantity: float,
        price: float
    ) -> Optional[Dict[str, Any]]:
        """
        Place a limit sell order.

        Args:
            symbol: Trading pair symbol
            quantity: Amount to sell
            price: Limit price

        Returns:
            Order result dictionary or None on error
        """
        pass

    @abstractmethod
    def cancel_order(
        self,
        symbol: str,
        order_id: str
    ) -> bool:
        """
        Cancel an open order.

        Args:
            symbol: Trading pair symbol
            order_id: Order ID to cancel

        Returns:
            True if cancelled successfully
        """
        pass

    @abstractmethod
    def get_open_orders(
        self,
        symbol: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """
        Get all open orders.

        Args:
            symbol: Optional symbol filter

        Returns:
            List of open order dictionaries
        """
        pass

    @abstractmethod
    def get_positions(
        self,
        symbol: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """
        Get all open positions.

        Args:
            symbol: Optional symbol filter

        Returns:
            List of position dictionaries
        """
        pass

    def is_connected(self) -> bool:
        """Check if connected to exchange."""
        return self._connected


class SimulatedExchange(ExchangeAPI):
    """
    Simulated exchange for testing and development.

    Simulates order execution with configurable parameters.
    """

    def __init__(
        self,
        api_key: str = "",
        api_secret: str = "",
        testnet: bool = True,
        initial_balance: float = 10000.0,
        base_price: float = 50000.0,
        volatility: float = 0.001
    ):
        """
        Initialize simulated exchange.

        Args:
            api_key: Ignored for simulation
            api_secret: Ignored for simulation
            testnet: Ignored for simulation
            initial_balance: Starting USDT balance
            base_price: Starting price for simulated market
            volatility: Price volatility factor
        """
        super().__init__(api_key, api_secret, testnet)

        self.initial_balance = initial_balance
        self.base_price = base_price
        self.volatility = volatility

        # Account state
        self.balances = {"USDT": initial_balance, "BTC": 0.0}
        self.positions: List[Dict] = []
        self.orders: Dict[str, Dict] = {}
        self._order_counter = 0

        # Market state
        self._current_price = base_price
        self._last_update = time.time()

    def connect(self) -> bool:
        """Connect to simulated exchange."""
        self._connected = True
        return True

    def disconnect(self) -> None:
        """Disconnect from simulated exchange."""
        self._connected = False

    def _update_price(self) -> None:
        """Update simulated price with random walk."""
        now = time.time()
        elapsed = now - self._last_update

        if elapsed > 0.1:  # Update every 100ms
            change = random.gauss(0, self.volatility)
            self._current_price *= (1 + change)
            self._current_price = max(self._current_price, 100)  # Floor
            self._last_update = now

    def get_ticker(self, symbol: str) -> Optional[Dict[str, Any]]:
        """Get simulated ticker data."""
        self._update_price()

        # Simulate spread (0.01%)
        spread = self._current_price * 0.0001
        bid = self._current_price - spread / 2
        ask = self._current_price + spread / 2

        return {
            "symbol": symbol,
            "bid": bid,
            "ask": ask,
            "last": self._current_price,
            "volume": random.uniform(100, 1000),
            "timestamp": datetime.now()
        }

    def get_balance(self) -> Dict[str, float]:
        """Get account balances."""
        return self.balances.copy()

    def _generate_order_id(self) -> str:
        """Generate unique order ID."""
        self._order_counter += 1
        return f"SIM_{self._order_counter}_{int(time.time() * 1000)}"

    def place_market_buy(
        self,
        symbol: str,
        quantity: float
    ) -> Optional[Dict[str, Any]]:
        """Place simulated market buy."""
        self._update_price()

        # Execute at ask
        spread = self._current_price * 0.0001
        execution_price = self._current_price + spread / 2

        # Calculate cost
        cost = quantity * execution_price

        # Check balance
        if cost > self.balances.get("USDT", 0):
            return {
                "order_id": None,
                "status": "rejected",
                "error": "Insufficient balance"
            }

        # Execute
        self.balances["USDT"] -= cost
        base_asset = symbol.replace("USDT", "")
        self.balances[base_asset] = self.balances.get(base_asset, 0) + quantity

        order_id = self._generate_order_id()

        result = {
            "order_id": order_id,
            "symbol": symbol,
            "side": "buy",
            "type": "market",
            "quantity": quantity,
            "price": execution_price,
            "status": "filled",
            "filled_quantity": quantity,
            "average_price": execution_price,
            "timestamp": datetime.now()
        }

        self.orders[order_id] = result
        return result

    def place_market_sell(
        self,
        symbol: str,
        quantity: float
    ) -> Optional[Dict[str, Any]]:
        """Place simulated market sell."""
        self._update_price()

        base_asset = symbol.replace("USDT", "")

        # Check balance
        if quantity > self.balances.get(base_asset, 0):
            return {
                "order_id": None,
                "status": "rejected",
                "error": "Insufficient balance"
            }

        # Execute at bid
        spread = self._current_price * 0.0001
        execution_price = self._current_price - spread / 2

        # Execute
        self.balances[base_asset] -= quantity
        self.balances["USDT"] += quantity * execution_price

        order_id = self._generate_order_id()

        result = {
            "order_id": order_id,
            "symbol": symbol,
            "side": "sell",
            "type": "market",
            "quantity": quantity,
            "price": execution_price,
            "status": "filled",
            "filled_quantity": quantity,
            "average_price": execution_price,
            "timestamp": datetime.now()
        }

        self.orders[order_id] = result
        return result

    def place_limit_buy(
        self,
        symbol: str,
        quantity: float,
        price: float
    ) -> Optional[Dict[str, Any]]:
        """Place simulated limit buy (immediately fills if price >= ask)."""
        self._update_price()

        spread = self._current_price * 0.0001
        ask = self._current_price + spread / 2

        order_id = self._generate_order_id()

        if price >= ask:
            # Fill immediately
            return self.place_market_buy(symbol, quantity)

        # Create pending order
        result = {
            "order_id": order_id,
            "symbol": symbol,
            "side": "buy",
            "type": "limit",
            "quantity": quantity,
            "price": price,
            "status": "pending",
            "filled_quantity": 0,
            "average_price": 0,
            "timestamp": datetime.now()
        }

        self.orders[order_id] = result
        return result

    def place_limit_sell(
        self,
        symbol: str,
        quantity: float,
        price: float
    ) -> Optional[Dict[str, Any]]:
        """Place simulated limit sell (immediately fills if price <= bid)."""
        self._update_price()

        spread = self._current_price * 0.0001
        bid = self._current_price - spread / 2

        order_id = self._generate_order_id()

        if price <= bid:
            # Fill immediately
            return self.place_market_sell(symbol, quantity)

        # Create pending order
        result = {
            "order_id": order_id,
            "symbol": symbol,
            "side": "sell",
            "type": "limit",
            "quantity": quantity,
            "price": price,
            "status": "pending",
            "filled_quantity": 0,
            "average_price": 0,
            "timestamp": datetime.now()
        }

        self.orders[order_id] = result
        return result

    def cancel_order(
        self,
        symbol: str,
        order_id: str
    ) -> bool:
        """Cancel a pending order."""
        if order_id in self.orders:
            order = self.orders[order_id]
            if order["status"] == "pending":
                order["status"] = "cancelled"
                return True
        return False

    def get_open_orders(
        self,
        symbol: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """Get all open/pending orders."""
        orders = [
            o for o in self.orders.values()
            if o["status"] == "pending"
        ]
        if symbol:
            orders = [o for o in orders if o["symbol"] == symbol]
        return orders

    def get_positions(
        self,
        symbol: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """Get current positions (based on balances)."""
        positions = []

        for asset, balance in self.balances.items():
            if asset != "USDT" and balance > 0:
                sym = f"{asset}USDT"
                if symbol is None or sym == symbol:
                    positions.append({
                        "symbol": sym,
                        "quantity": balance,
                        "entry_price": self._current_price,  # Simplified
                        "current_price": self._current_price,
                        "unrealized_pnl": 0.0
                    })

        return positions


class BinanceExchange(ExchangeAPI):
    """
    Binance exchange API implementation.

    TODO: Implement actual Binance API integration.
    """

    def __init__(
        self,
        api_key: str = "",
        api_secret: str = "",
        testnet: bool = True
    ):
        super().__init__(api_key, api_secret, testnet)
        # TODO: Initialize Binance client
        # from binance.client import Client
        # self.client = Client(api_key, api_secret, testnet=testnet)

    def connect(self) -> bool:
        """Connect to Binance."""
        # TODO: Implement connection
        raise NotImplementedError("Binance integration not yet implemented")

    def disconnect(self) -> None:
        """Disconnect from Binance."""
        pass

    def get_ticker(self, symbol: str) -> Optional[Dict[str, Any]]:
        """Get ticker from Binance."""
        raise NotImplementedError("Binance integration not yet implemented")

    def get_balance(self) -> Dict[str, float]:
        """Get balance from Binance."""
        raise NotImplementedError("Binance integration not yet implemented")

    def place_market_buy(
        self,
        symbol: str,
        quantity: float
    ) -> Optional[Dict[str, Any]]:
        """Place market buy on Binance."""
        raise NotImplementedError("Binance integration not yet implemented")

    def place_market_sell(
        self,
        symbol: str,
        quantity: float
    ) -> Optional[Dict[str, Any]]:
        """Place market sell on Binance."""
        raise NotImplementedError("Binance integration not yet implemented")

    def place_limit_buy(
        self,
        symbol: str,
        quantity: float,
        price: float
    ) -> Optional[Dict[str, Any]]:
        """Place limit buy on Binance."""
        raise NotImplementedError("Binance integration not yet implemented")

    def place_limit_sell(
        self,
        symbol: str,
        quantity: float,
        price: float
    ) -> Optional[Dict[str, Any]]:
        """Place limit sell on Binance."""
        raise NotImplementedError("Binance integration not yet implemented")

    def cancel_order(
        self,
        symbol: str,
        order_id: str
    ) -> bool:
        """Cancel order on Binance."""
        raise NotImplementedError("Binance integration not yet implemented")

    def get_open_orders(
        self,
        symbol: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """Get open orders from Binance."""
        raise NotImplementedError("Binance integration not yet implemented")

    def get_positions(
        self,
        symbol: Optional[str] = None
    ) -> List[Dict[str, Any]]:
        """Get positions from Binance."""
        raise NotImplementedError("Binance integration not yet implemented")


def create_exchange(
    exchange_name: str,
    api_key: str = "",
    api_secret: str = "",
    testnet: bool = True,
    **kwargs
) -> ExchangeAPI:
    """
    Factory function to create exchange API instance.

    Args:
        exchange_name: Name of exchange ('binance', 'simulated', etc.)
        api_key: API key
        api_secret: API secret
        testnet: Use testnet mode
        **kwargs: Additional exchange-specific parameters

    Returns:
        ExchangeAPI instance
    """
    exchange_name = exchange_name.lower()

    if exchange_name == "simulated" or exchange_name == "sim":
        return SimulatedExchange(
            api_key=api_key,
            api_secret=api_secret,
            testnet=testnet,
            **kwargs
        )

    elif exchange_name == "binance":
        return BinanceExchange(
            api_key=api_key,
            api_secret=api_secret,
            testnet=testnet
        )

    else:
        # Default to simulated for safety
        print(f"Warning: Unknown exchange '{exchange_name}', using simulated")
        return SimulatedExchange(
            api_key=api_key,
            api_secret=api_secret,
            testnet=testnet,
            **kwargs
        )
