//! Mock repository implementations for backtesting service tests

#![allow(dead_code, clippy::new_without_default)]

use anyhow::Result;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

use backtesting_service::performance::PerformanceMetrics;
use backtesting_service::repositories::*;
use backtesting_service::storage::BacktestSummary;
use backtesting_service::strategy_engine::{BacktestTrade, MarketData, NewsEvent};

/// Mock market data repository for testing
pub struct MockMarketDataRepository {
    pub data: Arc<RwLock<Vec<MarketData>>>,
}

impl MockMarketDataRepository {
    pub fn new() -> Self {
        Self {
            data: Arc::new(RwLock::new(Vec::new())),
        }
    }

    pub fn with_data(data: Vec<MarketData>) -> Self {
        Self {
            data: Arc::new(RwLock::new(data)),
        }
    }
}

#[async_trait]
impl MarketDataRepository for MockMarketDataRepository {
    async fn load_historical_data(
        &self,
        symbols: &[String],
        start_time: i64,
        end_time: i64,
    ) -> Result<Vec<MarketData>> {
        let data = self.data.read().await;
        let filtered: Vec<MarketData> = data
            .iter()
            .filter(|d| {
                symbols.contains(&d.symbol)
                    && d.timestamp.timestamp_nanos_opt().unwrap_or(0) >= start_time
                    && d.timestamp.timestamp_nanos_opt().unwrap_or(0) <= end_time
            })
            .cloned()
            .collect();
        Ok(filtered)
    }
}

/// Mock trading repository for testing
pub struct MockTradingRepository {
    pub trades: Arc<RwLock<HashMap<String, Vec<BacktestTrade>>>>,
    pub metrics: Arc<RwLock<HashMap<String, PerformanceMetrics>>>,
    pub backtests: Arc<RwLock<Vec<BacktestSummary>>>,
}

impl MockTradingRepository {
    pub fn new() -> Self {
        Self {
            trades: Arc::new(RwLock::new(HashMap::new())),
            metrics: Arc::new(RwLock::new(HashMap::new())),
            backtests: Arc::new(RwLock::new(Vec::new())),
        }
    }
}

#[async_trait]
impl TradingRepository for MockTradingRepository {
    async fn save_backtest_results(
        &self,
        backtest_id: &str,
        trades: &[BacktestTrade],
        metrics: &PerformanceMetrics,
    ) -> Result<()> {
        self.trades
            .write()
            .await
            .insert(backtest_id.to_string(), trades.to_vec());
        self.metrics
            .write()
            .await
            .insert(backtest_id.to_string(), metrics.clone());
        Ok(())
    }

    async fn load_backtest_results(
        &self,
        backtest_id: &str,
    ) -> Result<(Vec<BacktestTrade>, PerformanceMetrics)> {
        let trades = self
            .trades
            .read()
            .await
            .get(backtest_id)
            .cloned()
            .unwrap_or_default();
        let metrics = self
            .metrics
            .read()
            .await
            .get(backtest_id)
            .cloned()
            .unwrap_or_default();
        Ok((trades, metrics))
    }

    async fn list_backtests(
        &self,
        limit: u32,
        offset: u32,
        strategy_name: Option<String>,
        status_filter: Option<backtesting_service::foxhunt::tli::BacktestStatus>,
    ) -> Result<Vec<BacktestSummary>> {
        let backtests = self.backtests.read().await;
        let filtered: Vec<BacktestSummary> = backtests
            .iter()
            .filter(|bt| {
                let name_match = strategy_name
                    .as_ref()
                    .map(|n| bt.strategy_name == *n)
                    .unwrap_or(true);
                let status_match = status_filter.map(|s| bt.status == s).unwrap_or(true);
                name_match && status_match
            })
            .skip(offset as usize)
            .take(limit as usize)
            .cloned()
            .collect();
        Ok(filtered)
    }
}

/// Mock news repository for testing
pub struct MockNewsRepository {
    pub events: Arc<RwLock<Vec<NewsEvent>>>,
}

impl MockNewsRepository {
    pub fn new() -> Self {
        Self {
            events: Arc::new(RwLock::new(Vec::new())),
        }
    }

    pub fn with_events(events: Vec<NewsEvent>) -> Self {
        Self {
            events: Arc::new(RwLock::new(events)),
        }
    }
}

#[async_trait]
impl NewsRepository for MockNewsRepository {
    async fn load_news_events(
        &self,
        _symbols: &[String],
        _start_time: DateTime<Utc>,
        _end_time: DateTime<Utc>,
    ) -> Result<Vec<NewsEvent>> {
        let events = self.events.read().await;
        Ok(events.clone())
    }
}

/// Mock combined repositories for testing
pub struct MockBacktestingRepositories {
    market_data: Box<dyn MarketDataRepository>,
    trading: Box<dyn TradingRepository>,
    news: Box<dyn NewsRepository>,
}

impl MockBacktestingRepositories {
    pub fn new(
        market_data: Box<dyn MarketDataRepository>,
        trading: Box<dyn TradingRepository>,
        news: Box<dyn NewsRepository>,
    ) -> Self {
        Self {
            market_data,
            trading,
            news,
        }
    }
}

#[async_trait]
impl BacktestingRepositories for MockBacktestingRepositories {
    fn market_data(&self) -> &dyn MarketDataRepository {
        self.market_data.as_ref()
    }

    fn trading(&self) -> &dyn TradingRepository {
        self.trading.as_ref()
    }

    fn news(&self) -> &dyn NewsRepository {
        self.news.as_ref()
    }
}

/// Helper function to generate sample market data
///
/// Uses deterministic price pattern to avoid flaky tests
pub fn generate_sample_market_data(
    symbol: &str,
    num_points: usize,
    start_price: f64,
    volatility: f64,
) -> Vec<MarketData> {
    use rust_decimal::Decimal;

    let mut data = Vec::new();
    let start_time = Utc::now() - chrono::Duration::days(num_points as i64);

    for i in 0..num_points {
        // Deterministic oscillation: price varies +/-volatility in a sine wave pattern
        // This ensures price crosses any reasonable trigger level multiple times
        let phase = (i as f64) / (num_points as f64) * 4.0 * std::f64::consts::PI;
        let price_multiplier = 1.0 + volatility * phase.sin();
        let price = start_price * price_multiplier;

        let timestamp = start_time + chrono::Duration::days(i as i64);
        let open = Decimal::from_f64_retain(price * 0.99).unwrap_or(Decimal::ZERO);
        let high = Decimal::from_f64_retain(price * 1.02).unwrap_or(Decimal::ZERO);
        let low = Decimal::from_f64_retain(price * 0.98).unwrap_or(Decimal::ZERO);
        let close = Decimal::from_f64_retain(price).unwrap_or(Decimal::ZERO);
        // Deterministic volume based on index
        let volume =
            Decimal::from_f64_retain(2_000_000.0 + (i as f64 * 1000.0)).unwrap_or(Decimal::ZERO);

        data.push(MarketData {
            symbol: symbol.to_string(),
            timestamp,
            open,
            high,
            low,
            close,
            volume,
        });
    }

    data
}

/// Helper function to generate sample news events
pub fn generate_sample_news_events(_symbols: &[String], num_events: usize) -> Vec<NewsEvent> {
    let mut events = Vec::new();
    for _ in 0..num_events {
        events.push(NewsEvent);
    }
    events
}

/// Get project root directory
///
/// Resolves the path from the project root, handling different working directories.
pub fn get_project_root() -> String {
    // Try to find project root by looking for Cargo.toml
    let mut current = std::env::current_dir().expect("INVARIANT: Current directory should be accessible");

    // If we're in a subdirectory, go up until we find the workspace root
    while !current.join("Cargo.toml").exists() || !current.join("test_data").exists() {
        if !current.pop() {
            // Fallback to relative path if we can't find root
            return "../..".to_string();
        }
    }

    current.to_string_lossy().to_string()
}

/// Get absolute path to the test DBN file
///
/// Resolves the path from the project root, handling different working directories.
pub fn get_dbn_test_file_path() -> String {
    let root = get_project_root();
    format!(
        "{}/test_data/real/databento/ES.FUT_ohlcv-1m_2024-01-02.dbn",
        root
    )
}

/// Create a DBN-based market data repository for testing with real data
///
/// This function creates a repository that loads data from the real DBN file
/// in test_data/real/databento/.
///
/// # Returns
///
/// DbnMarketDataRepository configured with ES.FUT data for 2024-01-02
pub async fn create_dbn_repository() -> Result<Box<dyn MarketDataRepository>, anyhow::Error> {
    use backtesting_service::dbn_repository::DbnMarketDataRepository;
    use std::collections::HashMap;

    let mut file_mapping = HashMap::new();
    file_mapping.insert("ES.FUT".to_string(), get_dbn_test_file_path());

    let repo = DbnMarketDataRepository::new(file_mapping).await?;
    Ok(Box::new(repo))
}
