use crate::{BlockHeader, SignedTransaction, TransactionWithSignature};
use byteorder::{ByteOrder, LittleEndian};
use cfx_types::{Space, H256, U256};
use keccak_hash::keccak;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use rand::Rng;
use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream};
use siphasher::sip::SipHasher24;
use std::{
    fmt::{Debug, Formatter},
    hash::Hasher,
    sync::Arc,
};
pub type BlockNumber = u64;
pub type BlockHeight = u64;
#[derive(Debug, Clone, PartialEq)]
pub struct Block {
    pub block_header: BlockHeader,
    pub transactions: Vec<Arc<SignedTransaction>>,
    pub approximated_rlp_size: usize,
    pub approximated_rlp_size_with_public: usize,
}
impl MallocSizeOf for Block {
    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
        self.block_header.size_of(ops) + self.transactions.size_of(ops)
    }
}
impl Block {
    pub fn new(
        block_header: BlockHeader, transactions: Vec<Arc<SignedTransaction>>,
    ) -> Self {
        Self::new_with_rlp_size(block_header, transactions, None, None)
    }
    pub fn new_with_rlp_size(
        block_header: BlockHeader, transactions: Vec<Arc<SignedTransaction>>,
        rlp_size: Option<usize>, rlp_size_with_public: Option<usize>,
    ) -> Self {
        let approximated_rlp_size = match rlp_size {
            Some(size) => size,
            None => transactions
                .iter()
                .fold(block_header.approximated_rlp_size(), |accum, tx| {
                    accum + tx.rlp_size()
                }),
        };
        let approximated_rlp_size_with_public = match rlp_size_with_public {
            Some(size) => size,
            None => approximated_rlp_size + transactions.len() * 84, };
        Block {
            block_header,
            transactions,
            approximated_rlp_size,
            approximated_rlp_size_with_public,
        }
    }
    pub fn hash(&self) -> H256 { self.block_header.hash() }
    pub fn approximated_rlp_size(&self) -> usize { self.approximated_rlp_size }
    pub fn approximated_rlp_size_with_public(&self) -> usize {
        self.approximated_rlp_size_with_public
    }
    pub fn total_gas(&self) -> U256 {
        let mut sum = U256::from(0);
        for t in &self.transactions {
            sum += *t.gas();
        }
        sum
    }
    pub fn size(&self) -> usize {
        self.transactions
            .iter()
            .fold(0, |accum, tx| accum + tx.rlp_size())
    }
    pub fn transaction_hashes(&self, space_filter: Option<Space>) -> Vec<H256> {
        if let Some(space) = space_filter {
            self.transactions
                .iter()
                .filter(|tx| tx.space() == space)
                .map(|tx| tx.hash())
                .collect()
        } else {
            self.transactions.iter().map(|tx| tx.hash()).collect()
        }
    }
    pub fn to_compact(&self) -> CompactBlock {
        let nonce: u64 = rand::rng().random();
        let (k0, k1) =
            CompactBlock::get_shortid_key(&self.block_header, &nonce);
        CompactBlock {
            block_header: self.block_header.clone(),
            nonce,
            tx_short_ids: CompactBlock::create_shortids(
                &self.transactions,
                k0,
                k1,
            ),
            reconstructed_txns: Vec::new(),
        }
    }
    pub fn encode_body_with_tx_public(&self) -> Vec<u8> {
        let mut stream = RlpStream::new();
        stream.begin_list(self.transactions.len());
        for tx in &self.transactions {
            stream.append(tx.as_ref());
        }
        stream.drain()
    }
    pub fn decode_body_with_tx_public(
        rlp: &Rlp,
    ) -> Result<Vec<Arc<SignedTransaction>>, DecoderError> {
        if rlp.as_raw().len() != rlp.payload_info()?.total() {
            return Err(DecoderError::RlpIsTooBig);
        }
        let signed_transactions = rlp.as_list()?;
        let mut transactions = Vec::with_capacity(signed_transactions.len());
        for tx in signed_transactions {
            transactions.push(Arc::new(tx));
        }
        Ok(transactions)
    }
    pub fn encode_with_tx_public(&self) -> Vec<u8> {
        let mut stream = RlpStream::new();
        stream
            .begin_list(2)
            .append(&self.block_header)
            .append_raw(&*self.encode_body_with_tx_public(), 1);
        stream.drain()
    }
    pub fn decode_with_tx_public(rlp: &Rlp) -> Result<Self, DecoderError> {
        if rlp.as_raw().len() != rlp.payload_info()?.total() {
            return Err(DecoderError::RlpIsTooBig);
        }
        if rlp.item_count()? != 2 {
            return Err(DecoderError::RlpIncorrectListLen);
        }
        Ok(Block::new_with_rlp_size(
            rlp.val_at(0)?,
            Self::decode_body_with_tx_public(&rlp.at(1)?)?,
            None,
            Some(rlp.as_raw().len()),
        ))
    }
}
impl Encodable for Block {
    fn rlp_append(&self, stream: &mut RlpStream) {
        stream.begin_list(2).append(&self.block_header);
        stream.begin_list(self.transactions.len());
        for tx in &self.transactions {
            stream.append(&tx.transaction);
        }
    }
}
impl Decodable for Block {
    fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
        if rlp.as_raw().len() != rlp.payload_info()?.total() {
            return Err(DecoderError::RlpIsTooBig);
        }
        if rlp.item_count()? != 2 {
            return Err(DecoderError::RlpIncorrectListLen);
        }
        let transactions = rlp.list_at::<TransactionWithSignature>(1)?;
        let mut signed_transactions = Vec::with_capacity(transactions.len());
        for tx in transactions {
            let signed = SignedTransaction::new_unsigned(tx);
            signed_transactions.push(Arc::new(signed));
        }
        Ok(Block::new_with_rlp_size(
            rlp.val_at(0)?,
            signed_transactions,
            Some(rlp.as_raw().len()),
            None,
        ))
    }
}
#[derive(Clone, PartialEq)]
pub struct CompactBlock {
    pub block_header: BlockHeader,
    pub nonce: u64,
    pub tx_short_ids: Vec<u8>,
    pub reconstructed_txns: Vec<Option<Arc<SignedTransaction>>>,
}
impl Debug for CompactBlock {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        write!(
            f,
            "CompactBlock{{ block_header: {:?}, nonce: {:?}}}",
            self.block_header, self.nonce
        )
    }
}
impl MallocSizeOf for CompactBlock {
    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
        self.tx_short_ids.size_of(ops) + self.reconstructed_txns.size_of(ops)
    }
}
impl CompactBlock {
    const SHORT_ID_SIZE_IN_BYTES: usize = 6;
    pub fn len(&self) -> usize {
        self.tx_short_ids.len() / CompactBlock::SHORT_ID_SIZE_IN_BYTES
    }
    pub fn hash(&self) -> H256 { self.block_header.hash() }
    pub fn get_shortid_key(header: &BlockHeader, nonce: &u64) -> (u64, u64) {
        let mut stream = RlpStream::new();
        stream.begin_list(2).append(header).append(nonce);
        let to_hash = stream.out();
        let key_hash: [u8; 32] = keccak(to_hash).into();
        let k0 = LittleEndian::read_u64(&key_hash[0..8]);
        let k1 = LittleEndian::read_u64(&key_hash[8..16]);
        (k0, k1)
    }
    pub fn create_shortids(
        transactions: &Vec<Arc<SignedTransaction>>, k0: u64, k1: u64,
    ) -> Vec<u8> {
        let mut short_ids: Vec<u8> = vec![];
        for tx in transactions {
            let hash = tx.hash();
            let random = CompactBlock::get_random_bytes(&hash, k0, k1);
            short_ids.push(((random & 0xff00) >> 8) as u8);
            short_ids.push((random & 0xff) as u8);
            short_ids.push(hash[28]);
            short_ids.push(hash[29]);
            short_ids.push(hash[30]);
            short_ids.push(hash[31]);
        }
        short_ids
    }
    pub fn to_u16(v1: u8, v2: u8) -> u16 { ((v1 as u16) << 8) + v2 as u16 }
    pub fn to_u32(v1: u8, v2: u8, v3: u8, v4: u8) -> u32 {
        ((v1 as u32) << 24)
            + ((v2 as u32) << 16)
            + ((v3 as u32) << 8)
            + v4 as u32
    }
    pub fn get_random_bytes(
        transaction_id: &H256, key1: u64, key2: u64,
    ) -> u16 {
        let mut hasher = SipHasher24::new_with_keys(key1, key2);
        hasher.write(transaction_id.as_ref());
        (hasher.finish() & 0xffff) as u16
    }
    pub fn get_decomposed_short_ids(&self) -> (Vec<u16>, Vec<u32>) {
        let mut random_bytes_vector: Vec<u16> = Vec::new();
        let mut fixed_bytes_vector: Vec<u32> = Vec::new();
        for i in (0..self.tx_short_ids.len())
            .step_by(CompactBlock::SHORT_ID_SIZE_IN_BYTES)
        {
            random_bytes_vector.push(CompactBlock::to_u16(
                self.tx_short_ids[i],
                self.tx_short_ids[i + 1],
            ));
            fixed_bytes_vector.push(CompactBlock::to_u32(
                self.tx_short_ids[i + 2],
                self.tx_short_ids[i + 3],
                self.tx_short_ids[i + 4],
                self.tx_short_ids[i + 5],
            ));
        }
        (random_bytes_vector, fixed_bytes_vector)
    }
}
impl Encodable for CompactBlock {
    fn rlp_append(&self, steam: &mut RlpStream) {
        steam
            .begin_list(3)
            .append(&self.block_header)
            .append(&self.nonce)
            .append(&self.tx_short_ids);
    }
}
impl Decodable for CompactBlock {
    fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
        let short_ids: Vec<u8> = rlp.val_at(2)?;
        if short_ids.len() % CompactBlock::SHORT_ID_SIZE_IN_BYTES != 0 {
            return Err(DecoderError::Custom("Compact Block length Error!"));
        }
        Ok(CompactBlock {
            block_header: rlp.val_at(0)?,
            nonce: rlp.val_at(1)?,
            tx_short_ids: short_ids,
            reconstructed_txns: Vec::new(),
        })
    }
}