use crate::{
    block_data::{BlockData, BlockDataUnchecked, BlockType},
    common::{Author, Payload, Round},
    quorum_cert::QuorumCert,
};
use anyhow::{bail, ensure, format_err};
use diem_crypto::{hash::CryptoHash, HashValue};
use diem_infallible::duration_since_epoch;
use diem_types::{
    account_address::AccountAddress,
    block_info::{BlockInfo, PivotBlockDecision},
    block_metadata::BlockMetadata,
    epoch_state::EpochState,
    ledger_info::LedgerInfo,
    transaction::Version,
    validator_config::{ConsensusSignature, ConsensusVRFProof},
    validator_signer::ValidatorSigner,
    validator_verifier::ValidatorVerifier,
};
use mirai_annotations::debug_checked_verify_eq;
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt::{self, Display, Formatter};
#[path = "block_test_utils.rs"]
#[cfg(any(test, feature = "fuzzing"))]
pub mod block_test_utils;
#[cfg(test)]
#[path = "block_test.rs"]
pub mod block_test;
#[derive(Serialize, Clone, PartialEq, Eq)]
pub struct Block {
    #[serde(skip)]
    id: HashValue,
    block_data: BlockData,
    signature: Option<ConsensusSignature>,
    vrf_nonce_and_proof: Option<(u64, ConsensusVRFProof)>,
}
#[derive(Deserialize)]
pub struct BlockUnchecked {
    block_data: BlockDataUnchecked,
    signature: Option<ConsensusSignature>,
    vrf_nonce_and_proof: Option<(u64, ConsensusVRFProof)>,
}
impl From<BlockUnchecked> for Block {
    fn from(b: BlockUnchecked) -> Self {
        let block_data: BlockData = b.block_data.into();
        Self {
            id: block_data.hash(),
            block_data,
            signature: b.signature,
            vrf_nonce_and_proof: b.vrf_nonce_and_proof,
        }
    }
}
impl fmt::Debug for Block {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}", self) }
}
impl Display for Block {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        let nil_marker = if self.is_nil_block() { " (NIL)" } else { "" };
        write!(
            f,
            "[id: {}{}, epoch: {}, round: {:02}, parent_id: {}]",
            self.id,
            nil_marker,
            self.epoch(),
            self.round(),
            self.quorum_cert().certified_block().id(),
        )
    }
}
impl Block {
    pub fn author(&self) -> Option<Author> { self.block_data.author() }
    pub fn epoch(&self) -> u64 { self.block_data.epoch() }
    pub fn id(&self) -> HashValue { self.id }
    #[cfg(test)]
    pub fn is_parent_of(&self, block: &Self) -> bool {
        block.parent_id() == self.id
    }
    pub fn parent_id(&self) -> HashValue {
        self.block_data.quorum_cert().certified_block().id()
    }
    pub fn payload(&self) -> Option<&Payload> { self.block_data.payload() }
    pub fn quorum_cert(&self) -> &QuorumCert { self.block_data.quorum_cert() }
    pub fn round(&self) -> Round { self.block_data.round() }
    pub fn signature(&self) -> Option<&ConsensusSignature> {
        self.signature.as_ref()
    }
    pub fn vrf_proof(&self) -> Option<&ConsensusVRFProof> {
        self.vrf_nonce_and_proof
            .as_ref()
            .map(|(_nonce, proof)| proof)
    }
    pub fn vrf_nonce(&self) -> Option<u64> {
        self.vrf_nonce_and_proof
            .as_ref()
            .map(|(nonce, _proof)| *nonce)
    }
    pub fn set_vrf_nonce_and_proof(
        &mut self, vrf_nonce_and_proof: (u64, ConsensusVRFProof),
    ) {
        debug_assert!(self.vrf_nonce_and_proof.is_none());
        self.vrf_nonce_and_proof = Some(vrf_nonce_and_proof);
    }
    pub fn timestamp_usecs(&self) -> u64 { self.block_data.timestamp_usecs() }
    pub fn gen_block_info(
        &self, executed_state_id: HashValue, version: Version,
        next_epoch_state: Option<EpochState>,
        pivot: Option<PivotBlockDecision>,
    ) -> BlockInfo {
        BlockInfo::new(
            self.epoch(),
            self.round(),
            self.id(),
            executed_state_id,
            version,
            self.timestamp_usecs(),
            next_epoch_state,
            pivot,
        )
    }
    pub fn block_data(&self) -> &BlockData { &self.block_data }
    pub fn is_genesis_block(&self) -> bool {
        self.block_data.is_genesis_block()
    }
    pub fn is_nil_block(&self) -> bool { self.block_data.is_nil_block() }
    #[cfg(any(test, feature = "fuzzing"))]
    pub fn make_genesis_block() -> Self {
        Self::make_genesis_block_from_ledger_info(&LedgerInfo::mock_genesis(
            None,
        ))
    }
    pub fn make_genesis_block_from_ledger_info(
        ledger_info: &LedgerInfo,
    ) -> Self {
        let block_data = BlockData::new_genesis_from_ledger_info(ledger_info);
        Block {
            id: block_data.hash(),
            block_data,
            signature: None,
            vrf_nonce_and_proof: None,
        }
    }
    #[cfg(any(test, feature = "fuzzing"))]
    pub fn new_for_testing(
        id: HashValue, block_data: BlockData,
        signature: Option<ConsensusSignature>,
        vrf_proof: Option<(u64, ConsensusVRFProof)>,
    ) -> Self {
        Block {
            id,
            block_data,
            signature,
            vrf_nonce_and_proof: vrf_proof,
        }
    }
    pub fn new_nil(round: Round, quorum_cert: QuorumCert) -> Self {
        let block_data = BlockData::new_nil(round, quorum_cert);
        Block {
            id: block_data.hash(),
            block_data,
            signature: None,
            vrf_nonce_and_proof: None,
        }
    }
    pub fn new_proposal(
        payload: Payload, round: Round, timestamp_usecs: u64,
        quorum_cert: QuorumCert, validator_signer: &ValidatorSigner,
    ) -> Self {
        let block_data = BlockData::new_proposal(
            payload,
            validator_signer.author(),
            round,
            timestamp_usecs,
            quorum_cert,
        );
        Self::new_proposal_from_block_data(block_data, validator_signer)
    }
    pub fn new_proposal_from_block_data(
        block_data: BlockData, validator_signer: &ValidatorSigner,
    ) -> Self {
        let signature = validator_signer.sign(&block_data);
        Self::new_proposal_from_block_data_and_signature(
            block_data, signature, None,
        )
    }
    pub fn new_proposal_from_block_data_and_signature(
        block_data: BlockData, signature: ConsensusSignature,
        vrf_nonce_and_proof: Option<(u64, ConsensusVRFProof)>,
    ) -> Self {
        Block {
            id: block_data.hash(),
            block_data,
            signature: Some(signature),
            vrf_nonce_and_proof,
        }
    }
    pub fn validate_signature(
        &self, validator: &ValidatorVerifier,
    ) -> anyhow::Result<()> {
        match self.block_data.block_type() {
            BlockType::Genesis => {
                bail!("We should not accept genesis from others")
            }
            BlockType::NilBlock => self.quorum_cert().verify(validator),
            BlockType::Proposal { author, .. } => {
                let signature = self.signature.as_ref().ok_or_else(|| {
                    format_err!("Missing signature in Proposal")
                })?;
                validator.verify(*author, &self.block_data, signature)?;
                self.quorum_cert().verify(validator)
            }
        }
    }
    pub fn verify_well_formed(&self) -> anyhow::Result<()> {
        ensure!(
            !self.is_genesis_block(),
            "We must not accept genesis from others"
        );
        let parent = self.quorum_cert().certified_block();
        ensure!(
            parent.round() < self.round(),
            "Block must have a greater round than parent's block"
        );
        ensure!(
            parent.epoch() == self.epoch(),
            "block's parent should be in the same epoch"
        );
        if parent.has_reconfiguration() {
            ensure!(
                self.payload().map_or(true, |p| p.is_empty()),
                "Reconfiguration suffix should not carry payload"
            );
        }
        if self.is_nil_block() || parent.has_reconfiguration() {
            ensure!(
                self.timestamp_usecs() == parent.timestamp_usecs(),
                "Nil/reconfig suffix block must have same timestamp as parent"
            );
        } else {
            ensure!(
                self.timestamp_usecs() > parent.timestamp_usecs(),
                "Blocks must have strictly increasing timestamps"
            );
            let current_ts = duration_since_epoch();
            const TIMEBOUND: u64 = 300_000_000;
            ensure!(
                self.timestamp_usecs()
                    <= (current_ts.as_micros() as u64)
                        .saturating_add(TIMEBOUND),
                "Blocks must not be too far in the future"
            );
        }
        ensure!(
            !self.quorum_cert().ends_epoch(),
            "Block cannot be proposed in an epoch that has ended"
        );
        debug_checked_verify_eq!(
            self.id(),
            self.block_data.hash(),
            "Block id mismatch the hash"
        );
        Ok(())
    }
}
impl<'de> Deserialize<'de> for Block {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where D: Deserializer<'de> {
        #[derive(Deserialize)]
        #[serde(rename = "Block")]
        struct BlockWithoutId {
            block_data: BlockData,
            signature: Option<ConsensusSignature>,
            vrf_nonce_and_proof: Option<(u64, ConsensusVRFProof)>,
        }
        let BlockWithoutId {
            block_data,
            signature,
            vrf_nonce_and_proof,
        } = BlockWithoutId::deserialize(deserializer)?;
        Ok(Block {
            id: block_data.hash(),
            block_data,
            signature,
            vrf_nonce_and_proof,
        })
    }
}
impl From<&Block> for BlockMetadata {
    fn from(block: &Block) -> Self {
        Self::new(
            block.id(),
            block.round(),
            block.timestamp_usecs(),
            block
                .quorum_cert()
                .ledger_info()
                .signatures()
                .keys()
                .cloned()
                .collect(),
            block.author().unwrap_or(AccountAddress::ZERO),
        )
    }
}