use brk_cohort::{AgeRange, CohortContext, TERM_NAMES};
use brk_error::Result;
use brk_types::{Cents, Height, StoredF64, Version};
use vecdb::{CachedBoxedVec, Database, ReadableCloneableVec, UnaryTransform};

use super::{AggregateVecs, CohortVecs, HorizonVecs, Horizons, Split, Vecs, mobility};
use crate::{
    indexes,
    internal::{FiatPerBlock, LazyPerBlock, PerBlock, PriceWithRatioPerBlock, SpotValuePerBlock},
};

const VERSION: Version = Version::TWO;

struct ExposureToMobility;

impl UnaryTransform<StoredF64, StoredF64> for ExposureToMobility {
    #[inline(always)]
    fn apply(exposure: StoredF64) -> StoredF64 {
        StoredF64::from(mobility(*exposure))
    }
}

fn import_split<T>(mut import: impl FnMut(&str) -> Result<T>) -> Result<Split<T>> {
    Ok(Split {
        mobile: import("mobile")?,
        immobile: import("immobile")?,
    })
}

impl CohortVecs {
    fn forced_import(
        db: &vecdb::Database,
        name: &str,
        version: Version,
        indexes: &indexes::Vecs,
        spot_price: &CachedBoxedVec<Height, Cents>,
    ) -> Result<Self> {
        let spending_rate =
            PerBlock::forced_import(db, &format!("{name}_spending_rate"), version, indexes)?;
        let spending_exposure =
            PerBlock::forced_import(db, &format!("{name}_spending_exposure"), version, indexes)?;
        let mobility = LazyPerBlock::from_computed::<ExposureToMobility>(
            &format!("{name}_mobility"),
            version,
            spending_exposure.height.read_only_boxed_clone(),
            &spending_exposure,
        );

        Ok(Self {
            spending_rate,
            spending_exposure,
            mobility,
            supply: import_split(|side| {
                SpotValuePerBlock::forced_import(
                    db,
                    &format!("{name}_{side}_supply"),
                    version,
                    indexes,
                    spot_price,
                )
            })?,
        })
    }
}

impl AggregateVecs {
    fn forced_import(
        db: &Database,
        name: &str,
        version: Version,
        indexes: &indexes::Vecs,
        spot_price: &CachedBoxedVec<Height, Cents>,
    ) -> Result<Self> {
        let prefix = if name.is_empty() {
            String::new()
        } else {
            format!("{name}_")
        };

        Ok(Self {
            supply: import_split(|side| {
                SpotValuePerBlock::forced_import(
                    db,
                    &format!("{prefix}{side}_supply"),
                    version,
                    indexes,
                    spot_price,
                )
            })?,
            supply_in_loss_share: PerBlock::forced_import(
                db,
                &format!("{prefix}coinflow_supply_in_loss_share"),
                version,
                indexes,
            )?,
            horizon: Horizons::try_from_fn(|horizon, _| -> Result<_> {
                Ok(HorizonVecs {
                    supply_in_loss_share: PerBlock::forced_import(
                        db,
                        &format!("{prefix}coinflow_{horizon}_supply_in_loss_share"),
                        version,
                        indexes,
                    )?,
                })
            })?,
            cap: FiatPerBlock::forced_import(
                db,
                &format!("{prefix}coinflow_cap"),
                version,
                indexes,
            )?,
            price: PriceWithRatioPerBlock::forced_import(
                db,
                &format!("{prefix}coinflow_price"),
                version,
                indexes,
                spot_price,
            )?,
        })
    }
}

impl Vecs {
    pub(crate) fn forced_import(
        db: &Database,
        parent_version: Version,
        indexes: &indexes::Vecs,
        prices: &crate::price::Vecs,
    ) -> Result<Self> {
        let version = parent_version + VERSION;
        let aggregate_version = version + Version::ONE;
        let spot_price = prices.spot.cents.height.read_only_cached_boxed_clone();

        let this = Self {
            age_range: AgeRange::try_new(|_, name| {
                let name = CohortContext::Utxo.prefixed(name);
                CohortVecs::forced_import(db, &name, version, indexes, &spot_price)
            })?,
            all: AggregateVecs::forced_import(db, "", version, indexes, &spot_price)?,
            sth: AggregateVecs::forced_import(
                db,
                TERM_NAMES.short.id,
                aggregate_version,
                indexes,
                &spot_price,
            )?,
            lth: AggregateVecs::forced_import(
                db,
                TERM_NAMES.long.id,
                aggregate_version,
                indexes,
                &spot_price,
            )?,
        };

        Ok(this)
    }
}
