use std::collections::HashMap;

use bt_expr::ScalarValue;
use bt_kernel::{Result, Timestamp, NANOS_PER_SECOND};
use chrono::Utc;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use uuid::Uuid;

use crate::orchestrator::{BacktestConfig, BacktestRunner, StrategyPlan};

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct RunManifest {
    pub run_id: String,
    pub timestamp: Timestamp,
    pub strategy_name: String,
    pub strategy_code_hash: String,
    pub parameters: HashMap<String, ScalarValue>,
    pub config: BacktestConfig,
    pub data_versions: HashMap<String, String>,
    pub rng_seed: u64,
    pub engine_version: String,
    pub platform: String,
}

pub fn build_run_manifest(
    strategy: &StrategyPlan,
    config: &BacktestConfig,
    data_versions: HashMap<String, String>,
) -> Result<RunManifest> {
    let timestamp = utc_now_timestamp()?;
    Ok(RunManifest {
        run_id: Uuid::new_v4().to_string(),
        timestamp,
        strategy_name: strategy.name.clone(),
        strategy_code_hash: strategy_hash(strategy),
        parameters: strategy.parameters.clone(),
        config: config.clone(),
        data_versions,
        rng_seed: config.rng_seed.unwrap_or(0),
        engine_version: env!("CARGO_PKG_VERSION").to_string(),
        platform: format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH),
    })
}

/// Replay a backtest from a saved manifest.
///
/// Verifies the engine version matches, then re-runs the strategy with
/// the manifest's parameters and config. The caller must provide the
/// compiled strategy plan (from the original `StrategyDef`).
pub fn replay(
    manifest: &RunManifest,
    strategy: &StrategyPlan,
    data_store: &dyn bt_data::DataStore,
) -> Result<crate::orchestrator::BacktestResult> {
    let current_version = env!("CARGO_PKG_VERSION");
    if manifest.engine_version != current_version {
        return Err(bt_kernel::BtError::Strategy(format!(
            "engine version mismatch: manifest={}, current={}. \
             Results may differ across versions.",
            manifest.engine_version, current_version
        )));
    }

    // Apply manifest's parameters to the strategy.
    let replayed = crate::sweep::strategy_with_params(strategy, &manifest.parameters)?;

    let runner = crate::orchestrator::SimpleBacktestRunner::new();
    runner.run(&replayed, &manifest.config, data_store)
}

fn strategy_hash(strategy: &StrategyPlan) -> String {
    let mut hasher = Sha256::new();
    hasher.update(strategy.name.as_bytes());
    hasher.update(format!("{:?}", strategy.signals).as_bytes());
    hasher.update(format!("{:?}", strategy.position_sizing).as_bytes());
    hasher.update(format!("{:?}", strategy.parameters).as_bytes());
    let digest = hasher.finalize();
    let mut out = String::with_capacity(digest.len() * 2);
    for byte in digest {
        out.push_str(&format!("{byte:02x}"));
    }
    out
}

fn utc_now_timestamp() -> Result<Timestamp> {
    let now = Utc::now();
    let nanos = now
        .timestamp()
        .checked_mul(NANOS_PER_SECOND)
        .and_then(|value| value.checked_add(i64::from(now.timestamp_subsec_nanos())))
        .ok_or_else(|| bt_kernel::BtError::Data("timestamp overflow".to_string()))?;
    Ok(Timestamp::from_nanos(nanos))
}
