"""Main trading engine with order execution and position management."""

from decimal import Decimal
from typing import Optional, Dict, Any, List
from sqlalchemy.ext.asyncio import AsyncSession
from src.core.database import Order, OrderStatus, OrderSide, OrderType, get_database
from src.core.logger import get_logger
from src.core.repositories import OrderRepository
from src.exchanges import get_exchange
from .order_manager import get_order_manager
from .paper_trading import get_paper_trading
from .fee_calculator import get_fee_calculator
from src.risk.manager import get_risk_manager

logger = get_logger(__name__)


class TradingEngine:
    """Main trading engine orchestrator."""
    
    def __init__(self):
        """Initialize trading engine."""
        self.db = get_database()
        self.order_manager = get_order_manager()
        self.paper_trading = get_paper_trading()
        self.risk_manager = get_risk_manager()
        self.logger = get_logger(__name__)
        self._exchanges: Dict[int, Any] = {}  # exchange_id -> adapter
    
    async def get_exchange_adapter(self, exchange_id: int):
        """Get or create exchange adapter.
        
        Args:
            exchange_id: Exchange ID
            
        Returns:
            Exchange adapter or None
        """
        if exchange_id not in self._exchanges:
            # We assume get_exchange is a factory function that might NOT be async itself,
            # but returns an adapter that IS async.
            # If get_exchange does I/O, it should be awaited.
            # For now, let's assume it just instantiates the class.
            adapter = await get_exchange(exchange_id)
            if adapter:
                self._exchanges[exchange_id] = adapter
                if not adapter._connected:
                    await adapter.connect()
        return self._exchanges.get(exchange_id)
    
    async def execute_order(
        self,
        exchange_id: int,
        strategy_id: Optional[int],
        symbol: str,
        side: OrderSide,
        order_type: OrderType,
        quantity: Decimal,
        price: Optional[Decimal] = None,
        paper_trading: bool = True
    ) -> Optional[Order]:
        """Execute a trading order."""
        try:
            # Get exchange adapter
            adapter = await self.get_exchange_adapter(exchange_id)
            if not adapter:
                self.logger.error(f"Exchange {exchange_id} not available")
                return None
            
            # Get current balance/price for risk checks
            balance = self.paper_trading.get_balance() if paper_trading else Decimal(0)
            ticker = await adapter.get_ticker(symbol)
            current_price = ticker.get('last', price or Decimal(0))
            
            # Risk checks (includes fee validation)
            allowed, reason = await self.risk_manager.check_order_risk(
                symbol, side.value, quantity, current_price, balance, adapter
            )
            if not allowed:
                self.logger.warning(f"Order rejected: {reason}")
                return None
            
            # Create order in database using generic repository pattern would be better
            # but for now we create the object and save it using our own session management
            # or rely on the order_manager which we need to check if it's async-ready.
            # For this refactor, let's use the OrderRepository directly for creation 
            # to be safe with sessions.
            
            async with self.db.get_session() as session:
                repo = OrderRepository(session)
                order = Order(
                    exchange_id=exchange_id,
                    strategy_id=strategy_id,
                    symbol=symbol,
                    order_type=order_type,
                    side=side,
                    quantity=quantity,
                    price=price,
                    paper_trading=paper_trading
                )
                order = await repo.create(order)
            
            if paper_trading:
                # Execute in paper trading
                fill_price = current_price  # Use current price for market orders
                if order_type == OrderType.LIMIT and price:
                    fill_price = price
                
                is_maker = (order_type == OrderType.LIMIT)
                
                # Use paper trading fee calculation (uses configured fee_exchange)
                fee_calculator = get_fee_calculator()
                fee = fee_calculator.calculate_fee_for_paper_trading(
                    quantity=quantity,
                    price=fill_price,
                    order_type=order_type,
                    is_maker=is_maker
                )
                
                if await self.paper_trading.execute_order(order, fill_price, fee):
                    self.logger.info(f"Paper trading order {order.id} executed with fee: {fee}")
                    return order
                else:
                    self.logger.warning(f"Paper trading order {order.id} rejected (insufficient funds or no position)")
                    async with self.db.get_session() as session:
                        repo = OrderRepository(session)
                        await repo.update_status(order.id, OrderStatus.REJECTED)
                    return None
            else:
                # Execute live order
                from src.exchanges.base import Order as ExchangeOrder
                exchange_order = ExchangeOrder(
                    symbol=symbol,
                    side=side.value,
                    order_type=order_type.value,
                    quantity=quantity,
                    price=price
                )
                
                result = await adapter.place_order(exchange_order)
                if result.get('id'):
                    actual_fee = adapter.extract_fee_from_order_response(result)
                    
                    if actual_fee is None:
                        fee_calculator = get_fee_calculator()
                        is_maker = (order_type == OrderType.LIMIT)
                        estimated_price = price or current_price
                        actual_fee = fee_calculator.calculate_fee(
                            quantity=quantity,
                            price=estimated_price,
                            order_type=order_type,
                            exchange_adapter=adapter,
                            is_maker=is_maker
                        )
                    
                    async with self.db.get_session() as session:
                        repo = OrderRepository(session)
                        await repo.update_status(
                            order.id,
                            OrderStatus.OPEN,
                            exchange_order_id=result['id'],
                            fee=actual_fee
                        )
                    return order
                else:
                    async with self.db.get_session() as session:
                        repo = OrderRepository(session)
                        await repo.update_status(order.id, OrderStatus.REJECTED)
                    return None
                    
        except Exception as e:
            self.logger.error(f"Failed to execute order: {e}")
            return None
    
    async def cancel_order(self, order_id: int) -> bool:
        """Cancel an order."""
        async with self.db.get_session() as session:
            repo = OrderRepository(session)
            order = await repo.get_by_id(order_id)
            
            if not order:
                return False
            
            # If already in final state, cannot cancel
            if order.status in [OrderStatus.FILLED, OrderStatus.CANCELLED, OrderStatus.REJECTED, OrderStatus.EXPIRED]:
                self.logger.warning(f"Order {order_id} is already in state {order.status}, cannot cancel")
                return False
            
            # If paper trading, just update status in DB
            if order.paper_trading:
                await repo.update_status(order_id, OrderStatus.CANCELLED)
                self.logger.info(f"Paper trading order {order_id} cancelled")
                return True
                
            # If live trading, cancel on exchange
            adapter = await self.get_exchange_adapter(order.exchange_id)
            if not adapter:
                self.logger.error(f"Exchange adapter not found for exchange_id {order.exchange_id}")
                return False
            
            try:
                if await adapter.cancel_order(order.exchange_order_id or str(order.id), order.symbol):
                    # Update status in DB
                    await repo.update_status(order_id, OrderStatus.CANCELLED)
                    self.logger.info(f"Live order {order_id} cancelled on exchange")
                    return True
                else:
                    self.logger.error(f"Failed to cancel order {order_id} on exchange")
            except Exception as e:
                self.logger.error(f"Error cancelling order {order_id} on exchange: {e}")
                
            return False
    
    async def get_positions(self, exchange_id: Optional[int] = None) -> List[Dict[str, Any]]:
        """Get open positions."""
        if exchange_id:
            adapter = await self.get_exchange_adapter(exchange_id)
            if adapter:
                return await adapter.get_positions()
        
        # Get from paper trading
        positions = self.paper_trading.get_positions()
        return [
            {
                'symbol': pos.symbol,
                'side': pos.side,
                'quantity': pos.quantity,
                'entry_price': pos.entry_price,
                'current_price': pos.current_price,
                'unrealized_pnl': pos.unrealized_pnl,
            }
            for pos in positions
        ]


# Global trading engine
_trading_engine: Optional[TradingEngine] = None


def get_trading_engine() -> TradingEngine:
    """Get global trading engine instance."""
    global _trading_engine
    if _trading_engine is None:
        _trading_engine = TradingEngine()
    return _trading_engine
