"""Order management module for OpenClaw trading system.

This module provides OrderManager for managing trading orders, including
creation, execution, cancellation, and position tracking.
"""

from __future__ import annotations

import asyncio
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Any, Dict, List, Optional, Callable

from pydantic import BaseModel, Field

from openclaw.exchange.models import Order, OrderSide, OrderStatus, OrderType, Position
from openclaw.trading.live_mode import LiveModeManager
from openclaw.utils.logging import get_logger


class OrderTimeInForce(str, Enum):
    """Order time in force."""

    GTC = "gtc"  # Good Till Cancelled
    IOC = "ioc"  # Immediate Or Cancel
    FOK = "fok"  # Fill Or Kill
    DAY = "day"  # Day order


class OrderRequest(BaseModel):
    """Request to create a new order.

    Attributes:
        symbol: Trading pair/symbol (e.g., "BTC/USDT")
        side: Buy or sell
        order_type: Market, limit, stop_loss, etc.
        amount: Order quantity
        price: Order price (required for limit orders)
        stop_price: Stop price (required for stop orders)
        time_in_force: Order time in force
    """

    symbol: str = Field(..., description="Trading symbol")
    side: OrderSide = Field(..., description="Buy or sell")
    order_type: OrderType = Field(default=OrderType.MARKET, description="Order type")
    amount: float = Field(..., gt=0, description="Order quantity")
    price: Optional[float] = Field(default=None, ge=0, description="Limit price")
    stop_price: Optional[float] = Field(default=None, ge=0, description="Stop price")
    time_in_force: OrderTimeInForce = Field(
        default=OrderTimeInForce.GTC, description="Time in force"
    )

    def model_post_init(self, __context: Any) -> None:
        """Validate order request."""
        if self.order_type == OrderType.LIMIT and self.price is None:
            raise ValueError("Limit orders require a price")
        if self.order_type in (OrderType.STOP_LOSS, OrderType.TAKE_PROFIT) and self.stop_price is None:
            raise ValueError("Stop orders require a stop_price")


class PositionUpdate(BaseModel):
    """Position update event.

    Attributes:
        symbol: Trading symbol
        side: Position side
        amount: Position amount change
        price: Price of the update
        timestamp: Update timestamp
    """

    symbol: str = Field(..., description="Trading symbol")
    side: OrderSide = Field(..., description="Position side")
    amount: float = Field(..., description="Amount change")
    price: float = Field(..., description="Price")
    timestamp: datetime = Field(default_factory=datetime.now, description="Timestamp")


class OrderFill(BaseModel):
    """Order fill event.

    Attributes:
        fill_id: Unique fill identifier
        order_id: Order ID
        symbol: Trading symbol
        side: Trade side
        amount: Filled amount
        price: Fill price
        fee: Trading fee
        timestamp: Fill timestamp
    """

    fill_id: str = Field(..., description="Fill ID")
    order_id: str = Field(..., description="Order ID")
    symbol: str = Field(..., description="Trading symbol")
    side: OrderSide = Field(..., description="Trade side")
    amount: float = Field(..., gt=0, description="Filled amount")
    price: float = Field(..., gt=0, description="Fill price")
    fee: float = Field(default=0.0, ge=0, description="Trading fee")
    timestamp: datetime = Field(default_factory=datetime.now, description="Timestamp")


@dataclass
class OrderFilters:
    """Filters for querying orders."""

    symbol: Optional[str] = None
    side: Optional[OrderSide] = None
    status: Optional[OrderStatus] = None
    order_type: Optional[OrderType] = None
    start_time: Optional[datetime] = None
    end_time: Optional[datetime] = None
    agent_id: Optional[str] = None


class OrderManager:
    """Manager for trading orders and positions.

    Handles order lifecycle, execution simulation, position tracking,
    and integration with paper trading mode.

    Args:
        live_mode_manager: Optional LiveModeManager for trading mode control
        fee_rate: Trading fee rate (default 0.001 = 0.1%)
        paper_trading: Whether to use paper trading simulation
    """

    def __init__(
        self,
        live_mode_manager: Optional[LiveModeManager] = None,
        fee_rate: float = 0.001,
        paper_trading: bool = True,
    ):
        self.live_mode_manager = live_mode_manager
        self.fee_rate = fee_rate
        self.paper_trading = paper_trading
        self.logger = get_logger("trading.order_manager")

        # Order storage
        self._orders: Dict[str, Order] = {}
        self._order_fills: Dict[str, List[OrderFill]] = {}

        # Position tracking
        self._positions: Dict[str, Position] = {}
        self._position_history: List[PositionUpdate] = []

        # Balance tracking (for paper trading)
        self._balances: Dict[str, Dict[str, float]] = {}  # agent_id -> {asset: balance}

        # Callbacks
        self._order_callbacks: List[Callable[[Order], None]] = []
        self._fill_callbacks: List[Callable[[OrderFill], None]] = []
        self._position_callbacks: List[Callable[[Position], None]] = []

        # Background task
        self._running = False
        self._update_task: Optional[asyncio.Task] = None
        self._lock = asyncio.Lock()

        self.logger.info(
            f"OrderManager initialized: paper_trading={paper_trading}, fee_rate={fee_rate}"
        )

    async def start(self) -> None:
        """Start the order manager background task."""
        self._running = True
        self._update_task = asyncio.create_task(self._background_processor())
        self.logger.info("Order manager started")

    async def stop(self) -> None:
        """Stop the order manager background task."""
        self._running = False
        if self._update_task:
            self._update_task.cancel()
            try:
                await self._update_task
            except asyncio.CancelledError:
                pass
        self.logger.info("Order manager stopped")

    async def _background_processor(self) -> None:
        """Background task for processing orders."""
        while self._running:
            try:
                await asyncio.sleep(1)  # Process every second

                async with self._lock:
                    await self._process_pending_orders()
                    await self._check_stop_orders()

            except Exception as e:
                self.logger.error(f"Error in background processor: {e}")

    async def _process_pending_orders(self) -> None:
        """Process pending orders based on their type."""
        for order in list(self._orders.values()):
            if order.status == OrderStatus.PENDING:
                if order.order_type == OrderType.MARKET:
                    await self._execute_market_order(order)
                elif order.order_type == OrderType.LIMIT:
                    await self._check_limit_order(order)

    async def _check_stop_orders(self) -> None:
        """Check and trigger stop orders."""
        # In real implementation, this would check current market prices
        # For paper trading, we simulate price movements
        pass

    async def _execute_market_order(self, order: Order) -> None:
        """Execute a market order immediately."""
        # Simulate market execution
        fill_price = order.price or await self._get_market_price(order.symbol, order.side)

        if fill_price <= 0:
            order.status = OrderStatus.REJECTED
            self.logger.warning(f"Market order {order.order_id} rejected: invalid price")
            return

        # Create fill
        fill = OrderFill(
            fill_id=f"fill_{uuid.uuid4().hex[:8]}",
            order_id=order.order_id,
            symbol=order.symbol,
            side=order.side,
            amount=order.amount,
            price=fill_price,
            fee=order.amount * fill_price * self.fee_rate,
        )

        # Update order
        order.filled_amount = order.amount
        order.status = OrderStatus.FILLED
        order.updated_at = datetime.now()

        # Store fill
        if order.order_id not in self._order_fills:
            self._order_fills[order.order_id] = []
        self._order_fills[order.order_id].append(fill)

        # Update positions
        await self._update_position(fill)

        # Notify callbacks
        await self._notify_fill(fill)
        await self._notify_order_update(order)

        self.logger.info(
            f"Market order {order.order_id} filled: {order.side} {order.amount} "
            f"{order.symbol} @ ${fill_price:.2f}"
        )

    async def _check_limit_order(self, order: Order) -> None:
        """Check if a limit order can be filled."""
        current_price = await self._get_market_price(order.symbol, order.side)

        if order.price is None:
            return

        # Check if limit conditions are met
        can_fill = False
        if order.side == OrderSide.BUY and current_price <= order.price:
            can_fill = True
        elif order.side == OrderSide.SELL and current_price >= order.price:
            can_fill = True

        if can_fill:
            await self._execute_limit_order(order, current_price)

    async def _execute_limit_order(self, order: Order, price: float) -> None:
        """Execute a limit order."""
        fill = OrderFill(
            fill_id=f"fill_{uuid.uuid4().hex[:8]}",
            order_id=order.order_id,
            symbol=order.symbol,
            side=order.side,
            amount=order.amount,
            price=price,
            fee=order.amount * price * self.fee_rate,
        )

        order.filled_amount = order.amount
        order.status = OrderStatus.FILLED
        order.updated_at = datetime.now()

        if order.order_id not in self._order_fills:
            self._order_fills[order.order_id] = []
        self._order_fills[order.order_id].append(fill)

        await self._update_position(fill)
        await self._notify_fill(fill)
        await self._notify_order_update(order)

        self.logger.info(
            f"Limit order {order.order_id} filled: {order.side} {order.amount} "
            f"{order.symbol} @ ${price:.2f}"
        )

    async def _get_market_price(self, symbol: str, side: OrderSide) -> float:
        """Get current market price for a symbol.

        In paper trading mode, this simulates prices.
        In live mode, this would fetch from exchange.
        """
        # Simulated price based on symbol hash
        base_prices = {
            "BTC/USDT": 65000.0,
            "ETH/USDT": 3500.0,
            "AAPL": 175.0,
            "GOOGL": 140.0,
            "MSFT": 420.0,
            "AMZN": 180.0,
            "TSLA": 250.0,
        }

        base_price = base_prices.get(symbol, 100.0)

        # Add some randomness
        import random

        noise = random.uniform(-0.001, 0.001)
        price = base_price * (1 + noise)

        return round(price, 2)

    async def _update_position(self, fill: OrderFill) -> None:
        """Update position based on fill."""
        symbol = fill.symbol

        async with self._lock:
            if symbol not in self._positions:
                # New position
                self._positions[symbol] = Position(
                    symbol=symbol,
                    side=fill.side,
                    amount=fill.amount,
                    entry_price=fill.price,
                    current_price=fill.price,
                )
            else:
                position = self._positions[symbol]

                if position.side == fill.side:
                    # Adding to existing position
                    total_value = position.amount * position.entry_price + fill.amount * fill.price
                    position.amount += fill.amount
                    position.entry_price = total_value / position.amount
                else:
                    # Reducing or reversing position
                    if fill.amount < position.amount:
                        # Partial close
                        position.amount -= fill.amount
                    elif fill.amount == position.amount:
                        # Full close
                        del self._positions[symbol]
                    else:
                        # Position reversal
                        position.side = fill.side
                        position.amount = fill.amount - position.amount
                        position.entry_price = fill.price

                if symbol in self._positions:
                    position.current_price = fill.price

            # Record update
            update = PositionUpdate(
                symbol=symbol,
                side=fill.side,
                amount=fill.amount,
                price=fill.price,
            )
            self._position_history.append(update)

            # Notify
            if symbol in self._positions:
                await self._notify_position_update(self._positions[symbol])

    async def create_order(
        self, request: OrderRequest, agent_id: Optional[str] = None
    ) -> Order:
        """Create a new order.

        Args:
            request: Order creation request
            agent_id: Optional agent ID for the order

        Returns:
            Created order
        """
        order_id = f"order_{uuid.uuid4().hex[:12]}"

        order = Order(
            order_id=order_id,
            symbol=request.symbol.upper(),
            side=request.side,
            order_type=request.order_type,
            amount=request.amount,
            price=request.price,
            status=OrderStatus.PENDING,
            filled_amount=0.0,
            client_order_id=f"{agent_id or 'manual'}_{order_id}" if agent_id else None,
        )

        async with self._lock:
            self._orders[order_id] = order

        self.logger.info(
            f"Order created: {order_id} - {request.side} {request.amount} {request.symbol}"
        )

        # For market orders, execute immediately
        if request.order_type == OrderType.MARKET:
            await self._execute_market_order(order)
        elif request.order_type == OrderType.LIMIT:
            order.status = OrderStatus.OPEN
            await self._notify_order_update(order)

        return order

    async def cancel_order(self, order_id: str) -> Optional[Order]:
        """Cancel an open order.

        Args:
            order_id: Order ID to cancel

        Returns:
            Cancelled order or None if not found
        """
        async with self._lock:
            order = self._orders.get(order_id)
            if not order:
                return None

            if order.status not in (OrderStatus.OPEN, OrderStatus.PENDING, OrderStatus.PARTIALLY_FILLED):
                self.logger.warning(f"Cannot cancel order {order_id}: status is {order.status}")
                return None

            order.status = OrderStatus.CANCELLED
            order.updated_at = datetime.now()

            await self._notify_order_update(order)
            self.logger.info(f"Order cancelled: {order_id}")

            return order

    async def get_order(self, order_id: str) -> Optional[Order]:
        """Get order by ID.

        Args:
            order_id: Order ID

        Returns:
            Order or None if not found
        """
        return self._orders.get(order_id)

    async def get_orders(
        self,
        filters: Optional[OrderFilters] = None,
        limit: int = 100,
        offset: int = 0,
    ) -> List[Order]:
        """Get orders with optional filtering.

        Args:
            filters: Optional filters
            limit: Maximum number of orders to return
            offset: Offset for pagination

        Returns:
            List of orders
        """
        filters = filters or OrderFilters()

        orders = list(self._orders.values())

        # Apply filters
        if filters.symbol:
            orders = [o for o in orders if o.symbol.upper() == filters.symbol.upper()]
        if filters.side:
            orders = [o for o in orders if o.side == filters.side]
        if filters.status:
            orders = [o for o in orders if o.status == filters.status]
        if filters.order_type:
            orders = [o for o in orders if o.order_type == filters.order_type]
        if filters.start_time:
            orders = [o for o in orders if o.created_at >= filters.start_time]
        if filters.end_time:
            orders = [o for o in orders if o.created_at <= filters.end_time]

        # Sort by creation time descending
        orders.sort(key=lambda o: o.created_at, reverse=True)

        return orders[offset : offset + limit]

    async def get_position(self, symbol: str) -> Optional[Position]:
        """Get position for a symbol.

        Args:
            symbol: Trading symbol

        Returns:
            Position or None if not found
        """
        return self._positions.get(symbol.upper())

    async def get_all_positions(self) -> List[Position]:
        """Get all positions.

        Returns:
            List of positions
        """
        return list(self._positions.values())

    async def get_order_fills(self, order_id: str) -> List[OrderFill]:
        """Get fills for an order.

        Args:
            order_id: Order ID

        Returns:
            List of fills
        """
        return self._order_fills.get(order_id, [])

    async def get_account_summary(self) -> Dict[str, Any]:
        """Get account summary with order and position statistics.

        Returns:
            Account summary dictionary
        """
        total_orders = len(self._orders)
        open_orders = len([o for o in self._orders.values() if o.status == OrderStatus.OPEN])
        filled_orders = len([o for o in self._orders.values() if o.status == OrderStatus.FILLED])
        cancelled_orders = len(
            [o for o in self._orders.values() if o.status == OrderStatus.CANCELLED]
        )

        total_fills = sum(len(fills) for fills in self._order_fills.values())
        total_volume = sum(
            fill.amount * fill.price
            for fills in self._order_fills.values()
            for fill in fills
        )
        total_fees = sum(
            fill.fee for fills in self._order_fills.values() for fill in fills
        )

        positions_value = sum(p.market_value for p in self._positions.values())
        unrealized_pnl = sum(p.unrealized_pnl for p in self._positions.values())

        return {
            "total_orders": total_orders,
            "open_orders": open_orders,
            "filled_orders": filled_orders,
            "cancelled_orders": cancelled_orders,
            "total_fills": total_fills,
            "total_volume": total_volume,
            "total_fees": total_fees,
            "positions_count": len(self._positions),
            "positions_value": positions_value,
            "unrealized_pnl": unrealized_pnl,
        }

    def on_order_update(self, callback: Callable[[Order], None]) -> None:
        """Register callback for order updates.

        Args:
            callback: Function to call when order is updated
        """
        self._order_callbacks.append(callback)

    def on_fill(self, callback: Callable[[OrderFill], None]) -> None:
        """Register callback for order fills.

        Args:
            callback: Function to call when order is filled
        """
        self._fill_callbacks.append(callback)

    def on_position_update(self, callback: Callable[[Position], None]) -> None:
        """Register callback for position updates.

        Args:
            callback: Function to call when position is updated
        """
        self._position_callbacks.append(callback)

    async def _notify_order_update(self, order: Order) -> None:
        """Notify order update callbacks."""
        for callback in self._order_callbacks:
            try:
                if asyncio.iscoroutinefunction(callback):
                    await callback(order)
                else:
                    callback(order)
            except Exception as e:
                self.logger.error(f"Error in order callback: {e}")

    async def _notify_fill(self, fill: OrderFill) -> None:
        """Notify fill callbacks."""
        for callback in self._fill_callbacks:
            try:
                if asyncio.iscoroutinefunction(callback):
                    await callback(fill)
                else:
                    callback(fill)
            except Exception as e:
                self.logger.error(f"Error in fill callback: {e}")

    async def _notify_position_update(self, position: Position) -> None:
        """Notify position update callbacks."""
        for callback in self._position_callbacks:
            try:
                if asyncio.iscoroutinefunction(callback):
                    await callback(position)
                else:
                    callback(position)
            except Exception as e:
                self.logger.error(f"Error in position callback: {e}")

    async def update_position_prices(self) -> None:
        """Update current prices for all positions."""
        async with self._lock:
            for position in self._positions.values():
                # Get current market price
                current_price = await self._get_market_price(position.symbol, position.side)
                position.current_price = current_price
