"""Paper trading simulator with virtual funds."""

from decimal import Decimal
from datetime import datetime
from typing import Dict, Optional, List
from sqlalchemy.orm import Session
from sqlalchemy import select
from src.core.database import get_database, Trade, Position, PortfolioSnapshot, Order, OrderStatus
from src.core.config import get_config
from src.core.logger import get_logger
from .order_manager import get_order_manager

logger = get_logger(__name__)


class PaperTradingSimulator:
    """Paper trading simulator with virtual funds."""
    
    def __init__(self, initial_capital: Optional[Decimal] = None):
        """Initialize paper trading simulator.
        
        Args:
            initial_capital: Initial capital (default from config)
        """
        self.config = get_config()
        self.db = get_database()
        
        if initial_capital is None:
            initial_capital = Decimal(str(self.config.get("paper_trading.default_capital", 100.0)))
        
        self.initial_capital = initial_capital
        self.cash = initial_capital
        self.positions: Dict[str, Position] = {}
        # initialization moved to initialize()
    
    async def initialize(self):
        """Initialize paper trading simulator."""
        await self._initialize_portfolio()

    async def _initialize_portfolio(self):
        """Initialize portfolio in database."""
        async with self.db.get_session() as session:
            try:
                # Check if portfolio already exists
                stmt = select(PortfolioSnapshot).order_by(
                    PortfolioSnapshot.timestamp.desc()
                )
                result = await session.execute(stmt)
                latest = result.scalar_one_or_none()
                
                if not latest:
                    # Create initial snapshot
                    snapshot = PortfolioSnapshot(
                        total_value=self.initial_capital,
                        cash=self.initial_capital,
                        positions_value=Decimal(0),
                        unrealized_pnl=Decimal(0),
                        realized_pnl=Decimal(0),
                        paper_trading=True,
                        timestamp=datetime.utcnow()
                    )
                    session.add(snapshot)
                    await session.commit()
                    logger.info(f"Initialized paper trading portfolio with ${self.initial_capital}")
            except Exception as e:
                await session.rollback()
                logger.error(f"Failed to initialize portfolio: {e}")
    
    def get_balance(self, currency: str = "USD") -> Decimal:
        """Get cash balance.
        
        Args:
            currency: Currency (default USD)
            
        Returns:
            Cash balance
        """
        return self.cash
    
    def get_portfolio_value(self) -> Decimal:
        """Get total portfolio value (cash + positions).
        
        Returns:
            Total portfolio value
        """
        positions_value = sum(
            pos.quantity * (pos.current_price or pos.entry_price)
            for pos in self.positions.values()
        )
        return self.cash + positions_value
    
    async def execute_order(self, order: Order, fill_price: Decimal, fee: Decimal = Decimal(0)) -> bool:
        """Execute an order in paper trading.
        
        Args:
            order: Order to execute
            fill_price: Fill price
            fee: Trading fee
            
        Returns:
            True if execution successful
        """
        if not order.paper_trading:
            logger.error("Cannot execute live order in paper trading")
            return False
        
        async with self.db.get_session() as session:
            try:
                order_manager = get_order_manager()
                
                # Calculate total cost
                total_cost = order.quantity * fill_price + fee
                
                if order.side.value == "buy":
                    # Check if we have enough cash
                    if self.cash < total_cost:
                        logger.warning(f"Insufficient cash: ${self.cash} < ${total_cost}")
                        await order_manager.update_order_status(order.id, OrderStatus.REJECTED)
                        return False
                    
                    # Deduct cash
                    self.cash -= total_cost
                    
                    # Update or create position
                    symbol = order.symbol
                    if symbol in self.positions:
                        pos = self.positions[symbol]
                        # Average entry price
                        total_value = (pos.quantity * pos.entry_price) + (order.quantity * fill_price)
                        total_quantity = pos.quantity + order.quantity
                        pos.entry_price = total_value / total_quantity
                        pos.quantity = total_quantity
                        pos.current_price = fill_price
                    else:
                        # Create new position
                        pos = Position(
                            exchange_id=order.exchange_id,
                            symbol=symbol,
                            side="long",
                            quantity=order.quantity,
                            entry_price=fill_price,
                            current_price=fill_price,
                            unrealized_pnl=Decimal(0),
                            realized_pnl=Decimal(0),
                            trade_type=order.trade_type,
                            leverage=order.leverage,
                            paper_trading=True,
                            opened_at=datetime.utcnow()
                        )
                        session.add(pos)
                        await session.commit()
                        await session.refresh(pos)
                        self.positions[symbol] = pos
                    
                elif order.side.value == "sell":
                    # Check if we have position
                    symbol = order.symbol
                    if symbol not in self.positions:
                        logger.warning(f"No position to sell for {symbol}")
                        await order_manager.update_order_status(order.id, OrderStatus.REJECTED)
                        return False
                    
                    pos = self.positions[symbol]
                    if pos.quantity < order.quantity:
                        logger.warning(f"Insufficient position: {pos.quantity} < {order.quantity}")
                        await order_manager.update_order_status(order.id, OrderStatus.REJECTED)
                        return False
                    
                    # Calculate P&L
                    pnl = (fill_price - pos.entry_price) * order.quantity - fee
                    
                    # Add cash
                    self.cash += (order.quantity * fill_price) - fee
                    
                    # Update position
                    pos.quantity -= order.quantity
                    pos.realized_pnl += pnl
                    
                    if pos.quantity <= 0:
                        # Close position
                        await session.delete(pos)
                        del self.positions[symbol]
                    else:
                        pos.current_price = fill_price
                
                # Create trade record
                trade = Trade(
                    exchange_id=order.exchange_id,
                    strategy_id=order.strategy_id,
                    order_id=order.id,
                    symbol=order.symbol,
                    side=order.side,
                    quantity=order.quantity,
                    price=fill_price,
                    fee=fee,
                    total=order.quantity * fill_price + fee,
                    trade_type=order.trade_type,
                    paper_trading=True,
                    timestamp=datetime.utcnow()
                )
                session.add(trade)
                
                # Update order status
                await order_manager.update_order_status(
                    order.id,
                    OrderStatus.FILLED,
                    filled_quantity=order.quantity,
                    average_fill_price=fill_price,
                    fee=fee
                )
                
                # Update portfolio snapshot
                self._update_portfolio_snapshot(session)
                
                await session.commit()
                logger.info(f"Executed {order.side.value} order {order.id}: {order.quantity} {order.symbol} @ {fill_price}")
                return True
                
            except Exception as e:
                await session.rollback()
                logger.error(f"Failed to execute order {order.id}: {e}")
                return False
    
    def _update_portfolio_snapshot(self, session: Session):
        """Update portfolio snapshot.
        
        Args:
            session: Database session
        """
        positions_value = Decimal(0)
        unrealized_pnl = Decimal(0)
        
        for pos in self.positions.values():
            if pos.current_price:
                pos_value = pos.quantity * pos.current_price
                positions_value += pos_value
                unrealized_pnl += (pos.current_price - pos.entry_price) * pos.quantity
        
        total_value = self.cash + positions_value
        
        snapshot = PortfolioSnapshot(
            total_value=total_value,
            cash=self.cash,
            positions_value=positions_value,
            unrealized_pnl=unrealized_pnl,
            realized_pnl=sum(pos.realized_pnl for pos in self.positions.values()),
            paper_trading=True,
            timestamp=datetime.utcnow()
        )
        session.add(snapshot)
    
    async def update_positions_prices(self, prices: Dict[str, Decimal]):
        """Update current prices for positions.
        
        Args:
            prices: Dictionary of symbol -> current_price
        """
        async with self.db.get_session() as session:
            try:
                for symbol, price in prices.items():
                    if symbol in self.positions:
                        pos = self.positions[symbol]
                        pos.current_price = price
                        pos.unrealized_pnl = (price - pos.entry_price) * pos.quantity
                        pos.updated_at = datetime.utcnow()
                
                # Update portfolio snapshot
                self._update_portfolio_snapshot(session)
                await session.commit()
            except Exception as e:
                await session.rollback()
                logger.error(f"Failed to update positions prices: {e}")
    
    def get_positions(self) -> List[Position]:
        """Get all open positions.
        
        Returns:
            List of positions
        """
        return list(self.positions.values())
    
    def get_performance(self) -> Dict[str, Decimal]:
        """Get portfolio performance metrics.
        
        Returns:
            Dictionary with performance metrics
        """
        total_value = self.get_portfolio_value()
        total_return = ((total_value - self.initial_capital) / self.initial_capital) * 100
        
        realized_pnl = sum(pos.realized_pnl for pos in self.positions.values())
        unrealized_pnl = sum(
            (pos.current_price - pos.entry_price) * pos.quantity
            for pos in self.positions.values()
            if pos.current_price
        )
        
        return {
            "initial_capital": self.initial_capital,
            "current_value": total_value,
            "cash": self.cash,
            "positions_value": total_value - self.cash,
            "total_return_percent": total_return,
            "realized_pnl": realized_pnl,
            "unrealized_pnl": unrealized_pnl,
            "total_pnl": realized_pnl + unrealized_pnl,
        }
    
    async def reset(self, new_capital: Optional[Decimal] = None):
        """Reset paper trading account to initial state.
        
        Args:
            new_capital: New initial capital (uses config if not provided)
        """
        # Get new capital from config if not provided
        if new_capital is None:
            new_capital = Decimal(str(self.config.get("paper_trading.default_capital", 100.0)))
        
        async with self.db.get_session() as session:
            try:
                # Delete all portfolio snapshots for paper trading
                from sqlalchemy import delete
                stmt = delete(PortfolioSnapshot).where(PortfolioSnapshot.paper_trading == True)
                await session.execute(stmt)
                
                # Delete all paper trading positions
                stmt = delete(Position).where(Position.paper_trading == True)
                await session.execute(stmt)
                
                # Delete all paper trading trades
                stmt = delete(Trade).where(Trade.paper_trading == True)
                await session.execute(stmt)
                
                # Delete all paper trading orders
                stmt = delete(Order).where(Order.paper_trading == True)
                await session.execute(stmt)
                
                await session.commit()
                
                # Reset in-memory state
                self.initial_capital = new_capital
                self.cash = new_capital
                self.positions = {}
                
                # Create new initial snapshot
                snapshot = PortfolioSnapshot(
                    total_value=new_capital,
                    cash=new_capital,
                    positions_value=Decimal(0),
                    unrealized_pnl=Decimal(0),
                    realized_pnl=Decimal(0),
                    paper_trading=True,
                    timestamp=datetime.utcnow()
                )
                session.add(snapshot)
                await session.commit()
                
                logger.info(f"Reset paper trading account with ${new_capital}")
                return True
                
            except Exception as e:
                await session.rollback()
                logger.error(f"Failed to reset paper trading: {e}")
                return False


# Global paper trading simulator
_paper_trading: Optional[PaperTradingSimulator] = None


def get_paper_trading() -> PaperTradingSimulator:
    """Get global paper trading simulator instance."""
    global _paper_trading
    if _paper_trading is None:
        _paper_trading = PaperTradingSimulator()
    return _paper_trading

