use crate::block0;
use chain_impl_mockchain::{
    block::Block,
    header::{BlockDate, Header},
    leadership::{self, Leadership, Verification},
    ledger::{EpochRewardsInfo, Ledger},
};
use chain_time::{
    era::{EpochPosition, EpochSlotOffset},
    Epoch, Slot, TimeFrame,
};
use std::time::SystemTime;
use thiserror::Error;
pub struct EpochInfo {
    time_frame: TimeFrame,
    epoch_leadership_schedule: Leadership,
    epoch_rewards_info: Option<EpochRewardsInfo>,
}
#[derive(Debug, Error)]
pub enum EpochInfoError {
    #[error("Cannot needed information from the block0")]
    InvalidBlock0(
        #[source]
        #[from]
        block0::Block0Error,
    ),
    #[error("Block Header's verification failed")]
    HeaderVerification {
        #[source]
        #[from]
        source: leadership::Error,
    },
}
impl EpochInfo {
    pub(crate) fn new(block0: &Block, ledger: &Ledger) -> Result<Self, EpochInfoError> {
        let epoch = block0.header().block_date().epoch;
        let time_frame = {
            let start_time = block0::start_time(block0)?;
            let slot_duration = block0::slot_duration(block0)?;
            TimeFrame::new(
                chain_time::Timeline::new(start_time),
                chain_time::SlotDuration::from_secs(slot_duration.as_secs() as u32),
            )
        };
        let epoch_leadership_schedule = Leadership::new(epoch, ledger);
        let epoch_rewards_info = None;
        Ok(Self {
            time_frame,
            epoch_leadership_schedule,
            epoch_rewards_info,
        })
    }
    pub(crate) fn chain(
        &self,
        leadership: Leadership,
        epoch_rewards_info: Option<EpochRewardsInfo>,
    ) -> Self {
        Self {
            time_frame: self.time_frame.clone(),
            epoch_leadership_schedule: leadership,
            epoch_rewards_info,
        }
    }
    pub fn check_header(&self, header: &Header) -> Result<(), EpochInfoError> {
        match self.epoch_leadership_schedule.verify(header) {
            Verification::Failure(error) => {
                Err(EpochInfoError::HeaderVerification { source: error })
            }
            Verification::Success => Ok(()),
        }
    }
    pub fn epoch(&self) -> u32 {
        self.epoch_leadership_schedule.epoch()
    }
    pub fn slot_of(&self, date: BlockDate) -> Slot {
        let epoch = Epoch(date.epoch);
        let slot = EpochSlotOffset(date.slot_id);
        let pos = EpochPosition { epoch, slot };
        self.epoch_leadership_schedule.era().from_era_to_slot(pos)
    }
    pub fn time_of(&self, date: BlockDate) -> Option<SystemTime> {
        let slot = self.slot_of(date);
        self.time_frame.slot_to_systemtime(slot)
    }
    pub fn epoch_leadership_schedule(&self) -> &Leadership {
        &self.epoch_leadership_schedule
    }
    pub fn epoch_rewards_info(&self) -> Option<&EpochRewardsInfo> {
        self.epoch_rewards_info.as_ref()
    }
}