use candid::CandidType;
use ex3_serde::bincode;
use ex3_timestamp::TimeInNs;
use ic_stable_structures::{storable::Bound, Storable};
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
use std::ops::Range;

use crate::range::CandidRange;
use crate::{BlockHash, BlockHeight, CandidBlockHeight, CandidPackageId, MerkleRoot, PackageId};

/// Block head
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct BlockHeader {
    /// Block version
    pub version: u8,

    /// Height of the block
    pub height: BlockHeight,

    /// The last accepted package id
    pub p_id: PackageId,

    /// Previous block hash
    pub pre_block_hash: BlockHash,

    /// Merkle root of the transactions in the block
    pub tx_root: MerkleRoot,

    /// Merkle root of the state changed in the block
    pub state_changed_root: MerkleRoot,

    /// Merkle root of the data integrity of sharding vaults
    pub data_integrity_root: MerkleRoot,

    /// Rejected transaction merkle root of the block rejected transactions
    pub rejected_tx_root: MerkleRoot,
}

impl Ord for BlockHeader {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.height.cmp(&other.height)
    }
}

impl PartialOrd for BlockHeader {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

#[derive(CandidType, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct CandidBlockHeader {
    pub version: u8,
    pub height: CandidBlockHeight,
    pub p_id: CandidPackageId,
    #[serde(with = "serde_bytes")]
    pub pre_block_hash: BlockHash,
    #[serde(with = "serde_bytes")]
    pub tx_root: MerkleRoot,
    #[serde(with = "serde_bytes")]
    pub state_changed_root: MerkleRoot,
    #[serde(with = "serde_bytes")]
    pub data_integrity_root: MerkleRoot,
    #[serde(with = "serde_bytes")]
    pub rejected_tx_root: MerkleRoot,
}

impl Storable for BlockHeader {
    fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
        bincode::serialize(self).unwrap().into()
    }

    fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
        bincode::deserialize(bytes.as_ref()).unwrap()
    }

    const BOUND: Bound = Bound::Bounded {
        max_size: 225,
        is_fixed_size: false,
    };
}

impl From<CandidBlockHeader> for BlockHeader {
    fn from(candid_block_header: CandidBlockHeader) -> Self {
        BlockHeader {
            version: candid_block_header.version,
            height: candid_block_header.height.into(),
            p_id: candid_block_header.p_id.into(),
            pre_block_hash: candid_block_header.pre_block_hash,
            tx_root: candid_block_header.tx_root,
            state_changed_root: candid_block_header.state_changed_root,
            data_integrity_root: candid_block_header.data_integrity_root,
            rejected_tx_root: candid_block_header.rejected_tx_root,
        }
    }
}

impl From<BlockHeader> for CandidBlockHeader {
    fn from(block_header: BlockHeader) -> Self {
        CandidBlockHeader {
            version: block_header.version,
            height: block_header.height.into(),
            p_id: block_header.p_id.into(),
            pre_block_hash: block_header.pre_block_hash,
            tx_root: block_header.tx_root,
            state_changed_root: block_header.state_changed_root,
            data_integrity_root: block_header.data_integrity_root,
            rejected_tx_root: block_header.rejected_tx_root,
        }
    }
}

impl BlockHeader {
    pub fn hash(&self) -> BlockHash {
        let block_header_bytes = bincode::serialize(&self).unwrap();
        ex3_crypto::sha256(block_header_bytes.as_slice())
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EncodedBlockHeader(pub ByteBuf);

impl From<BlockHeader> for EncodedBlockHeader {
    fn from(head: BlockHeader) -> Self {
        let bytes = bincode::serialize(&head).unwrap();
        EncodedBlockHeader(ByteBuf::from(bytes))
    }
}

impl From<EncodedBlockHeader> for BlockHeader {
    fn from(encoded_head: EncodedBlockHeader) -> Self {
        bincode::deserialize(encoded_head.0.as_slice()).expect("Decode block head should success")
    }
}

impl std::ops::Deref for EncodedBlockHeader {
    type Target = ByteBuf;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct BlockHeaderExt {
    pub height: BlockHeight,
    pub validate_on_chain: bool,
    pub sharding_reported: bool,
    pub has_local_body: bool,
}

/// Block head
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct ConsensusBlockHeader {
    /// Head
    pub head: BlockHeader,

    /// Timestamp of reached consensus
    pub timestamp: TimeInNs,
}

impl Storable for ConsensusBlockHeader {
    fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
        let mut bytes = self.head.to_bytes().into_owned();
        bytes.extend_from_slice(self.timestamp.0.to_le_bytes().as_slice());
        bytes.into()
    }

    fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
        let mut bytes: Vec<u8> = bytes.into();
        let timestamp_bytes = bytes
            .drain(bytes.len() - 8..)
            .collect::<Vec<u8>>()
            .try_into()
            .expect("ConsensusBlockHeader timestamp should have 8 bytes");

        ConsensusBlockHeader {
            head: BlockHeader::from_bytes(bytes.into()),
            timestamp: TimeInNs(u64::from_le_bytes(timestamp_bytes)),
        }
    }

    const BOUND: Bound = Bound::Bounded {
        max_size: 233,
        is_fixed_size: false,
    };
}

#[derive(CandidType, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct CandidConsensusBlockHeader {
    pub head: CandidBlockHeader,
    pub timestamp: TimeInNs,
}

impl From<CandidConsensusBlockHeader> for ConsensusBlockHeader {
    fn from(candid_consensus_block_header: CandidConsensusBlockHeader) -> Self {
        ConsensusBlockHeader {
            head: candid_consensus_block_header.head.into(),
            timestamp: candid_consensus_block_header.timestamp,
        }
    }
}

impl From<ConsensusBlockHeader> for CandidConsensusBlockHeader {
    fn from(consensus_block_header: ConsensusBlockHeader) -> Self {
        CandidConsensusBlockHeader {
            head: consensus_block_header.head.into(),
            timestamp: consensus_block_header.timestamp,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_storable_for_block_header() {
        let block_header = BlockHeader {
            version: 0,
            height: BlockHeight::from(u128::MAX),
            p_id: PackageId::from(u128::MAX),
            pre_block_hash: [1; 32],
            tx_root: [2; 32],
            state_changed_root: [3; 32],
            data_integrity_root: [4; 32],
            rejected_tx_root: [5; 32],
        };

        let bytes = block_header.to_bytes();
        assert!(
            bytes.len() <= 225,
            "block head bytes length should be less than 225, but got {}",
            bytes.len()
        );
        let block_header2 = BlockHeader::from_bytes(bytes);
        assert_eq!(block_header, block_header2);
    }

    #[test]
    fn test_bincode_for_bytes() {
        let bytes = vec![1; 255];
        let bytes2 = ByteBuf::from(bytes.clone());
        let s1 = bincode::serialize(&bytes).unwrap();
        let s2 = bincode::serialize(&bytes2).unwrap();
        assert_eq!(
            s1.len(),
            s2.len(),
            "bincode serialize for bytes and ByteBuf should have same length, but got {} and {}",
            s1.len(),
            s2.len()
        );
    }
}
