use crate::encode::decode_vector;
use crate::encode::encode_vector;
use crate::nexa::hash_types::BlockHash;
use crate::nexa::transaction::Transaction;
use crate::utilnumber::BitcoindVarInt;

use bitcoin_hashes::Hash;
use bitcoincash::consensus::Decodable;
use bitcoincash::consensus::Encodable;
use bitcoincash::hash_types::TxMerkleNode;
use bitcoincash::util::uint::Uint256;

use sha2::Digest;
use sha2::Sha256;

use crate::impl_nexa_consensus_encoding;
use std::mem::size_of_val;

#[derive(PartialEq, Eq, Clone, Debug)]
pub struct BlockHeader {
    /// Reference to the previous block in the chain.
    pub prev_blockhash: BlockHash,
    /// The target value below which the blockhash must lie, encoded as a
    /// a float (with well-defined rounding, of course).
    pub bits: u32,
    /// Hash of a specific ancestor block (see nexa documentation for exact ancestor)
    pub ancestor: BlockHash,
    /// The root hash of the merkle tree of transactions in the block.
    pub merkle_root: TxMerkleNode,
    /// commitment to a probabilistic transaction/address filter that allows light clients to discover if they may need the block. (e.g. neutrino)
    pub tx_filter: Uint256,
    /// The timestamp of the block, as claimed by the miner.
    pub time: u32,
    /// Height of this block
    pub height: BitcoindVarInt,
    /// Cumulative work in the chain
    pub chain_work: Uint256,
    /// Block size in bytes -- mutable because it is calculated from the other fields
    pub size: u64,
    /// Number of transactions in the block
    pub tx_count: BitcoindVarInt,
    /// Quantity of satoshis in fee pool AFTER transaction evaluation (algorithmically determined, but part of hash commitment).
    pub fee_pool_amt: BitcoindVarInt,
    /// Commitment to a data structure containing all unspent coins
    /// MUST be len 0 for now. MUST be < 128 bytes
    pub utxo_commitment: Vec<u8>,
    /// Miner-specific data -- this is not a free-for all field.  It must follow documented conventions
    /// MUST be len 0 for now
    pub miner_data: Vec<u8>,
    /// mining nonce
    /// nonce length must be < 16 bytes.  This means the header hash + nonce fit in 1 sha256 round (with spare room)
    pub nonce: Vec<u8>,
}
impl_nexa_consensus_encoding!(
    BlockHeader,
    prev_blockhash,
    bits,
    ancestor,
    merkle_root,
    tx_filter,
    time,
    height,
    chain_work,
    size,
    tx_count,
    fee_pool_amt,
    utxo_commitment,
    miner_data,
    nonce
);

/**
 * Hash data buffer to sha256
 */
fn sha256hash(data: &[u8]) -> Vec<u8> {
    let mut hasher = Sha256::new();
    hasher.update(data);
    let h = hasher.finalize();
    h.to_vec()
}

impl BlockHeader {
    pub fn block_hash_commitment(&self) -> Vec<u8> {
        [self.mini_hash(), self.extended_hash()].concat()
    }

    pub fn block_hash(&self) -> BlockHash {
        let hash = sha256hash(&self.block_hash_commitment());
        BlockHash::from_slice(&hash).unwrap()
    }

    pub fn mini_header(&self) -> Vec<u8> {
        let e = "in mem buffers don't error";
        let mut buf = std::io::BufWriter::new(Vec::with_capacity(
            size_of_val(&self.prev_blockhash) + size_of_val(&self.bits),
        ));
        self.prev_blockhash.consensus_encode(&mut buf).expect(e);
        self.bits.consensus_encode(&mut buf).expect(e);
        buf.into_inner().expect(e)
    }

    pub fn mini_hash(&self) -> Vec<u8> {
        sha256hash(&self.mini_header())
    }

    pub fn extended_header(&self) -> Vec<u8> {
        let e = "in mem buffers don't error";
        let mut buf = std::io::BufWriter::new(Vec::new());
        self.ancestor.consensus_encode(&mut buf).expect(e);
        self.tx_filter.consensus_encode(&mut buf).expect(e);
        self.merkle_root.consensus_encode(&mut buf).expect(e);
        self.time.consensus_encode(&mut buf).expect(e);
        self.height.0.consensus_encode(&mut buf).expect(e);
        self.chain_work.consensus_encode(&mut buf).expect(e);
        self.size.consensus_encode(&mut buf).expect(e);
        self.tx_count.0.consensus_encode(&mut buf).expect(e);
        self.fee_pool_amt.0.consensus_encode(&mut buf).expect(e);
        self.utxo_commitment.consensus_encode(&mut buf).expect(e);
        self.miner_data.consensus_encode(&mut buf).expect(e);
        self.nonce.consensus_encode(&mut buf).expect(e);
        buf.into_inner().expect(e)
    }

    pub fn extended_hash(&self) -> Vec<u8> {
        sha256hash(&self.extended_header())
    }

    /**
     * Height of this block in the blockchain.
     */
    pub fn height(&self) -> u64 {
        self.height.0
    }
}

/// A Bitcoin block, which is a collection of transactions with an attached
/// proof of work.
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Block {
    /// The block header
    pub header: BlockHeader,
    /// List of transactions contained in the block
    pub txdata: Vec<Transaction>,
}

impl Block {
    /// Returns the block hash.
    pub fn block_hash(&self) -> BlockHash {
        self.header.block_hash()
    }
}

impl Encodable for Block {
    fn consensus_encode<W: std::io::Write + ?Sized>(
        &self,
        w: &mut W,
    ) -> Result<usize, std::io::Error> {
        let mut len = 0;
        len += self.header.consensus_encode(w)?;

        // Can't implement Encodable trait for Vec<TxIn> or TxOut because of Rust orphan rules.
        // (We don't own Encodeable and we don't own Vec).
        len += encode_vector(w, &self.txdata)?;

        Ok(len)
    }
}

impl Decodable for Block {
    fn consensus_decode<R: std::io::Read + ?Sized>(
        r: &mut R,
    ) -> Result<Self, bitcoincash::consensus::encode::Error> {
        let header: BlockHeader = Decodable::consensus_decode(r)?;
        let txdata: Vec<Transaction> = decode_vector(r)?;

        Ok(Block { header, txdata })
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use bitcoincash::consensus::encode::deserialize;
    use bitcoincash::consensus::encode::deserialize_partial;
    use bitcoincash::consensus::serialize;
    use bitcoincash::hashes::hex::ToHex;

    #[test]
    fn parse_nexa_header() {
        let header_serialized = hex::decode("0000000000000000000000000000000000000000000000000000000000000000ffff7f20000000000000000000000000000000000000000000000000000000000000000049317c27d6893f423308ccd46e1f51ec0310892a49b8e6645e176e7235ba04c8000000000000000000000000000000000000000000000000000000000000000027ffee60000200000000000000000000000000000000000000000000000000000000000000e700000000000000010000000105").unwrap();
        let header: BlockHeader = deserialize(&header_serialized).unwrap();
        assert_eq!(header.prev_blockhash, BlockHash::all_zeros());
        assert_eq!(header.bits, 545259519);
        assert_eq!(header.ancestor, BlockHash::all_zeros());
        assert_eq!(
            header.merkle_root.to_hex(),
            "c804ba35726e175e64e6b8492a891003ec511f6ed4cc0833423f89d6277c3149"
        );
        assert_eq!(header.tx_filter, Uint256::default());
        assert_eq!(header.time, 1626275623);
        assert_eq!(header.height.0, 0);
        assert_eq!(header.chain_work, Uint256::from_u64(0x02).unwrap());
        assert_eq!(header.size, 231);
        assert_eq!(header.tx_count.0, 1);
        assert_eq!(header.fee_pool_amt.0, 0);
        let empty: Vec<u8> = vec![];
        assert_eq!(header.utxo_commitment, empty);
        assert_eq!(header.miner_data, empty);
        assert_eq!(header.nonce, vec![5]);
    }

    /**
     * Test with block height 128. This test exposed a bug in varint encoding of the header height property.
     */
    #[test]
    fn parse_nexa_header_2() {
        let hex = "94bcb94f3f6dfbe905072eacc76763828e177e02f2bf00d11a50a8191ce40c11ffff7f2071b3d0c9122622e3045c15b60e2c65d5f171e0216b1af3fe2dd107e331e41ed726659cf11d2d4f2f8683038b5961020482733cddc0deacb9d36839f92252d02b0000000000000000000000000000000000000000000000000000000000000000c1d9906280000201000000000000000000000000000000000000000000000000000000000000f800000000000000010000000406000000";
        let header_serialized = hex::decode(hex).unwrap();
        let header: (BlockHeader, usize) = deserialize_partial(&header_serialized).unwrap();
        assert_eq!(header.1, header_serialized.len());
        assert_eq!(header.0.height.0, 128);
    }

    #[test]
    fn parse_nexa_header_3() {
        let hex = "0000000000000000000000000000000000000000000000000000000000000000ffff7f20000000000000000000000000000000000000000000000000000000000000000049317c27d6893f423308ccd46e1f51ec0310892a49b8e6645e176e7235ba04c8000000000000000000000000000000000000000000000000000000000000000027ffee60000200000000000000000000000000000000000000000000000000000000000000e700000000000000010000000105";
        let header_serialized = hex::decode(hex).unwrap();
        let header: (BlockHeader, usize) = deserialize_partial(&header_serialized).unwrap();
        println!("{:?}", header.0);
        assert_eq!(header.1, header_serialized.len());
        assert_eq!(header.0.height.0, 0);
    }

    #[test]
    fn parse_nexa_serialize() {
        let header_hex = "94bcb94f3f6dfbe905072eacc76763828e177e02f2bf00d11a50a8191ce40c11ffff7f2071b3d0c9122622e3045c15b60e2c65d5f171e0216b1af3fe2dd107e331e41ed726659cf11d2d4f2f8683038b5961020482733cddc0deacb9d36839f92252d02b0000000000000000000000000000000000000000000000000000000000000000c1d9906280000201000000000000000000000000000000000000000000000000000000000000f800000000000000010000000406000000";

        let serialized = hex::decode(&header_hex).unwrap();
        let header: BlockHeader = deserialize(&serialized).unwrap();
        let serialized_again = serialize(&header);

        assert_eq!(header_hex, hex::encode(&serialized_again))
    }

    #[test]
    fn block_hash() {
        let header_hex = "0000000000000000000000000000000000000000000000000000000000000000ffff7f20000000000000000000000000000000000000000000000000000000000000000049317c27d6893f423308ccd46e1f51ec0310892a49b8e6645e176e7235ba04c8000000000000000000000000000000000000000000000000000000000000000027ffee60000200000000000000000000000000000000000000000000000000000000000000e700000000000000010000000105";
        let serialized = hex::decode(&header_hex).unwrap();
        let header: BlockHeader = deserialize(&serialized).unwrap();

        assert_eq!(
            "0000000000000000000000000000000000000000000000000000000000000000ffff7f20",
            header.mini_header().to_hex()
        );

        assert_eq!(
            "1d45cd14d4f3134b608f80a0ac1f5d011438b59639b2fdf78a8d3f257de7994a",
            header.mini_hash().to_hex()
        );

        assert_eq!(
            "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000049317c27d6893f423308ccd46e1f51ec0310892a49b8e6645e176e7235ba04c827ffee6000000000000000000200000000000000000000000000000000000000000000000000000000000000e7000000000000000100000000000000000000000000000000000105",
            header.extended_header().to_hex(),
        );

        assert_eq!(
            "eb88bb4791c173b0ba458404d05c34131364850aeae48cef6ea0e902dcade3f0",
            header.extended_hash().to_hex()
        );

        assert_eq!(
            "1d45cd14d4f3134b608f80a0ac1f5d011438b59639b2fdf78a8d3f257de7994aeb88bb4791c173b0ba458404d05c34131364850aeae48cef6ea0e902dcade3f0",
            header.block_hash_commitment().to_hex()
        );

        assert_eq!(
            "d71ee431e307d12dfef31a6b21e071f1d5652c0eb6155c04e3222612c9d0b371",
            header.block_hash().to_hex()
        )
    }

    #[test]
    fn parse_block() {
        let block_hex = "0000000000000000000000000000000000000000000000000000000000000000ffff7f20000000000000000000000000000000000000000000000000000000000000000049317c27d6893f423308ccd46e1f51ec0310892a49b8e6645e176e7235ba04c8000000000000000000000000000000000000000000000000000000000000000027ffee60000200000000000000000000000000000000000000000000000000000000000000e700000000000000010000000105010000020000000000000000000151000000000000000000156a00023b1c0f54686973206973207265677465737400000000";
        let serialized = hex::decode(&block_hex).unwrap();
        let block: Block = deserialize(&serialized).unwrap();
        assert_eq!(
            "d71ee431e307d12dfef31a6b21e071f1d5652c0eb6155c04e3222612c9d0b371",
            block.block_hash().to_hex()
        );
        assert_eq!(block.txdata.len(), 1);
        assert!(block.txdata[0].is_coin_base());
    }
}
