"""Order state machine and order management system."""

from datetime import datetime
from decimal import Decimal
from enum import Enum
from typing import Optional, Dict, Any, List
from sqlalchemy.orm import Session
from sqlalchemy import select
from src.core.database import get_database, Order, OrderStatus, OrderType, OrderSide, TradeType
from src.core.logger import get_logger

logger = get_logger(__name__)


class OrderStateMachine:
    """Order state machine for managing order lifecycle."""
    
    # Valid state transitions
    TRANSITIONS = {
        OrderStatus.PENDING: [OrderStatus.OPEN, OrderStatus.REJECTED],
        OrderStatus.OPEN: [
            OrderStatus.PARTIALLY_FILLED,
            OrderStatus.FILLED,
            OrderStatus.CANCELLED,
            OrderStatus.EXPIRED
        ],
        OrderStatus.PARTIALLY_FILLED: [
            OrderStatus.FILLED,
            OrderStatus.CANCELLED,
            OrderStatus.EXPIRED
        ],
        OrderStatus.FILLED: [],  # Terminal state
        OrderStatus.CANCELLED: [],  # Terminal state
        OrderStatus.REJECTED: [],  # Terminal state
        OrderStatus.EXPIRED: [],  # Terminal state
    }
    
    @staticmethod
    def can_transition(from_status: OrderStatus, to_status: OrderStatus) -> bool:
        """Check if state transition is valid.
        
        Args:
            from_status: Current status
            to_status: Target status
            
        Returns:
            True if transition is valid
        """
        return to_status in OrderStateMachine.TRANSITIONS.get(from_status, [])
    
    @staticmethod
    def transition(order: Order, new_status: OrderStatus, **kwargs) -> bool:
        """Transition order to new status.
        
        Args:
            order: Order object
            new_status: New status
            **kwargs: Additional data (filled_quantity, average_fill_price, etc.)
            
        Returns:
            True if transition successful
        """
        if not OrderStateMachine.can_transition(order.status, new_status):
            logger.warning(
                f"Invalid transition from {order.status} to {new_status} for order {order.id}"
            )
            return False
        
        old_status = order.status
        order.status = new_status
        order.updated_at = datetime.utcnow()
        
        # Update filled data if provided
        if 'filled_quantity' in kwargs:
            order.filled_quantity = Decimal(str(kwargs['filled_quantity']))
        if 'average_fill_price' in kwargs:
            order.average_fill_price = Decimal(str(kwargs['average_fill_price']))
        if 'fee' in kwargs:
            order.fee = Decimal(str(kwargs['fee']))
        if new_status == OrderStatus.FILLED:
            order.filled_at = datetime.utcnow()
        
        logger.info(
            f"Order {order.id} transitioned from {old_status} to {new_status}"
        )
        return True


class OrderManager:
    """Manages orders and their state."""
    
    def __init__(self):
        """Initialize order manager."""
        self.db = get_database()
    
    async def create_order(
        self,
        exchange_id: int,
        strategy_id: Optional[int],
        symbol: str,
        order_type: OrderType,
        side: OrderSide,
        quantity: Decimal,
        price: Optional[Decimal] = None,
        trade_type: TradeType = TradeType.SPOT,
        leverage: int = 1,
        paper_trading: bool = True
    ) -> Order:
        """Create a new order in database.
        
        Args:
            exchange_id: Exchange ID
            strategy_id: Strategy ID (optional)
            symbol: Trading symbol
            order_type: Order type
            side: Buy or sell
            quantity: Order quantity
            price: Order price (for limit orders)
            trade_type: Spot, futures, or margin
            leverage: Leverage (for futures/margin)
            paper_trading: Paper trading flag
            
        Returns:
            Order object
        """
        async with self.db.get_session() as session:
            try:
                order = Order(
                    exchange_id=exchange_id,
                    strategy_id=strategy_id,
                    symbol=symbol,
                    order_type=order_type,
                    side=side,
                    status=OrderStatus.PENDING,
                    quantity=quantity,
                    price=price,
                    trade_type=trade_type,
                    leverage=leverage,
                    paper_trading=paper_trading,
                    created_at=datetime.utcnow()
                )
                
                session.add(order)
                await session.commit()
                await session.refresh(order)
                
                logger.info(f"Created order {order.id}: {side} {quantity} {symbol} @ {price or 'market'}")
                return order
            except Exception as e:
                await session.rollback()
                logger.error(f"Failed to create order: {e}")
                raise

    async def update_order_status(
        self,
        order_id: int,
        new_status: OrderStatus,
        filled_quantity: Optional[Decimal] = None,
        average_fill_price: Optional[Decimal] = None,
        fee: Optional[Decimal] = None,
        exchange_order_id: Optional[str] = None
    ) -> bool:
        """Update order status.
        
        Args:
            order_id: Order ID
            new_status: New status
            filled_quantity: Filled quantity
            average_fill_price: Average fill price
            fee: Trading fee
            exchange_order_id: Exchange order ID
            
        Returns:
            True if update successful
        """
        async with self.db.get_session() as session:
            try:
                stmt = select(Order).filter_by(id=order_id)
                result = await session.execute(stmt)
                order = result.scalar_one_or_none()
                
                if not order:
                    logger.error(f"Order {order_id} not found")
                    return False
                
                if OrderStateMachine.transition(
                    order, new_status,
                    filled_quantity=filled_quantity,
                    average_fill_price=average_fill_price,
                    fee=fee
                ):
                    if exchange_order_id:
                        order.exchange_order_id = exchange_order_id
                    await session.commit()
                    return True
                return False
            except Exception as e:
                await session.rollback()
                logger.error(f"Failed to update order {order_id}: {e}")
                return False

    async def get_order(self, order_id: int) -> Optional[Order]:
        """Get order by ID.
        
        Args:
            order_id: Order ID
            
        Returns:
            Order object or None
        """
        async with self.db.get_session() as session:
            stmt = select(Order).filter_by(id=order_id)
            result = await session.execute(stmt)
            return result.scalar_one_or_none()

    async def get_open_orders(
        self,
        exchange_id: Optional[int] = None,
        strategy_id: Optional[int] = None,
        symbol: Optional[str] = None
    ) -> List[Order]:
        """Get open orders.
        
        Args:
            exchange_id: Filter by exchange
            strategy_id: Filter by strategy
            symbol: Filter by symbol
            
        Returns:
            List of open orders
        """
        async with self.db.get_session() as session:
            stmt = select(Order).filter(
                Order.status.in_([
                    OrderStatus.PENDING,
                    OrderStatus.OPEN,
                    OrderStatus.PARTIALLY_FILLED
                ])
            )
            
            if exchange_id:
                stmt = stmt.filter_by(exchange_id=exchange_id)
            if strategy_id:
                stmt = stmt.filter_by(strategy_id=strategy_id)
            if symbol:
                stmt = stmt.filter_by(symbol=symbol)
            
            result = await session.execute(stmt)
            return list(result.scalars().all())

    async def cancel_order(self, order_id: int) -> bool:
        """Cancel an order.
        
        Args:
            order_id: Order ID
            
        Returns:
            True if cancellation successful
        """
        return await self.update_order_status(order_id, OrderStatus.CANCELLED)

    async def get_order_history(
        self,
        exchange_id: Optional[int] = None,
        strategy_id: Optional[int] = None,
        symbol: Optional[str] = None,
        limit: int = 100
    ) -> List[Order]:
        """Get order history.
        
        Args:
            exchange_id: Filter by exchange
            strategy_id: Filter by strategy
            symbol: Filter by symbol
            limit: Maximum number of orders
            
        Returns:
            List of orders
        """
        async with self.db.get_session() as session:
            stmt = select(Order)
            
            if exchange_id:
                stmt = stmt.filter_by(exchange_id=exchange_id)
            if strategy_id:
                stmt = stmt.filter_by(strategy_id=strategy_id)
            if symbol:
                stmt = stmt.filter_by(symbol=symbol)
            
            stmt = stmt.order_by(Order.created_at.desc()).limit(limit)
            result = await session.execute(stmt)
            return list(result.scalars().all())


# Global order manager
_order_manager: Optional[OrderManager] = None


def get_order_manager() -> OrderManager:
    """Get global order manager instance."""
    global _order_manager
    if _order_manager is None:
        _order_manager = OrderManager()
    return _order_manager

