//! Strategy execution engine for backtesting - REFACTORED with repository injection

use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use rust_decimal::{prelude::ToPrimitive, Decimal};
// For Decimal::from_f64
use std::collections::HashMap;
use std::sync::Arc;
use tracing::{debug, info};

use crate::repositories::BacktestingRepositories;
use config::structures::BacktestingStrategyConfig;

/// News event structure for strategy consumption
#[derive(Debug, Clone)]
pub struct NewsEvent;

/// Market data structure for backtesting (standard OHLCV)
#[derive(Debug, Clone)]
pub struct MarketData {
    /// Symbol
    pub symbol: String,
    /// Timestamp
    pub timestamp: DateTime<Utc>,
    /// Open price
    pub open: Decimal,
    /// High price
    pub high: Decimal,
    /// Low price
    pub low: Decimal,
    /// Close price
    pub close: Decimal,
    /// Volume
    pub volume: Decimal,
}

/// `Trade` execution result from backtesting
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BacktestTrade {
    /// Unique trade ID
    pub trade_id: String,
    /// Symbol traded
    pub symbol: String,
    /// Buy or Sell
    pub side: TradeSide,
    /// Quantity
    pub quantity: Decimal,
    /// Entry price
    pub entry_price: Decimal,
    /// Exit price
    pub exit_price: Decimal,
    /// Entry timestamp
    pub entry_time: DateTime<Utc>,
    /// Exit timestamp
    pub exit_time: DateTime<Utc>,
    /// Profit/Loss
    pub pnl: Decimal,
    /// Return percentage
    pub return_percent: Decimal,
    /// Entry signal information
    pub entry_signal: String,
    /// Exit signal information
    pub exit_signal: String,
}

/// `Trade` side enumeration
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum TradeSide {
    /// Buy side trade (long position)
    Buy,
    /// Sell side trade (short position)
    Sell,
}

/// `Position` tracking for backtesting
#[derive(Debug, Clone)]
pub struct Position {
    /// Symbol
    pub symbol: String,
    /// Current quantity (positive = long, negative = short)
    pub quantity: Decimal,
    /// Average entry price
    pub avg_price: Decimal,
    /// Total cost basis
    pub cost_basis: Decimal,
    /// Entry timestamp
    pub entry_time: DateTime<Utc>,
}

/// Backtesting portfolio state
#[derive(Debug, Clone)]
pub struct Portfolio {
    /// Cash balance
    cash: Decimal,
    /// Open positions
    positions: HashMap<String, Position>,
    /// Completed trades
    trades: Vec<BacktestTrade>,
    /// Transaction costs
    total_commissions: Decimal,
    /// Total slippage costs
    total_slippage: Decimal,
}

impl Portfolio {
    /// Create a new portfolio with initial capital
    pub fn new(initial_capital: Decimal) -> Self {
        Self {
            cash: initial_capital,
            positions: HashMap::new(),
            trades: Vec::new(),
            total_commissions: Decimal::ZERO,
            total_slippage: Decimal::ZERO,
        }
    }

    /// Get position for symbol
    pub fn get_position(&self, symbol: &str) -> Option<&Position> {
        self.positions.get(symbol)
    }

    /// Execute a trade (buy or sell)
    fn execute_trade(
        &mut self,
        symbol: String,
        side: TradeSide,
        quantity: Decimal,
        price: Decimal,
        timestamp: DateTime<Utc>,
        commission_rate: Decimal,
        slippage_rate: Decimal,
        trade_id: String,
        signal: String,
    ) -> Result<Option<BacktestTrade>> {
        let trade_value = quantity * price;
        let commission = trade_value * commission_rate;
        let slippage = trade_value * slippage_rate;
        let _total_cost = commission + slippage;

        // Adjust price for slippage
        let adjusted_price = match side {
            TradeSide::Buy => price * (Decimal::ONE + slippage_rate),
            TradeSide::Sell => price * (Decimal::ONE - slippage_rate),
        };

        match side {
            TradeSide::Buy => {
                let total_needed = quantity * adjusted_price + commission;
                if self.cash < total_needed {
                    return Ok(None); // Insufficient funds
                }

                self.cash -= total_needed;
                self.total_commissions += commission;
                self.total_slippage += slippage;

                // Update or create position
                if let Some(position) = self.positions.get_mut(&symbol) {
                    let new_quantity = position.quantity + quantity;
                    let new_cost_basis = position.cost_basis + quantity * adjusted_price;
                    position.avg_price = new_cost_basis / new_quantity;
                    position.quantity = new_quantity;
                    position.cost_basis = new_cost_basis;
                } else {
                    self.positions.insert(
                        symbol.clone(),
                        Position {
                            symbol: symbol.clone(),
                            quantity,
                            avg_price: adjusted_price,
                            cost_basis: quantity * adjusted_price,
                            entry_time: timestamp,
                        },
                    );
                }
            },
            TradeSide::Sell => {
                let position = self.positions.get_mut(&symbol);

                // Check if position exists and has sufficient quantity
                let has_sufficient_position = position
                    .as_ref()
                    .map(|p| p.quantity >= quantity)
                    .unwrap_or(false);

                if !has_sufficient_position {
                    return Ok(None); // No position or insufficient quantity
                }

                // Safe to unwrap here since we verified position exists above
                let position =
                    position.ok_or_else(|| anyhow::anyhow!("Position unexpectedly missing"))?;

                let proceeds = quantity * adjusted_price - commission;
                self.cash += proceeds;
                self.total_commissions += commission;
                self.total_slippage += slippage;

                // Calculate PnL for this portion
                let cost_basis = position.avg_price * quantity;
                let pnl = proceeds - cost_basis;
                let return_percent = if cost_basis > Decimal::ZERO {
                    pnl / cost_basis
                } else {
                    Decimal::ZERO
                };

                // Create completed trade
                let trade = BacktestTrade {
                    trade_id,
                    symbol: position.symbol.clone(),
                    side,
                    quantity,
                    entry_price: position.avg_price,
                    exit_price: adjusted_price,
                    entry_time: position.entry_time,
                    exit_time: timestamp,
                    pnl,
                    return_percent,
                    entry_signal: "buy".to_string(), // Simplified
                    exit_signal: signal,
                };

                self.trades.push(trade.clone());

                // Update position
                position.quantity -= quantity;
                position.cost_basis -= cost_basis;

                if position.quantity <= Decimal::ZERO {
                    self.positions.remove(&symbol);
                }

                return Ok(Some(trade));
            },
        }

        Ok(None)
    }
}

/// Strategy execution engine for backtesting - REFACTORED
pub struct StrategyEngine {
    /// Configuration
    config: BacktestingStrategyConfig,
    /// Repository for data access - NO DIRECT DATABASE COUPLING
    repositories: Arc<dyn BacktestingRepositories>,
    /// Available strategies
    strategies: HashMap<String, Box<dyn StrategyExecutor>>,
}

/// Trait for strategy execution
pub trait StrategyExecutor: Send + Sync + std::fmt::Debug {
    /// Execute strategy for a given market data point
    fn execute(
        &self,
        market_data: &MarketData,
        portfolio: &Portfolio,
        parameters: &HashMap<String, String>,
    ) -> Result<Vec<TradeSignal>>;
}

/// `Trade` signal from strategy
#[derive(Debug, Clone)]
pub struct TradeSignal {
    /// Symbol to trade
    pub symbol: String,
    /// `Trade` side
    pub side: TradeSide,
    /// Quantity (can be percentage of portfolio or absolute)
    pub quantity: Decimal,
    /// Signal reason/description
    pub reason: String,
}

/// Simple moving average crossover strategy
#[derive(Debug)]
struct MovingAverageCrossoverStrategy;

impl StrategyExecutor for MovingAverageCrossoverStrategy {
    fn execute(
        &self,
        market_data: &MarketData,
        portfolio: &Portfolio,
        parameters: &HashMap<String, String>,
    ) -> Result<Vec<TradeSignal>> {
        let mut signals = Vec::new();

        if let Some(price_str) = parameters.get("trigger_price") {
            let trigger_price: Decimal = price_str.parse().context("Invalid trigger price")?;

            // Use bar range (high - low) as volatility filter
            let bar_range = market_data.high - market_data.low;
            let max_range = parameters
                .get("max_bar_range")
                .and_then(|s| s.parse::<Decimal>().ok());

            // Skip signals during extreme volatility bars
            if let Some(max) = max_range {
                if bar_range > max {
                    return Ok(signals);
                }
            }

            if let Some(position) = portfolio.get_position(&market_data.symbol) {
                // Sell if close drops below trigger or low pierces stop level
                let stop_level = trigger_price
                    * Decimal::from_f64_retain(0.98).unwrap_or(Decimal::ONE);
                if market_data.close < trigger_price || market_data.low < stop_level {
                    signals.push(TradeSignal {
                        symbol: market_data.symbol.clone(),
                        side: TradeSide::Sell,
                        quantity: position.quantity,
                        reason: format!(
                            "Exit: close={}, low={}, trigger={}",
                            market_data.close, market_data.low, trigger_price
                        ),
                    });
                }
            } else {
                // Buy if close above trigger and open confirms bullish bar
                if market_data.close > trigger_price && market_data.close > market_data.open {
                    let quantity = Decimal::from_f64_retain(0.01).unwrap_or(Decimal::ONE);
                    signals.push(TradeSignal {
                        symbol: market_data.symbol.clone(),
                        side: TradeSide::Buy,
                        quantity,
                        reason: format!(
                            "Entry: bullish bar open={}, close={}, range={}",
                            market_data.open, market_data.close, bar_range
                        ),
                    });
                }
            }
        }

        Ok(signals)
    }
}

/// Buy and hold strategy
#[derive(Debug)]
struct BuyAndHoldStrategy;

impl StrategyExecutor for BuyAndHoldStrategy {
    fn execute(
        &self,
        market_data: &MarketData,
        portfolio: &Portfolio,
        parameters: &HashMap<String, String>,
    ) -> Result<Vec<TradeSignal>> {
        let mut signals = Vec::new();

        // Only buy if we don't have a position
        if portfolio.get_position(&market_data.symbol).is_none() {
            // Require minimum volume for liquidity
            let min_volume = parameters
                .get("min_volume")
                .and_then(|s| s.parse::<Decimal>().ok())
                .unwrap_or(Decimal::ZERO);

            if market_data.volume < min_volume {
                return Ok(signals);
            }

            let allocation = parameters
                .get("allocation")
                .and_then(|s| s.parse::<f64>().ok())
                .unwrap_or(1.0);

            let quantity = portfolio.cash
                * Decimal::from_f64_retain(allocation).unwrap_or(Decimal::ONE)
                / market_data.close;

            signals.push(TradeSignal {
                symbol: market_data.symbol.clone(),
                side: TradeSide::Buy,
                quantity,
                reason: format!(
                    "Buy and hold: close={}, volume={}",
                    market_data.close, market_data.volume
                ),
            });
        }

        Ok(signals)
    }
}

/// News-aware trading strategy that uses news events for decision making
#[derive(Debug)]
struct NewsAwareStrategy;

impl StrategyExecutor for NewsAwareStrategy {
    fn execute(
        &self,
        market_data: &MarketData,
        portfolio: &Portfolio,
        parameters: &HashMap<String, String>,
    ) -> Result<Vec<TradeSignal>> {
        let mut signals = Vec::new();

        // This is a simplified example - in reality, the strategy would use
        // the UnifiedFeatureExtractor to get features that include news sentiment,
        // volume, importance, etc., and make decisions based on those features.

        // For now, we'll create a basic momentum strategy with news consideration
        let sentiment_threshold = parameters
            .get("sentiment_threshold")
            .and_then(|s| s.parse::<f64>().ok())
            .unwrap_or(0.3);

        let max_position_size = parameters
            .get("max_position_size")
            .and_then(|s| s.parse::<f64>().ok())
            .unwrap_or(0.1); // 10% of portfolio

        // Check if we should enter a position
        let current_position = portfolio.get_position(&market_data.symbol);
        let is_long = current_position
            .map(|p| p.quantity > Decimal::ZERO)
            .unwrap_or(false);
        let _is_short = current_position
            .map(|p| p.quantity < Decimal::ZERO)
            .unwrap_or(false);

        // For demo purposes, simulate some basic logic
        let simulated_sentiment = 0.2; // Would come from features
        let simulated_momentum = 55.0; // Would come from features

        // Entry signals based on news sentiment and momentum
        if !is_long && simulated_sentiment > sentiment_threshold && simulated_momentum > 60.0 {
            // Bullish signal: positive sentiment + strong momentum
            let position_value = portfolio.cash
                * Decimal::from_f64_retain(max_position_size).unwrap_or_else(|| {
                    Decimal::from_f64_retain(0.1).unwrap_or(Decimal::ONE / Decimal::from(10))
                });
            let quantity = position_value / market_data.close;

            signals.push(TradeSignal {
                symbol: market_data.symbol.clone(),
                side: TradeSide::Buy,
                quantity,
                reason: format!(
                    "News-driven bullish signal: sentiment={:.2}, momentum={:.1}",
                    simulated_sentiment, simulated_momentum
                ),
            });
        }

        Ok(signals)
    }
}

impl StrategyEngine {
    /// Create a new strategy engine with repository injection - NO DATABASE COUPLING
    pub async fn new(
        config: &BacktestingStrategyConfig,
        repositories: Arc<dyn BacktestingRepositories>,
    ) -> Result<Self> {
        info!("Initializing strategy engine with repository injection - NO DIRECT DATABASE ACCESS");

        let mut strategies: HashMap<String, Box<dyn StrategyExecutor>> = HashMap::new();

        // Register built-in strategies
        strategies.insert(
            "moving_average_crossover".to_string(),
            Box::new(MovingAverageCrossoverStrategy),
        );
        strategies.insert("buy_and_hold".to_string(), Box::new(BuyAndHoldStrategy));
        strategies.insert(
            "news_aware_strategy".to_string(),
            Box::new(NewsAwareStrategy),
        );

        Ok(Self {
            config: config.clone(),
            repositories,
            strategies,
        })
    }

    /// Execute a backtest using repository pattern
    ///
    /// This is the backward-compatible entry point. For progress reporting use
    /// [`execute_backtest_with_progress`].
    pub async fn execute_backtest(
        &self,
        context: &crate::service::BacktestContext,
    ) -> Result<Vec<BacktestTrade>> {
        self.execute_backtest_with_progress(context, None).await
    }

    /// Execute a backtest with an optional progress callback channel
    ///
    /// When `progress_tx` is `Some`, progress percentages (0.0 ..= 100.0) are
    /// sent every 100 bars via non-blocking `try_send` so a slow receiver never
    /// blocks the replay loop.
    ///
    /// # Arguments
    ///
    /// * `context` - Backtest configuration and metadata
    /// * `progress_tx` - Optional channel for progress updates (0.0-100.0)
    pub async fn execute_backtest_with_progress(
        &self,
        context: &crate::service::BacktestContext,
        progress_tx: Option<&tokio::sync::mpsc::Sender<f64>>,
    ) -> Result<Vec<BacktestTrade>> {
        info!(
            "Executing backtest {} for strategy {} using repository pattern",
            context.id, context.strategy_name
        );

        // Get strategy executor
        let strategy = self
            .strategies
            .get(&context.strategy_name)
            .ok_or_else(|| anyhow::anyhow!("Strategy not found: {}", context.strategy_name))?;

        // Initialize portfolio
        let mut portfolio = Portfolio::new(
            Decimal::from_f64_retain(context.initial_capital).unwrap_or(Decimal::ZERO),
        );

        // Load market data for the backtest period using repository
        let market_data = self
            .load_market_data(
                &context.symbols,
                context.started_at,
                context
                    .completed_at
                    .unwrap_or(chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)),
            )
            .await?;

        let mut trade_counter = 0;
        let total_data_points = market_data.len();

        // Execute strategy on each data point
        for (i, data_point) in market_data.into_iter().enumerate() {
            // Generate signals
            let signals = strategy.execute(&data_point, &portfolio, &context.parameters)?;

            // Execute trades from signals
            for signal in signals {
                let trade_id = format!("{}_{}", context.id, trade_counter);
                trade_counter += 1;

                let commission_rate =
                    Decimal::from_f64_retain(self.config.commission_rate).unwrap_or(Decimal::ZERO);
                let slippage_rate =
                    Decimal::from_f64_retain(self.config.slippage_rate).unwrap_or(Decimal::ZERO);

                if let Some(_trade) = portfolio.execute_trade(
                    signal.symbol,
                    signal.side,
                    signal.quantity,
                    data_point.close,
                    data_point.timestamp,
                    commission_rate,
                    slippage_rate,
                    trade_id,
                    signal.reason,
                )? {
                    debug!(
                        "Executed trade: {} shares at {}",
                        signal.quantity, data_point.close
                    );
                }
            }

            // Send progress update every 100 bars (non-blocking)
            if i % 100 == 0 && total_data_points > 0 {
                let progress = (i as f64 / total_data_points as f64) * 100.0;
                debug!("Backtest progress: {:.1}%", progress);

                if let Some(tx) = progress_tx {
                    // try_send is non-blocking: if the channel is full we silently drop the update
                    let _ = tx.try_send(progress);
                }
            }
        }

        // Send final 100% progress
        if let Some(tx) = progress_tx {
            let _ = tx.try_send(100.0);
        }

        info!("Backtest completed with {} trades", portfolio.trades.len());
        Ok(portfolio.trades)
    }

    /// Load market data for backtesting using repository - NO DIRECT DATABASE ACCESS
    pub async fn load_market_data(
        &self,
        symbols: &[String],
        start_time: i64,
        end_time: i64,
    ) -> Result<Vec<MarketData>> {
        info!(
            "Loading market data for {} symbols from {} to {} using repository",
            symbols.len(),
            start_time,
            end_time
        );

        // Load historical data through repository - NO DIRECT DATABASE COUPLING
        let market_data = self
            .repositories
            .market_data()
            .load_historical_data(symbols, start_time, end_time)
            .await
            .context("Failed to load market data from repository")?;

        // Load news events and update feature extractor
        let start_date = DateTime::from_timestamp_nanos(start_time);
        let end_date = DateTime::from_timestamp_nanos(end_time);

        let news_events = self
            .repositories
            .news()
            .load_news_events(symbols, start_date, end_date)
            .await
            .context("Failed to load news events from repository")?;

        info!("Loaded {} news events for backtesting", news_events.len());

        // Update feature extractor with news events - simplified for now
        // In production, this would properly convert NewsEvent to the format expected by UnifiedFeatureExtractor

        info!("Loaded {} market data points", market_data.len());
        Ok(market_data)
    }
}

impl From<BacktestTrade> for crate::foxhunt::tli::Trade {
    fn from(trade: BacktestTrade) -> Self {
        Self {
            trade_id: trade.trade_id,
            symbol: trade.symbol,
            side: match trade.side {
                TradeSide::Buy => crate::foxhunt::tli::OrderSide::Buy as i32,
                TradeSide::Sell => crate::foxhunt::tli::OrderSide::Sell as i32,
            },
            quantity: trade.quantity.to_f64().unwrap_or(0.0),
            entry_price: trade.entry_price.to_f64().unwrap_or(0.0),
            exit_price: trade.exit_price.to_f64().unwrap_or(0.0),
            entry_time_unix_nanos: trade.entry_time.timestamp_nanos_opt().unwrap_or(0),
            exit_time_unix_nanos: trade.exit_time.timestamp_nanos_opt().unwrap_or(0),
            pnl: trade.pnl.to_f64().unwrap_or(0.0),
            return_percent: trade.return_percent.to_f64().unwrap_or(0.0),
            entry_signal: trade.entry_signal,
            exit_signal: trade.exit_signal,
        }
    }
}

impl std::fmt::Display for TradeSide {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TradeSide::Buy => write!(f, "Buy"),
            TradeSide::Sell => write!(f, "Sell"),
        }
    }
}
