use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, Version};
use vecdb::{
    BinaryTransform, CachedBoxedVec, Database, Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode,
    VecIndex, VecValue,
};

use crate::{
    indexes,
    internal::{
        CachedValuePerBlock, CachedWindowStartVec, LazyRollingAvgsAmountFromHeight,
        LazyRollingSumsAmountFromHeight, LazyValueBlock, RollingDistributionValuePerBlock,
        SatsToCents, WindowStarts, Windows,
    },
    price,
};

#[derive(Traversable)]
pub struct CachedValuePerBlockFull<M: StorageMode = Rw> {
    pub block: LazyValueBlock,
    pub cumulative: CachedValuePerBlock<M>,
    pub sum: LazyRollingSumsAmountFromHeight,
    pub average: LazyRollingAvgsAmountFromHeight,
    #[traversable(flatten)]
    pub distribution: RollingDistributionValuePerBlock<M>,
}

const VERSION: Version = Version::TWO;

impl CachedValuePerBlockFull {
    pub(crate) fn cached_cumulative_sats(&self) -> CachedBoxedVec<Height, Sats> {
        self.cumulative.sats.height.read_only_cached_boxed_clone()
    }

    pub(crate) fn forced_import(
        db: &Database,
        name: &str,
        version: Version,
        indexes: &indexes::Vecs,
        cached_starts: &Windows<&CachedWindowStartVec>,
    ) -> Result<Self> {
        let full_version = version + VERSION;
        let rolling_version = full_version + Version::TWO;
        let cumulative_version = rolling_version + Version::ONE;
        let cumulative = CachedValuePerBlock::forced_import(
            db,
            &format!("{name}_cumulative"),
            cumulative_version,
            indexes,
        )?;
        let cumulative_sats = cumulative.sats.height.read_only_clone();
        let block = LazyValueBlock::from_cumulative_sources(
            name,
            cumulative_version,
            &cumulative_sats,
            &cumulative.cents.height,
        );
        let sum = LazyRollingSumsAmountFromHeight::new(
            &format!("{name}_sum"),
            rolling_version,
            &cumulative_sats,
            &cumulative.cents.height,
            cached_starts,
            indexes,
        );
        let average = LazyRollingAvgsAmountFromHeight::new(
            &format!("{name}_average"),
            rolling_version,
            &cumulative_sats,
            &cumulative.cents.height,
            cached_starts,
            indexes,
        );
        let distribution =
            RollingDistributionValuePerBlock::forced_import(db, name, full_version, indexes)?;

        Ok(Self {
            block,
            cumulative,
            sum,
            average,
            distribution,
        })
    }

    #[allow(clippy::too_many_arguments)]
    pub(crate) fn compute_from_indexes<A, B>(
        &mut self,
        max_from: Height,
        windows: &WindowStarts<'_>,
        prices: &price::Vecs,
        first_indexes: &impl ReadableVec<Height, A>,
        indexes_count: &impl ReadableVec<Height, B>,
        source: &impl ReadableVec<A, Sats>,
        exit: &Exit,
    ) -> Result<()>
    where
        A: VecIndex + VecValue,
        B: VecValue,
        usize: From<B>,
    {
        self.cumulative.compute_sats_from_indexes(
            max_from,
            first_indexes,
            indexes_count,
            source,
            exit,
        )?;

        self.cumulative
            .cents
            .height
            .compute_cumulative_transformed_binary(
                max_from,
                &self.block.sats,
                &prices.spot.cents.height,
                SatsToCents::apply,
                exit,
            )?;

        self.compute_distribution(max_from, windows, exit)
    }

    fn compute_distribution(
        &mut self,
        max_from: Height,
        windows: &WindowStarts<'_>,
        exit: &Exit,
    ) -> Result<()> {
        self.distribution
            .compute(max_from, windows, &self.block.sats, &self.block.cents, exit)?;

        Ok(())
    }
}
