"""Tests for ExchangeOrderManager.

This module contains tests for the ExchangeOrderManager class which provides
a unified interface for order management with support for both paper trading
and live exchange trading.
"""

import asyncio
import pytest
from datetime import datetime
from typing import Optional

from openclaw.exchange.order_manager import (
    ExchangeOrderManager,
    CreateOrderRequest,
    ClosePositionRequest,
    OrderSummary,
    PositionSummary,
    TradingModeEnum,
    TradingModeStatus,
)
from openclaw.exchange.models import OrderSide, OrderStatus, OrderType


class TestExchangeOrderManager:
    """Tests for ExchangeOrderManager class."""

    @pytest.fixture
    async def manager(self):
        """Fixture to create an ExchangeOrderManager instance."""
        manager = ExchangeOrderManager(paper_trading=True, fee_rate=0.001)
        await manager.start()
        yield manager
        await manager.stop()

    def test_initialization_paper_mode(self):
        """Test ExchangeOrderManager initialization in paper mode."""
        manager = ExchangeOrderManager(paper_trading=True)
        assert manager.paper_trading is True
        assert manager.is_paper_trading is True
        assert manager.is_live_trading is False
        assert manager.trading_mode == TradingModeEnum.PAPER

    def test_initialization_live_mode(self):
        """Test ExchangeOrderManager initialization in live mode."""
        manager = ExchangeOrderManager(paper_trading=False)
        assert manager.paper_trading is False
        assert manager.is_live_trading is True
        assert manager.trading_mode == TradingModeEnum.LIVE

    def test_fee_rate_configuration(self):
        """Test custom fee rate configuration."""
        manager = ExchangeOrderManager(fee_rate=0.002)
        assert manager.fee_rate == 0.002

    @pytest.mark.asyncio
    async def test_create_market_buy_order(self, manager):
        """Test creating a market buy order."""
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.MARKET,
            amount=0.1,
        )

        order = await manager.create_order(request)

        assert order.symbol == "BTC/USDT"
        assert order.side == OrderSide.BUY
        assert order.order_type == OrderType.MARKET
        assert order.amount == 0.1
        assert order.status in [OrderStatus.FILLED, OrderStatus.PENDING]
        assert order.order_id is not None

    @pytest.mark.asyncio
    async def test_create_market_sell_order(self, manager):
        """Test creating a market sell order."""
        request = CreateOrderRequest(
            symbol="ETH/USDT",
            side=OrderSide.SELL,
            order_type=OrderType.MARKET,
            amount=1.0,
        )

        order = await manager.create_order(request)

        assert order.symbol == "ETH/USDT"
        assert order.side == OrderSide.SELL
        assert order.order_type == OrderType.MARKET
        assert order.amount == 1.0

    @pytest.mark.asyncio
    async def test_create_limit_order(self, manager):
        """Test creating a limit order."""
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.LIMIT,
            amount=0.1,
            price=50000.0,
        )

        order = await manager.create_order(request)

        assert order.order_type == OrderType.LIMIT
        assert order.price == 50000.0
        assert order.status in [OrderStatus.OPEN, OrderStatus.PENDING]

    @pytest.mark.asyncio
    async def test_get_order(self, manager):
        """Test getting an order by ID."""
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.MARKET,
            amount=0.1,
        )

        created = await manager.create_order(request)
        retrieved = await manager.get_order(created.order_id)

        assert retrieved is not None
        assert retrieved.order_id == created.order_id
        assert retrieved.symbol == created.symbol

    @pytest.mark.asyncio
    async def test_get_order_not_found(self, manager):
        """Test getting a non-existent order."""
        order = await manager.get_order("non_existent_order_id")
        assert order is None

    @pytest.mark.asyncio
    async def test_list_orders(self, manager):
        """Test listing orders."""
        # Create a few orders
        for i in range(3):
            request = CreateOrderRequest(
                symbol=f"SYM{i}/USDT",
                side=OrderSide.BUY,
                order_type=OrderType.MARKET,
                amount=0.1,
            )
            await manager.create_order(request)

        orders = await manager.list_orders()
        assert len(orders) >= 3

    @pytest.mark.asyncio
    async def test_list_orders_with_filter(self, manager):
        """Test listing orders with filters."""
        # Create orders with different symbols
        request1 = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.MARKET,
            amount=0.1,
        )
        request2 = CreateOrderRequest(
            symbol="ETH/USDT",
            side=OrderSide.SELL,
            order_type=OrderType.MARKET,
            amount=1.0,
        )
        await manager.create_order(request1)
        await manager.create_order(request2)

        # Filter by symbol
        btc_orders = await manager.list_orders(symbol="BTC/USDT")
        assert all(o.symbol == "BTC/USDT" for o in btc_orders)

        # Filter by side
        buy_orders = await manager.list_orders(side=OrderSide.BUY)
        assert all(o.side == OrderSide.BUY for o in buy_orders)

    @pytest.mark.asyncio
    async def test_cancel_order(self, manager):
        """Test cancelling an order."""
        # Create a limit order (market orders are filled immediately)
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.LIMIT,
            amount=0.1,
            price=10000.0,  # Low price so it doesn't fill immediately
        )

        order = await manager.create_order(request)
        cancelled = await manager.cancel_order(order.order_id)

        assert cancelled is not None
        assert cancelled.status == OrderStatus.CANCELLED

    @pytest.mark.asyncio
    async def test_cancel_order_not_found(self, manager):
        """Test cancelling a non-existent order."""
        result = await manager.cancel_order("non_existent_id")
        assert result is None

    @pytest.mark.asyncio
    async def test_cancel_already_filled_order(self, manager):
        """Test cancelling an already filled order."""
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.MARKET,
            amount=0.1,
        )

        order = await manager.create_order(request)
        # Try to cancel the filled order
        cancelled = await manager.cancel_order(order.order_id)
        assert cancelled is None  # Can't cancel filled orders

    @pytest.mark.asyncio
    async def test_get_position(self, manager):
        """Test getting a position."""
        # First create an order to establish a position
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.MARKET,
            amount=0.1,
        )
        await manager.create_order(request)

        position = await manager.get_position("BTC/USDT")
        assert position is not None
        assert position.symbol == "BTC/USDT"
        assert position.side == OrderSide.BUY
        assert position.amount == 0.1

    @pytest.mark.asyncio
    async def test_get_position_not_found(self, manager):
        """Test getting a non-existent position."""
        position = await manager.get_position("NONEXISTENT/USD")
        assert position is None

    @pytest.mark.asyncio
    async def test_list_positions(self, manager):
        """Test listing all positions."""
        # Create positions in different symbols
        symbols = ["BTC/USDT", "ETH/USDT"]
        for symbol in symbols:
            request = CreateOrderRequest(
                symbol=symbol,
                side=OrderSide.BUY,
                order_type=OrderType.MARKET,
                amount=0.1,
            )
            await manager.create_order(request)

        positions = await manager.list_positions()
        assert len(positions) == len(symbols)

    @pytest.mark.asyncio
    async def test_close_position(self, manager):
        """Test closing a position."""
        # First create a position
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.MARKET,
            amount=0.1,
        )
        await manager.create_order(request)

        # Close the position
        close_request = ClosePositionRequest(symbol="BTC/USDT")
        close_order = await manager.close_position(close_request)

        assert close_order is not None
        assert close_order.side == OrderSide.SELL  # Opposite of BUY
        assert close_order.amount == 0.1

    @pytest.mark.asyncio
    async def test_close_position_not_found(self, manager):
        """Test closing a non-existent position."""
        close_request = ClosePositionRequest(symbol="NONEXISTENT/USD")
        result = await manager.close_position(close_request)
        assert result is None

    @pytest.mark.asyncio
    async def test_close_partial_position(self, manager):
        """Test closing a partial position."""
        # Create a position
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.MARKET,
            amount=1.0,
        )
        await manager.create_order(request)

        # Close half the position
        close_request = ClosePositionRequest(symbol="BTC/USDT", amount=0.5)
        close_order = await manager.close_position(close_request)

        assert close_order is not None
        assert close_order.amount == 0.5

    @pytest.mark.asyncio
    async def test_get_order_summary(self, manager):
        """Test getting order summary."""
        # Create some orders
        for _ in range(3):
            request = CreateOrderRequest(
                symbol="BTC/USDT",
                side=OrderSide.BUY,
                order_type=OrderType.MARKET,
                amount=0.1,
            )
            await manager.create_order(request)

        summary = await manager.get_order_summary()
        assert isinstance(summary, OrderSummary)
        assert summary.total_orders >= 3
        assert summary.filled_orders >= 3

    @pytest.mark.asyncio
    async def test_get_position_summaries(self, manager):
        """Test getting position summaries."""
        # Create a position
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.MARKET,
            amount=0.1,
        )
        await manager.create_order(request)

        summaries = await manager.get_position_summaries()
        assert len(summaries) >= 1
        assert isinstance(summaries[0], PositionSummary)
        assert summaries[0].symbol == "BTC/USDT"

    def test_get_trading_mode_status(self, manager):
        """Test getting trading mode status."""
        status = manager.get_trading_mode_status()
        assert isinstance(status, TradingModeStatus)
        assert status.mode in ["simulated", "live"]
        assert isinstance(status.is_live, bool)
        assert isinstance(status.daily_limit_usd, float)
        assert isinstance(status.trade_count_today, int)

    def test_set_trading_mode(self, manager):
        """Test setting trading mode."""
        # Start in paper mode
        assert manager.is_paper_trading is True

        # Switch to live mode (may fail if not configured)
        result = manager.set_trading_mode(TradingModeEnum.LIVE)
        # Result depends on configuration

        # Switch back to paper
        result = manager.set_trading_mode(TradingModeEnum.PAPER)
        assert result is True
        assert manager.is_paper_trading is True

    def test_toggle_trading_mode(self, manager):
        """Test toggling trading mode."""
        initial_mode = manager.trading_mode
        status = manager.toggle_trading_mode()
        assert isinstance(status, TradingModeStatus)
        # Mode should have changed
        assert status.mode != initial_mode.value

    @pytest.mark.asyncio
    async def test_get_account_summary(self, manager):
        """Test getting complete account summary."""
        # Create an order to have some data
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.MARKET,
            amount=0.1,
        )
        await manager.create_order(request)

        summary = await manager.get_account_summary()
        assert "orders" in summary
        assert "positions" in summary
        assert "trading_mode" in summary
        assert isinstance(summary["total_position_value"], float)
        assert isinstance(summary["total_unrealized_pnl"], float)
        assert isinstance(summary["positions_count"], int)


class TestCreateOrderRequest:
    """Tests for CreateOrderRequest model."""

    def test_create_request(self):
        """Test creating a valid order request."""
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.LIMIT,
            amount=0.1,
            price=50000.0,
        )
        assert request.symbol == "BTC/USDT"
        assert request.side == OrderSide.BUY
        assert request.order_type == OrderType.LIMIT
        assert request.amount == 0.1
        assert request.price == 50000.0

    def test_create_market_request_without_price(self):
        """Test creating a market order request without price."""
        request = CreateOrderRequest(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            order_type=OrderType.MARKET,
            amount=0.1,
        )
        assert request.price is None


class TestClosePositionRequest:
    """Tests for ClosePositionRequest model."""

    def test_close_request_full(self):
        """Test creating a full close request."""
        request = ClosePositionRequest(symbol="BTC/USDT")
        assert request.symbol == "BTC/USDT"
        assert request.amount is None  # Full close
        assert request.order_type == OrderType.MARKET

    def test_close_request_partial(self):
        """Test creating a partial close request."""
        request = ClosePositionRequest(
            symbol="BTC/USDT",
            amount=0.5,
            order_type=OrderType.LIMIT,
            price=60000.0,
        )
        assert request.symbol == "BTC/USDT"
        assert request.amount == 0.5
        assert request.order_type == OrderType.LIMIT
        assert request.price == 60000.0


class TestOrderSummary:
    """Tests for OrderSummary model."""

    def test_default_values(self):
        """Test default values."""
        summary = OrderSummary()
        assert summary.total_orders == 0
        assert summary.open_orders == 0
        assert summary.filled_orders == 0
        assert summary.cancelled_orders == 0
        assert summary.total_volume == 0.0
        assert summary.total_fees == 0.0

    def test_custom_values(self):
        """Test custom values."""
        summary = OrderSummary(
            total_orders=10,
            open_orders=2,
            filled_orders=7,
            cancelled_orders=1,
            total_volume=100000.0,
            total_fees=100.0,
        )
        assert summary.total_orders == 10
        assert summary.open_orders == 2
        assert summary.filled_orders == 7
        assert summary.cancelled_orders == 1
        assert summary.total_volume == 100000.0
        assert summary.total_fees == 100.0


class TestPositionSummary:
    """Tests for PositionSummary model."""

    def test_position_summary(self):
        """Test position summary creation."""
        summary = PositionSummary(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            amount=0.5,
            entry_price=50000.0,
            current_price=55000.0,
            unrealized_pnl=2500.0,
            unrealized_pnl_pct=10.0,
            market_value=27500.0,
        )
        assert summary.symbol == "BTC/USDT"
        assert summary.side == OrderSide.BUY
        assert summary.amount == 0.5
        assert summary.entry_price == 50000.0
        assert summary.current_price == 55000.0
        assert summary.unrealized_pnl == 2500.0
        assert summary.unrealized_pnl_pct == 10.0
        assert summary.market_value == 27500.0

    def test_position_summary_optional_price(self):
        """Test position summary without current price."""
        summary = PositionSummary(
            symbol="BTC/USDT",
            side=OrderSide.BUY,
            amount=0.5,
            entry_price=50000.0,
        )
        assert summary.current_price is None


class TestTradingModeEnum:
    """Tests for TradingModeEnum."""

    def test_enum_values(self):
        """Test enum values."""
        assert TradingModeEnum.PAPER == "paper"
        assert TradingModeEnum.LIVE == "live"

    def test_enum_comparison(self):
        """Test enum comparison."""
        assert TradingModeEnum.PAPER != TradingModeEnum.LIVE
        mode = TradingModeEnum.PAPER
        assert mode == TradingModeEnum.PAPER
