//! DQN Replay Strategy - Replays pre-computed DQN actions through backtesting engine
//!
//! This strategy reads a CSV file of pre-computed DQN actions (one per bar) and
//! converts them to trading signals that the backtesting engine executes.
//! It maintains position state to ensure proper action mapping (e.g., Buy when flat,
//! Sell to close long, etc.).

use anyhow::{Context, Result};
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use common::{Order, Position, Price, Quantity, Symbol};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use trading_engine::types::events::MarketEvent;

use crate::strategy_tester::{
    SignalType, Strategy, StrategyConfig, StrategyContext, StrategyResult, TradingSignal,
};

/// DQN Replay Strategy - Replays pre-computed DQN actions through backtesting engine
///
/// # Action Synchronization
///
/// - **Assumption**: Actions CSV has 1 row per market bar (chronological order)
/// - **Index Management**: `current_index` increments per `on_market_event()` call
/// - **Bounds Checking**: Errors if action count != bar count
///
/// # Position State Machine
///
/// - **Flat**: No position, can Buy or Sell
/// - **Long**: Positive quantity, can CloseLong or AddToLong
/// - **Short**: Negative quantity, can CloseShort or AddToShort
///
#[derive(Debug)]
pub struct DQNReplayStrategy {
    /// Strategy name for identification
    name: String,

    /// Pre-loaded DQN actions from CSV (chronological order)
    actions: Vec<DQNAction>,

    /// Current action index (increments per bar)
    current_index: usize,

    /// Current position state (None = flat, Some = long/short)
    current_position: PositionState,

    /// Symbol being traded (single-asset for simplicity)
    symbol: Symbol,

    /// Strategy configuration
    config: Option<StrategyConfig>,

    /// Initial capital for metrics
    initial_capital: Decimal,

    /// Performance tracking
    metrics: PerformanceMetrics,
}

/// Pre-computed DQN action from CSV
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DQNAction {
    /// Bar index (0-based, matches CSV row number - 1)
    pub bar_index: usize,

    /// DQN action decision (0=Buy, 1=Sell, 2=Hold)
    pub action: TradingActionType,

    /// Q-values for all 3 actions [buy_q, sell_q, hold_q]
    pub q_values: [f64; 3],

    /// Timestamp of the action (optional, for validation)
    pub timestamp: Option<DateTime<Utc>>,
}

/// Trading action type from DQN model
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TradingActionType {
    /// Buy signal (action=0)
    Buy = 0,
    /// Sell signal (action=1)
    Sell = 1,
    /// Hold signal (action=2)
    Hold = 2,
}

impl TradingActionType {
    /// Parse from integer (CSV column)
    pub fn from_int(val: u8) -> Result<Self> {
        match val {
            0 => Ok(TradingActionType::Buy),
            1 => Ok(TradingActionType::Sell),
            2 => Ok(TradingActionType::Hold),
            _ => Err(anyhow::anyhow!(
                "Invalid action value: {} (expected 0-2)",
                val
            )),
        }
    }
}

/// Position state for tracking current holdings
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PositionState {
    /// No position (cash)
    Flat,
    /// Long position (positive quantity)
    Long,
    /// Short position (negative quantity)
    Short,
}

impl PositionState {
    /// Determine position state from quantity
    pub fn from_quantity(quantity: Decimal) -> Self {
        if quantity > Decimal::ZERO {
            PositionState::Long
        } else if quantity < Decimal::ZERO {
            PositionState::Short
        } else {
            PositionState::Flat
        }
    }
}

/// Performance metrics tracked during execution
#[derive(Debug, Clone, Default)]
struct PerformanceMetrics {
    total_actions: usize,
    buy_signals: usize,
    sell_signals: usize,
    hold_signals: usize,
    position_flips: usize, // Long->Short or Short->Long
    index_mismatches: usize,
}

impl DQNReplayStrategy {
    /// Create new DQN replay strategy from CSV file
    ///
    /// # CSV Format
    ///
    /// Expected columns: `bar_index,action,q_buy,q_sell,q_hold[,timestamp]`
    ///
    /// Example:
    /// ```csv
    /// bar_index,action,q_buy,q_sell,q_hold
    /// 0,2,0.123,-0.456,0.789
    /// 1,0,1.234,0.567,0.890
    /// 2,1,-0.345,1.678,0.234
    /// ```
    ///
    /// # Arguments
    ///
    /// * `name` - Strategy name
    /// * `csv_path` - Path to DQN actions CSV
    /// * `symbol` - Symbol to trade
    ///
    /// # Errors
    ///
    /// Returns error if:
    /// - CSV file not found or invalid format
    /// - Action values out of range (0-2)
    /// - Duplicate bar indices
    ///
    pub fn from_csv<P: AsRef<std::path::Path>>(
        name: String,
        csv_path: P,
        symbol: Symbol,
    ) -> Result<Self> {
        use csv::ReaderBuilder;
        use std::fs::File;

        let file = File::open(csv_path.as_ref()).with_context(|| {
            format!("Failed to open CSV: {}", csv_path.as_ref().display())
        })?;

        let mut reader = ReaderBuilder::new().has_headers(true).from_reader(file);

        let mut actions = Vec::new();

        for (row_num, result) in reader.records().enumerate() {
            let record =
                result.with_context(|| format!("Failed to parse CSV row {}", row_num))?;

            // Parse CSV columns
            let bar_index: usize = record
                .get(0)
                .ok_or_else(|| anyhow::anyhow!("Missing bar_index at row {}", row_num))?
                .parse()
                .with_context(|| format!("Invalid bar_index at row {}", row_num))?;

            let action_int: u8 = record
                .get(1)
                .ok_or_else(|| anyhow::anyhow!("Missing action at row {}", row_num))?
                .parse()
                .with_context(|| format!("Invalid action at row {}", row_num))?;

            let action = TradingActionType::from_int(action_int)
                .with_context(|| format!("Invalid action value at row {}", row_num))?;

            let q_buy: f64 = record
                .get(2)
                .ok_or_else(|| anyhow::anyhow!("Missing q_buy at row {}", row_num))?
                .parse()
                .with_context(|| format!("Invalid q_buy at row {}", row_num))?;

            let q_sell: f64 = record
                .get(3)
                .ok_or_else(|| anyhow::anyhow!("Missing q_sell at row {}", row_num))?
                .parse()
                .with_context(|| format!("Invalid q_sell at row {}", row_num))?;

            let q_hold: f64 = record
                .get(4)
                .ok_or_else(|| anyhow::anyhow!("Missing q_hold at row {}", row_num))?
                .parse()
                .with_context(|| format!("Invalid q_hold at row {}", row_num))?;

            // Optional timestamp column
            let timestamp = record
                .get(5)
                .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
                .map(|dt| dt.with_timezone(&chrono::Utc));

            actions.push(DQNAction {
                bar_index,
                action,
                q_values: [q_buy, q_sell, q_hold],
                timestamp,
            });
        }

        // Validate chronological order and no gaps
        for (i, action) in actions.iter().enumerate() {
            if action.bar_index != i {
                return Err(anyhow::anyhow!(
                    "Bar index mismatch at row {}: expected {}, got {}",
                    i,
                    i,
                    action.bar_index
                ));
            }
        }

        tracing::info!(
            "Loaded {} DQN actions from CSV for symbol {}",
            actions.len(),
            symbol
        );

        Ok(Self {
            name,
            actions,
            current_index: 0,
            current_position: PositionState::Flat,
            symbol,
            config: None,
            initial_capital: Decimal::ZERO,
            metrics: PerformanceMetrics::default(),
        })
    }

    /// Convert DQN action to TradingSignal based on current position state
    fn convert_action_to_signal(
        &mut self,
        dqn_action: &DQNAction,
        current_price: Price,
        _context: &StrategyContext,
    ) -> Result<Vec<TradingSignal>> {
        use SignalType::*;

        // Determine position size (fixed for simplicity, can be made dynamic)
        let position_size = Quantity::from_f64(1.0).unwrap_or(Quantity::ZERO);

        let signal_type = match (dqn_action.action, self.current_position) {
            // HOLD: No action regardless of position
            (TradingActionType::Hold, _) => return Ok(vec![]),

            // BUY LOGIC
            (TradingActionType::Buy, PositionState::Flat) => {
                // Flat -> Long: Buy to open
                self.current_position = PositionState::Long;
                Buy
            }
            (TradingActionType::Buy, PositionState::Long) => {
                // Long -> Long: Hold (already long, DQN wants to stay long)
                return Ok(vec![]);
            }
            (TradingActionType::Buy, PositionState::Short) => {
                // Short -> Long: Close short + Buy to open
                self.current_position = PositionState::Long;
                self.metrics.position_flips += 1;
                // NOTE: This requires TWO signals: Cover + Buy
                // Simplified to single reversal signal for now
                Cover // Close short position
            }

            // SELL LOGIC
            (TradingActionType::Sell, PositionState::Flat) => {
                // Flat -> Short: Sell to open (short)
                self.current_position = PositionState::Short;
                Short
            }
            (TradingActionType::Sell, PositionState::Short) => {
                // Short -> Short: Hold (already short, DQN wants to stay short)
                return Ok(vec![]);
            }
            (TradingActionType::Sell, PositionState::Long) => {
                // Long -> Short: Close long + Sell to open
                self.current_position = PositionState::Short;
                self.metrics.position_flips += 1;
                CloseLong // Close long position
            }
        };

        // Build trading signal
        let signal = TradingSignal {
            symbol: self.symbol.clone(),
            signal_type,
            quantity: position_size,
            target_price: Some(current_price),
            stop_loss: None,  // No stop loss for replay (already decided)
            take_profit: None, // No take profit for replay
            confidence: Decimal::from_f64_retain(dqn_action.q_values[dqn_action.action as usize])
                .unwrap_or(Decimal::ZERO),
            metadata: {
                let mut meta = HashMap::new();
                meta.insert("strategy".to_string(), serde_json::json!("dqn_replay"));
                meta.insert(
                    "bar_index".to_string(),
                    serde_json::json!(dqn_action.bar_index),
                );
                meta.insert(
                    "q_values".to_string(),
                    serde_json::json!(dqn_action.q_values),
                );
                meta
            },
        };

        Ok(vec![signal])
    }
}

#[async_trait(?Send)]
impl Strategy for DQNReplayStrategy {
    fn name(&self) -> &str {
        &self.name
    }

    async fn initialize(
        &mut self,
        initial_capital: Decimal,
        config: StrategyConfig,
    ) -> Result<()> {
        self.initial_capital = initial_capital;
        self.config = Some(config);

        tracing::info!(
            "DQNReplayStrategy initialized: {} actions loaded, symbol: {}, capital: {}",
            self.actions.len(),
            self.symbol,
            initial_capital
        );

        Ok(())
    }

    async fn on_market_event(
        &mut self,
        event: &MarketEvent,
        context: &StrategyContext,
    ) -> Result<Vec<TradingSignal>> {
        // Only process Bar events (ignore Quotes, Trades, etc.)
        let (symbol, close) = match event {
            MarketEvent::Bar { symbol, close, .. } => (symbol, close),
            _ => return Ok(vec![]), // Ignore non-bar events
        };

        // Ignore other symbols
        if symbol != &self.symbol {
            return Ok(vec![]);
        }

        // Check if we have an action for this bar
        if self.current_index >= self.actions.len() {
            return Err(anyhow::anyhow!(
                "Action index out of bounds: {} >= {} (possible mismatch between CSV and Parquet)",
                self.current_index,
                self.actions.len()
            ));
        }

        // Get current action (clone to avoid borrow checker issues)
        let dqn_action = self.actions[self.current_index].clone();

        // Validate bar index matches (optional, for debugging)
        if dqn_action.bar_index != self.current_index {
            tracing::warn!(
                "Action bar_index mismatch: CSV says {}, but we're at index {}",
                dqn_action.bar_index,
                self.current_index
            );
            self.metrics.index_mismatches += 1;
        }

        // Update metrics
        self.metrics.total_actions += 1;
        match dqn_action.action {
            TradingActionType::Buy => self.metrics.buy_signals += 1,
            TradingActionType::Sell => self.metrics.sell_signals += 1,
            TradingActionType::Hold => self.metrics.hold_signals += 1,
        }

        // Convert DQN action to trading signal
        let signal = self.convert_action_to_signal(&dqn_action, *close, context)?;

        // Increment index for next bar
        self.current_index += 1;

        Ok(signal)
    }

    async fn on_order_update(&mut self, order: &Order, _context: &StrategyContext) -> Result<()> {
        // Log order execution for debugging
        tracing::debug!(
            "Order update: {:?} {} @ {:?}",
            order.side,
            order.quantity,
            order.average_price
        );

        Ok(())
    }

    async fn on_position_update(
        &mut self,
        position: &Position,
        _context: &StrategyContext,
    ) -> Result<()> {
        // Update our position state based on actual position from backtesting engine
        self.current_position = PositionState::from_quantity(position.quantity);

        tracing::debug!(
            "Position updated: {:?} (quantity: {})",
            self.current_position,
            position.quantity
        );

        Ok(())
    }

    async fn finalize(&mut self, context: &StrategyContext) -> Result<StrategyResult> {
        // Calculate final performance metrics
        let total_return = if self.initial_capital > Decimal::ZERO {
            (context.account_balance - self.initial_capital) / self.initial_capital
        } else {
            Decimal::ZERO
        };

        tracing::info!(
            "DQN Replay Strategy Finalized - Metrics: total={}, buy={}, sell={}, hold={}, flips={}, mismatches={}",
            self.metrics.total_actions,
            self.metrics.buy_signals,
            self.metrics.sell_signals,
            self.metrics.hold_signals,
            self.metrics.position_flips,
            self.metrics.index_mismatches
        );

        // Create strategy result (simplified, full implementation would calculate all metrics)
        Ok(StrategyResult {
            strategy_name: self.name.clone(),
            total_return,
            annualized_return: total_return, // Simplified
            max_drawdown: Decimal::ZERO,     // Would need to track peak value
            sharpe_ratio: Decimal::ZERO,     // Would need daily returns
            total_trades: self.metrics.buy_signals as u64 + self.metrics.sell_signals as u64,
            win_rate: Decimal::ZERO,    // Would need to analyze trades
            avg_trade_return: Decimal::ZERO,
            final_value: context.account_balance,
            trades: vec![], // Would convert trade_history to TradeRecord format
            performance_timeline: vec![],
        })
    }

    async fn get_state(&self) -> Result<serde_json::Value> {
        Ok(serde_json::json!({
            "name": self.name,
            "current_index": self.current_index,
            "total_actions": self.actions.len(),
            "current_position": format!("{:?}", self.current_position),
            "metrics": {
                "total_actions": self.metrics.total_actions,
                "buy_signals": self.metrics.buy_signals,
                "sell_signals": self.metrics.sell_signals,
                "hold_signals": self.metrics.hold_signals,
                "position_flips": self.metrics.position_flips,
                "index_mismatches": self.metrics.index_mismatches,
            }
        }))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Write;
    use tempfile::NamedTempFile;

    /// Helper to create a test CSV file
    fn create_test_csv(actions: &[(usize, u8, [f64; 3])]) -> Result<NamedTempFile> {
        let mut file = NamedTempFile::new()?;
        writeln!(file, "bar_index,action,q_buy,q_sell,q_hold")?;
        for (bar_index, action, q_values) in actions {
            writeln!(
                file,
                "{},{},{},{},{}",
                bar_index, action, q_values[0], q_values[1], q_values[2]
            )?;
        }
        file.flush()?;
        Ok(file)
    }

    #[test]
    fn test_csv_loading_valid() {
        let csv_file = create_test_csv(&[
            (0, 2, [0.1, 0.2, 0.9]), // Hold
            (1, 0, [0.8, 0.1, 0.3]), // Buy
            (2, 1, [0.2, 0.7, 0.4]), // Sell
        ])
        .unwrap();

        let strategy = DQNReplayStrategy::from_csv(
            "test_strategy".to_string(),
            csv_file.path(),
            Symbol::from("ES"),
        )
        .unwrap();

        assert_eq!(strategy.actions.len(), 3);
        assert_eq!(strategy.actions[0].action, TradingActionType::Hold);
        assert_eq!(strategy.actions[1].action, TradingActionType::Buy);
        assert_eq!(strategy.actions[2].action, TradingActionType::Sell);
    }

    #[test]
    fn test_csv_loading_invalid_action() {
        let csv_file = create_test_csv(&[
            (0, 3, [0.1, 0.2, 0.3]), // Invalid action
        ])
        .unwrap();

        let result = DQNReplayStrategy::from_csv(
            "test_strategy".to_string(),
            csv_file.path(),
            Symbol::from("ES"),
        );

        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("Invalid action value"));
    }

    #[test]
    fn test_csv_loading_gap_in_indices() {
        let csv_file = create_test_csv(&[
            (0, 2, [0.1, 0.2, 0.9]),
            (2, 0, [0.8, 0.1, 0.3]), // Gap: missing index 1
        ])
        .unwrap();

        let result = DQNReplayStrategy::from_csv(
            "test_strategy".to_string(),
            csv_file.path(),
            Symbol::from("ES"),
        );

        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("Bar index mismatch"));
    }

    #[test]
    fn test_position_state_from_quantity() {
        use rust_decimal_macros::dec;

        assert_eq!(
            PositionState::from_quantity(dec!(10.0)),
            PositionState::Long
        );
        assert_eq!(
            PositionState::from_quantity(dec!(-5.0)),
            PositionState::Short
        );
        assert_eq!(
            PositionState::from_quantity(Decimal::ZERO),
            PositionState::Flat
        );
    }

    #[test]
    fn test_trading_action_type_from_int() {
        assert_eq!(
            TradingActionType::from_int(0).unwrap(),
            TradingActionType::Buy
        );
        assert_eq!(
            TradingActionType::from_int(1).unwrap(),
            TradingActionType::Sell
        );
        assert_eq!(
            TradingActionType::from_int(2).unwrap(),
            TradingActionType::Hold
        );
        assert!(TradingActionType::from_int(3).is_err());
    }
}
