//! Shared DBN file loading utilities for hyperopt adapters.
//!
//! Provides recursive .dbn/.dbn.zst file discovery and OHLCV bar decoding.
//! Used by TFT, Mamba2, and DQN hyperopt adapters to load training data
//! from Databento files.

use std::path::{Path, PathBuf};

use tracing::info;

use crate::features::extraction::OHLCVBar;

/// Recursively collect all .dbn and .dbn.zst files from a directory.
pub fn collect_dbn_files(dir: &Path) -> Vec<PathBuf> {
    let mut files = Vec::new();
    if let Ok(entries) = std::fs::read_dir(dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.is_dir() {
                files.extend(collect_dbn_files(&path));
            } else if path.extension().and_then(|s| s.to_str()) == Some("dbn")
                || path.to_string_lossy().ends_with(".dbn.zst")
            {
                files.push(path);
            } else {
                // Skip non-DBN files
            }
        }
    }
    files.sort();
    files
}

/// Decode OHLCV bars from a DBN decoder stream.
pub fn decode_ohlcv_bars<R: std::io::Read>(
    decoder: &mut dbn::decode::dbn::Decoder<R>,
) -> anyhow::Result<Vec<OHLCVBar>> {
    use dbn::decode::DecodeRecordRef;
    use dbn::OhlcvMsg;

    let mut bars = Vec::new();
    while let Some(record_ref) = decoder
        .decode_record_ref()
        .map_err(|e| anyhow::anyhow!("Failed to decode record: {}", e))?
    {
        if let Some(ohlcv) = record_ref.get::<OhlcvMsg>() {
            bars.push(OHLCVBar {
                timestamp: chrono::DateTime::from_timestamp_nanos(ohlcv.hd.ts_event as i64),
                open: ohlcv.open as f64 / 1e9,
                high: ohlcv.high as f64 / 1e9,
                low: ohlcv.low as f64 / 1e9,
                close: ohlcv.close as f64 / 1e9,
                volume: ohlcv.volume as f64,
            });
        }
    }
    Ok(bars)
}

/// Load all OHLCV bars from DBN files in a directory, sorted chronologically.
pub fn load_bars_from_dbn_dir(dir: &Path) -> anyhow::Result<Vec<OHLCVBar>> {
    use dbn::decode::dbn::Decoder as DbnDecoder;

    let dbn_files = collect_dbn_files(dir);
    if dbn_files.is_empty() {
        anyhow::bail!("No .dbn or .dbn.zst files found in {}", dir.display());
    }

    info!("Found {} DBN files in {}", dbn_files.len(), dir.display());

    let mut all_bars = Vec::new();
    for dbn_file in &dbn_files {
        let is_zstd = dbn_file.to_string_lossy().ends_with(".dbn.zst");
        let bars = if is_zstd {
            let mut decoder = DbnDecoder::from_zstd_file(dbn_file)
                .map_err(|e| anyhow::anyhow!("Failed to open {}: {}", dbn_file.display(), e))?;
            decode_ohlcv_bars(&mut decoder)?
        } else {
            let file = std::fs::File::open(dbn_file)
                .map_err(|e| anyhow::anyhow!("Failed to open {}: {}", dbn_file.display(), e))?;
            let buf = std::io::BufReader::new(file);
            let mut decoder = DbnDecoder::new(buf)
                .map_err(|e| anyhow::anyhow!("Failed to decode {}: {}", dbn_file.display(), e))?;
            decode_ohlcv_bars(&mut decoder)?
        };
        info!("  {} -> {} bars", dbn_file.display(), bars.len());
        all_bars.extend(bars);
    }

    all_bars.sort_by_key(|b| b.timestamp);
    info!(
        "Loaded {} bars total from {} DBN files",
        all_bars.len(),
        dbn_files.len()
    );
    Ok(all_bars)
}
