use bytes::Bytes;
use chrono::NaiveDate;
use object_store::aws::AmazonS3Builder;
use object_store::path::Path;
use object_store::{ClientOptions, Error as OsError, ObjectStore};

pub struct BinanceS3Client {
    store: Box<dyn ObjectStore>,
}

/// Return the S3 base path prefix for the given source type.
fn s3_base(source_type: &str) -> &'static str {
    match source_type {
        "futures_um" => "data/futures/um",
        "futures_cm" => "data/futures/cm",
        _ => "data/spot",
    }
}

impl BinanceS3Client {
    pub fn new() -> Result<Self, OsError> {
        let client_options = ClientOptions::new().with_timeout_disabled();
        let store = AmazonS3Builder::new()
            .with_bucket_name("data.binance.vision")
            .with_region("ap-northeast-1")
            .with_skip_signature(true)
            .with_client_options(client_options)
            .build()?;
        Ok(Self {
            store: Box::new(store),
        })
    }

    /// Fetch a daily trade file. Returns `None` if the file does not exist yet.
    pub async fn fetch_daily(
        &self,
        symbol: &str,
        date: NaiveDate,
        source_type: &str,
    ) -> Result<Option<Bytes>, OsError> {
        let base = s3_base(source_type);
        let path = Path::from(format!(
            "{base}/daily/trades/{symbol}/{symbol}-trades-{}.zip",
            date.format("%Y-%m-%d")
        ));
        self.get(&path).await
    }

    /// Fetch a monthly trade file. Returns `None` if the file does not exist yet.
    pub async fn fetch_monthly(
        &self,
        symbol: &str,
        year: i32,
        month: u32,
        source_type: &str,
    ) -> Result<Option<Bytes>, OsError> {
        let base = s3_base(source_type);
        let path = Path::from(format!(
            "{base}/monthly/trades/{symbol}/{symbol}-trades-{year}-{month:02}.zip",
        ));
        self.get(&path).await
    }

    /// Fetch a daily kline file for the given symbol, interval, and date.
    /// Returns `None` if the file does not exist yet.
    pub async fn fetch_daily_klines(
        &self,
        symbol: &str,
        interval: &str,
        date: NaiveDate,
        source_type: &str,
    ) -> Result<Option<Bytes>, OsError> {
        let base = s3_base(source_type);
        let path = Path::from(format!(
            "{base}/daily/klines/{symbol}/{interval}/{symbol}-{interval}-{}.zip",
            date.format("%Y-%m-%d")
        ));
        self.get(&path).await
    }

    /// Fetch a monthly kline file for the given symbol, interval, year, and month.
    /// Returns `None` if the file does not exist yet.
    pub async fn fetch_monthly_klines(
        &self,
        symbol: &str,
        interval: &str,
        year: i32,
        month: u32,
        source_type: &str,
    ) -> Result<Option<Bytes>, OsError> {
        let base = s3_base(source_type);
        let path = Path::from(format!(
            "{base}/monthly/klines/{symbol}/{interval}/{symbol}-{interval}-{year}-{month:02}.zip",
        ));
        self.get(&path).await
    }

    async fn get(&self, path: &Path) -> Result<Option<Bytes>, OsError> {
        match self.store.get(path).await {
            Ok(result) => Ok(Some(result.bytes().await?)),
            Err(OsError::NotFound { .. }) => Ok(None),
            Err(e) => Err(e),
        }
    }
}
