//! Local file provider that reads Parquet files from the data store.
//!
//! Directory layout:
//! ```text
//! {root}/
//!   {SYMBOL}/
//!     bars/
//!       *.parquet
//! ```
//!
//! All providers (Binance, Polygon, Databento, CSV import…) ultimately write
//! Parquet into this layout.  `LocalProvider` is the read path.

use std::path::PathBuf;

use arrow::compute::concat_batches;
use arrow::record_batch::RecordBatch;
use async_trait::async_trait;
use bt_kernel::{AssetClass, BtError, Result, TimeRange};
use parquet::arrow::arrow_reader::ParquetRecordBatchReaderBuilder;

use crate::provider::{BarInterval, DataProvider, ProviderSymbol, RateLimitInfo};

/// Reads Parquet files from a local directory tree.
pub struct LocalProvider {
    root: PathBuf,
}

impl LocalProvider {
    pub fn new(root: PathBuf) -> Self {
        Self { root }
    }

    fn bars_dir(&self, symbol: &str) -> PathBuf {
        self.root.join(symbol.to_uppercase()).join("bars")
    }

    fn read_parquet_dir(dir: &std::path::Path) -> Result<Option<RecordBatch>> {
        if !dir.exists() {
            return Ok(None);
        }

        let mut parquet_files: Vec<PathBuf> = std::fs::read_dir(dir)
            .map_err(|e| BtError::Data(format!("reading dir {}: {e}", dir.display())))?
            .filter_map(|entry| {
                let path = entry.ok()?.path();
                if path.extension().and_then(|e| e.to_str()) == Some("parquet") {
                    Some(path)
                } else {
                    None
                }
            })
            .collect();

        if parquet_files.is_empty() {
            return Ok(None);
        }

        parquet_files.sort();

        let mut batches = Vec::with_capacity(parquet_files.len());
        for path in &parquet_files {
            let file = std::fs::File::open(path)
                .map_err(|e| BtError::Data(format!("opening {}: {e}", path.display())))?;
            let reader = ParquetRecordBatchReaderBuilder::try_new(file)
                .map_err(|e| BtError::Data(format!("reading parquet {}: {e}", path.display())))?
                .build()
                .map_err(|e| BtError::Data(format!("building reader {}: {e}", path.display())))?;

            for batch_result in reader {
                let batch = batch_result.map_err(|e| {
                    BtError::Data(format!("reading batch from {}: {e}", path.display()))
                })?;
                batches.push(batch);
            }
        }

        if batches.is_empty() {
            return Ok(None);
        }

        let schema = batches[0].schema();
        let merged = concat_batches(&schema, &batches)?;
        Ok(Some(merged))
    }
}

#[async_trait]
impl DataProvider for LocalProvider {
    fn name(&self) -> &str {
        "local"
    }

    fn asset_classes(&self) -> &[AssetClass] {
        &[
            AssetClass::CryptoSpot,
            AssetClass::CryptoPerpetual,
            AssetClass::CryptoFuture,
            AssetClass::Equity,
            AssetClass::Future,
            AssetClass::EquityOption,
            AssetClass::Index,
            AssetClass::Custom,
        ]
    }

    async fn fetch_trades(&self, _symbol: &str, _range: TimeRange) -> Result<RecordBatch> {
        Err(BtError::Unsupported(
            "LocalProvider does not support raw trades; use fetch_bars instead".to_string(),
        ))
    }

    async fn fetch_bars(
        &self,
        symbol: &str,
        _range: TimeRange,
        _interval: BarInterval,
    ) -> Result<Option<RecordBatch>> {
        Self::read_parquet_dir(&self.bars_dir(symbol))
    }

    async fn fetch_funding_rates(
        &self,
        _symbol: &str,
        _range: TimeRange,
    ) -> Result<Option<RecordBatch>> {
        Ok(None)
    }

    async fn list_symbols(&self) -> Result<Vec<ProviderSymbol>> {
        let mut symbols = Vec::new();
        if !self.root.exists() {
            return Ok(symbols);
        }

        let entries = std::fs::read_dir(&self.root)
            .map_err(|e| BtError::Data(format!("reading root dir {}: {e}", self.root.display())))?;

        for entry in entries {
            let entry = entry.map_err(|e| BtError::Data(format!("reading dir entry: {e}")))?;
            let path = entry.path();
            if path.is_dir() {
                if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
                    if path.join("bars").exists() {
                        symbols.push(ProviderSymbol {
                            symbol: name.to_string(),
                            base_asset: name.to_string(),
                            quote_asset: "UNKNOWN".to_string(),
                            status: "active".to_string(),
                            asset_class: AssetClass::Custom,
                        });
                    }
                }
            }
        }

        symbols.sort_by(|a, b| a.symbol.cmp(&b.symbol));
        Ok(symbols)
    }

    fn rate_limits(&self) -> RateLimitInfo {
        RateLimitInfo {
            requests_per_minute: None,
            burst: None,
        }
    }
}
