//! Parameter stability analysis.
//!
//! Sweeps a single parameter across a range of values and measures how stable
//! a given metric is. A high stability score (close to 1.0) means the strategy
//! is robust to parameter changes — it's not over-fit to a single magic number.

use std::collections::HashMap;

use bt_core::orchestrator::{BacktestConfig, StrategyPlan};
use bt_core::sweep::strategy_with_params;
use bt_core::{BacktestRunner, BatchResultLite, SimpleBacktestRunner};
use bt_data::DataStore;
use bt_expr::ScalarValue;
use bt_kernel::Result;
use serde::{Deserialize, Serialize};

use crate::extract_metric;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StabilityConfig {
    pub param_name: String,
    pub values: Vec<ScalarValue>,
    pub metric: String,
    pub max_parallelism: usize,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StabilityResult {
    pub param_name: String,
    pub values: Vec<ScalarValue>,
    pub metric_values: Vec<f64>,
    /// 1 - (std / |mean|), clamped to [0, 1]. Higher = more stable.
    pub stability_score: f64,
    pub mean_metric: f64,
    pub std_metric: f64,
    pub results: Vec<BatchResultLite>,
}

/// Sweep a single parameter and compute stability metrics.
pub fn run_stability(
    strategy: &StrategyPlan,
    stability_config: &StabilityConfig,
    backtest_config: &BacktestConfig,
    data_store: &dyn DataStore,
) -> Result<StabilityResult> {
    // Build one strategy per parameter value.
    let strategies: Vec<StrategyPlan> = stability_config
        .values
        .iter()
        .map(|value| {
            let mut params = HashMap::new();
            params.insert(stability_config.param_name.clone(), value.clone());
            strategy_with_params(strategy, &params)
        })
        .collect::<Result<Vec<_>>>()?;

    // Run all via batch_lite.
    let runner = SimpleBacktestRunner::new();
    let results = runner.run_batch_lite(
        &strategies,
        backtest_config,
        data_store,
        stability_config.max_parallelism,
    )?;

    // Extract metric values.
    let metric_values: Vec<f64> = results
        .iter()
        .map(|r| extract_metric(&r.metrics, &stability_config.metric))
        .collect();

    // Compute mean and std.
    let n = metric_values.len() as f64;
    let mean = if n > 0.0 {
        metric_values.iter().sum::<f64>() / n
    } else {
        0.0
    };
    let variance = if n > 1.0 {
        metric_values
            .iter()
            .map(|v| (v - mean).powi(2))
            .sum::<f64>()
            / (n - 1.0)
    } else {
        0.0
    };
    let std = variance.sqrt();

    // Stability score: 1 - coefficient of variation, clamped to [0, 1].
    let stability_score = if mean.abs() > 1e-15 {
        (1.0 - std / mean.abs()).clamp(0.0, 1.0)
    } else {
        0.0
    };

    Ok(StabilityResult {
        param_name: stability_config.param_name.clone(),
        values: stability_config.values.clone(),
        metric_values,
        stability_score,
        mean_metric: mean,
        std_metric: std,
        results,
    })
}
