use std::sync::Arc;

use bt_data::{ArrowIpcDataStore, DataStore, MegaDataStore, ParquetDataStore};
use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;

use crate::metadata_from_store;

#[pyclass(name = "DataStore")]
#[derive(Clone)]
pub struct PyDataStore {
    data_root: String,
    metadata_db: String,
    dataset: String,
    store: Arc<dyn DataStore>,
}

#[pymethods]
impl PyDataStore {
    #[new]
    #[pyo3(signature = (data_root, metadata_db="metadata/metadata.sqlite".to_string(), dataset="bars_1m".to_string(), mega=None, arrow_dir=None))]
    pub fn new(
        data_root: String,
        metadata_db: String,
        dataset: String,
        mega: Option<String>,
        arrow_dir: Option<String>,
    ) -> PyResult<Self> {
        let (store, dataset): (Arc<dyn DataStore>, String) = if let Some(dir) = arrow_dir {
            let s = ArrowIpcDataStore::new(&dir, &metadata_db)
                .map_err(|err| {
                    PyRuntimeError::new_err(format!("failed to open Arrow IPC store: {err}"))
                })?;
            (Arc::new(s), "arrow_ipc".to_string())
        } else if let Some(mega_path) = mega {
            let s = MegaDataStore::new(&mega_path, &metadata_db)
                .map_err(|err| {
                    PyRuntimeError::new_err(format!("failed to open mega data store: {err}"))
                })?;
            (Arc::new(s), dataset)
        } else {
            let s = ParquetDataStore::with_dataset(&data_root, &metadata_db, &dataset)
                .map_err(|err| {
                    PyRuntimeError::new_err(format!("failed to open data store: {err}"))
                })?;
            (Arc::new(s), dataset)
        };

        Ok(Self {
            data_root,
            metadata_db,
            dataset,
            store,
        })
    }

    pub fn data_root(&self) -> String {
        self.data_root.clone()
    }

    pub fn metadata_db(&self) -> String {
        self.metadata_db.clone()
    }

    pub fn dataset(&self) -> String {
        self.dataset.clone()
    }

    pub fn active_version(&self, dataset: &str) -> PyResult<String> {
        let version = self
            .store
            .active_version(dataset)
            .map_err(super::map_bt_error)?;
        Ok(version.id)
    }

    pub fn list_versions(&self, dataset: &str) -> PyResult<Vec<String>> {
        let versions = self
            .store
            .list_versions(dataset)
            .map_err(super::map_bt_error)?;
        Ok(versions.into_iter().map(|version| version.id).collect())
    }

    /// Resolve a ticker name (e.g. "BTCUSDT") to its integer SymbolId.
    pub fn resolve_symbol(&self, ticker: &str) -> PyResult<u32> {
        let infos = metadata_from_store(&self.metadata_db)?;
        for info in &infos {
            if info.ticker.eq_ignore_ascii_case(ticker) {
                return Ok(info.id.0);
            }
        }
        Err(pyo3::exceptions::PyValueError::new_err(format!(
            "symbol '{ticker}' not found in metadata store"
        )))
    }

    /// List all symbols as (id, ticker) tuples.
    pub fn list_symbols(&self) -> PyResult<Vec<(u32, String)>> {
        let infos = metadata_from_store(&self.metadata_db)?;
        Ok(infos
            .into_iter()
            .map(|info| (info.id.0, info.ticker))
            .collect())
    }
}

impl PyDataStore {
    pub(crate) fn store(&self) -> &Arc<dyn DataStore> {
        &self.store
    }
}
