"""Backtesting engine with historical data replay and performance metrics."""

import pandas as pd
from decimal import Decimal
from datetime import datetime
from typing import Dict, List, Optional, Any
from sqlalchemy import select
from src.core.database import get_database, MarketData, OrderType
from src.core.logger import get_logger
from src.strategies.base import BaseStrategy
from src.trading.paper_trading import PaperTradingSimulator
from src.exchanges.factory import get_exchange
from .metrics import BacktestMetrics
from .slippage import FeeModel

logger = get_logger(__name__)


class BacktestingEngine:
    """Backtesting engine for strategy evaluation."""
    
    def __init__(self):
        """Initialize backtesting engine."""
        self.db = get_database()
        self.logger = get_logger(__name__)
    
    async def run_backtest(
        self,
        strategy: BaseStrategy,
        symbol: str,
        exchange: str,
        timeframe: str,
        start_date: datetime,
        end_date: datetime,
        initial_capital: Decimal = Decimal("100.0"),
        slippage: float = 0.001,  # 0.1% slippage
        fee_model: Optional[FeeModel] = None,
        exchange_id: Optional[int] = None,
    ) -> Dict[str, Any]:
        """Run backtest on strategy.
        
        Args:
            strategy: Strategy instance
            symbol: Trading symbol
            exchange: Exchange name
            timeframe: Timeframe
            start_date: Start date
            end_date: End date
            initial_capital: Initial capital
            slippage: Slippage percentage
            fee_model: FeeModel instance (if None, creates default)
            exchange_id: Exchange ID for fee structure retrieval
            
        Returns:
            Backtest results dictionary
        """
        # Get historical data
        data = await self._get_historical_data(exchange, symbol, timeframe, start_date, end_date)
        
        if len(data) == 0:
            return {"error": "No historical data available"}
        
        # Initialize fee model
        if fee_model is None:
            # Try to get exchange adapter for fee structure
            exchange_adapter = None
            if exchange_id:
                try:
                    exchange_adapter = await get_exchange(exchange_id)
                except Exception as e:
                    self.logger.warning(f"Could not get exchange adapter for fees: {e}")
            
            fee_model = FeeModel(exchange_adapter=exchange_adapter)
        
        # Initialize paper trading simulator
        simulator = PaperTradingSimulator(initial_capital)
        await simulator.initialize()
        strategy.enabled = True
        
        # Run backtest
        trades = []
        total_fees = Decimal(0)
        
        for i, row in data.iterrows():
            price = Decimal(str(row['close']))
            
            # Generate signal
            signal = await strategy.on_tick(
                symbol,
                price,
                timeframe,
                {'open': row['open'], 'high': row['high'], 'low': row['low'], 'volume': row['volume']}
            )
            
            if signal and strategy.should_execute(signal):
                # Determine order type (default to MARKET for backtesting)
                # In real trading, strategies could specify limit orders
                order_type = OrderType.MARKET
                is_maker = (order_type == OrderType.LIMIT)
                
                # Execute trade with slippage and fees
                slippage_multiplier = (Decimal("1") + Decimal(str(slippage))) if signal.signal_type.value == "buy" else (Decimal("1") - Decimal(str(slippage)))
                fill_price = price * slippage_multiplier
                quantity = signal.quantity or strategy.calculate_position_size(signal, simulator.get_balance(), fill_price)
                
                # Calculate fee using FeeModel with maker/taker distinction
                fee = fee_model.calculate_fee(quantity, fill_price, is_maker=is_maker)
                total_fees += fee
                
                # Create and execute order
                from src.core.database import Order as DBOrder, OrderSide
                from src.trading.order_manager import get_order_manager
                
                order_manager = get_order_manager()
                order = await order_manager.create_order(
                    exchange_id=exchange_id or 1,  # Use provided exchange_id or placeholder
                    strategy_id=None,
                    symbol=symbol,
                    order_type=order_type,
                    side=OrderSide.BUY if signal.signal_type.value == "buy" else OrderSide.SELL,
                    quantity=quantity,
                    paper_trading=True
                )
                
                if order and await simulator.execute_order(order, fill_price, fee):
                    trades.append({
                        'timestamp': i,
                        'price': float(fill_price),
                        'quantity': float(quantity),
                        'side': signal.signal_type.value,
                        'fee': float(fee),
                    })
        
        # Calculate metrics
        metrics = BacktestMetrics()
        results = metrics.calculate_metrics(simulator, trades, initial_capital, total_fees)
        
        # Add fee information to results
        results['total_fees'] = float(total_fees)
        results['fee_percentage'] = float((total_fees / initial_capital) * 100) if initial_capital > 0 else 0.0
        
        return results
    
    async def _get_historical_data(
        self,
        exchange: str,
        symbol: str,
        timeframe: str,
        start_date: datetime,
        end_date: datetime
    ) -> pd.DataFrame:
        """Get historical OHLCV data.
        
        Args:
            exchange: Exchange name
            symbol: Trading symbol
            timeframe: Timeframe
            start_date: Start date
            end_date: End date
            
        Returns:
            DataFrame with OHLCV data
        """
        async with self.db.get_session() as session:
            try:
                stmt = select(MarketData).filter(
                    MarketData.exchange == exchange,
                    MarketData.symbol == symbol,
                    MarketData.timeframe == timeframe,
                    MarketData.timestamp >= start_date,
                    MarketData.timestamp <= end_date
                ).order_by(MarketData.timestamp)
                
                result = await session.execute(stmt)
                market_data = result.scalars().all()
                
                if len(market_data) == 0:
                    return pd.DataFrame()
                
                data = {
                    'timestamp': [md.timestamp for md in market_data],
                    'open': [float(md.open) for md in market_data],
                    'high': [float(md.high) for md in market_data],
                    'low': [float(md.low) for md in market_data],
                    'close': [float(md.close) for md in market_data],
                    'volume': [float(md.volume) for md in market_data],
                }
                
                df = pd.DataFrame(data)
                df.set_index('timestamp', inplace=True)
                return df
            except Exception as e:
                logger.error(f"Failed to get historical data: {e}")
                return pd.DataFrame()


# Global backtesting engine
_backtesting_engine: Optional[BacktestingEngine] = None


def get_backtest_engine() -> BacktestingEngine:
    """Get global backtesting engine instance."""
    global _backtesting_engine
    if _backtesting_engine is None:
        _backtesting_engine = BacktestingEngine()
    return _backtesting_engine

