pub(crate) mod by_unit;
mod compute;
pub(crate) mod ohlcs;

use std::path::Path;

use brk_traversable::Traversable;
use brk_types::Version;
use brk_types::{Cents, Dollars, Height};
use vecdb::{CachedVec, Database, LazyVecFrom1, ReadableCloneableVec, Rw, StorageMode};

use crate::{
    indexes,
    internal::{
        CentsUnsignedToDollars, CentsUnsignedToSats, EagerIndexes, LazyEagerIndexes, LazyPerBlock,
        OhlcCentsToDollars, OhlcCentsToSats, PerBlock, Resolutions,
        db_utils::{finalize_db, open_db},
    },
};

use by_unit::{OhlcByUnit, PriceByUnit, SplitByUnit, SplitCloseByUnit, SplitIndexesByUnit};
use ohlcs::{LazyOhlcVecs, OhlcVecs};

pub const DB_NAME: &str = "prices";

#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
    #[traversable(skip)]
    pub db: Database,

    #[traversable(skip)]
    pub cached_spot_cents: CachedVec<Height, Cents>,
    #[traversable(skip)]
    pub cached_spot_usd: LazyVecFrom1<Height, Dollars, Height, Cents>,

    pub split: SplitByUnit<M>,
    pub ohlc: OhlcByUnit<M>,
    pub spot: PriceByUnit<M>,
}

impl Vecs {
    pub(crate) fn forced_import(
        parent: &Path,
        version: Version,
        indexes: &indexes::Vecs,
    ) -> brk_error::Result<Self> {
        let db = open_db(parent, DB_NAME, 100_000)?;
        let this = Self::forced_import_inner(&db, version, indexes)?;
        finalize_db(&this.db, &this)?;
        Ok(this)
    }

    fn forced_import_inner(
        db: &Database,
        version: Version,
        indexes: &indexes::Vecs,
    ) -> brk_error::Result<Self> {
        let version = version + Version::new(11);

        let price_cents = PerBlock::forced_import(db, "price_cents", version, indexes)?;

        let open_cents = EagerIndexes::forced_import(db, "price_open_cents", version)?;
        let high_cents = EagerIndexes::forced_import(db, "price_high_cents", version)?;
        let low_cents = EagerIndexes::forced_import(db, "price_low_cents", version)?;

        let close_cents = Resolutions::forced_import(
            "price_close_cents",
            price_cents.height.read_only_boxed_clone(),
            version,
            indexes,
        );

        let ohlc_cents = OhlcVecs::forced_import(db, "price_ohlc_cents", version)?;

        let price_usd = LazyPerBlock::from_computed::<CentsUnsignedToDollars>(
            "price",
            version,
            price_cents.height.read_only_boxed_clone(),
            &price_cents,
        );

        let open_usd = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
            "price_open",
            version,
            &open_cents,
        );
        let high_usd = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
            "price_high",
            version,
            &high_cents,
        );
        let low_usd = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
            "price_low",
            version,
            &low_cents,
        );

        let close_usd = Resolutions::forced_import(
            "price_close",
            price_usd.height.read_only_boxed_clone(),
            version,
            indexes,
        );

        let ohlc_usd = LazyOhlcVecs::from_eager_ohlc_indexes::<OhlcCentsToDollars>(
            "price_ohlc",
            version,
            &ohlc_cents,
        );

        let price_sats = LazyPerBlock::from_computed::<CentsUnsignedToSats>(
            "price_sats",
            version,
            price_cents.height.read_only_boxed_clone(),
            &price_cents,
        );

        let open_sats = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
            "price_open_sats",
            version,
            &open_cents,
        );
        // Sats are inversely related to cents (sats = 10B/cents), so high↔low are swapped
        let high_sats = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
            "price_high_sats",
            version,
            &low_cents,
        );
        let low_sats = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
            "price_low_sats",
            version,
            &high_cents,
        );

        let close_sats = Resolutions::forced_import(
            "price_close_sats",
            price_sats.height.read_only_boxed_clone(),
            version,
            indexes,
        );

        // OhlcCentsToSats handles the high↔low swap internally
        let ohlc_sats = LazyOhlcVecs::from_eager_ohlc_indexes::<OhlcCentsToSats>(
            "price_ohlc_sats",
            version,
            &ohlc_cents,
        );

        let split = SplitByUnit {
            open: SplitIndexesByUnit {
                usd: open_usd,
                cents: open_cents,
                sats: open_sats,
            },
            high: SplitIndexesByUnit {
                usd: high_usd,
                cents: high_cents,
                sats: high_sats,
            },
            low: SplitIndexesByUnit {
                usd: low_usd,
                cents: low_cents,
                sats: low_sats,
            },
            close: SplitCloseByUnit {
                usd: close_usd,
                cents: close_cents,
                sats: close_sats,
            },
        };

        let ohlc = OhlcByUnit {
            usd: ohlc_usd,
            cents: ohlc_cents,
            sats: ohlc_sats,
        };

        let cached_spot_cents = CachedVec::new(&price_cents.height);
        let cached_spot_usd = LazyVecFrom1::transformed::<CentsUnsignedToDollars>(
            "price",
            version,
            cached_spot_cents.read_only_boxed_clone(),
        );

        let spot = PriceByUnit {
            usd: price_usd,
            cents: price_cents,
            sats: price_sats,
        };

        Ok(Self {
            db: db.clone(),
            cached_spot_cents,
            cached_spot_usd,
            split,
            ohlc,
            spot,
        })
    }
}
