use bitcoin::consensus::{encode, Decodable, Encodable};
use bitcoin::hashes::{hash_newtype, sha256d, Hash};
use bitcoin::merkle_tree;
use bitcoin_io::{BufRead, Error, Write};
use std::ops::Deref;

use crate::transaction::Transaction;
use crate::{consensus_decode_from_vec, consensus_encode_vec};

hash_newtype! {
    /// A bitcoin block hash.
    pub struct BlockHash(sha256d::Hash);
    /// A hash of the Merkle tree branch or root for transactions.
    pub struct TxMerkleNode(sha256d::Hash);
}

impl Default for BlockHash {
    fn default() -> BlockHash {
        BlockHash(sha256d::Hash::all_zeros())
    }
}

impl Deref for BlockHash {
    type Target = [u8; 32];

    fn deref(&self) -> &[u8; 32] {
        self.0.as_byte_array()
    }
}

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

impl Decodable for BlockHash {
    #[inline]
    fn consensus_decode_from_finite_reader<R: BufRead + ?Sized>(
        r: &mut R,
    ) -> Result<Self, encode::Error> {
        let hash: sha256d::Hash = Decodable::consensus_decode_from_finite_reader(r)?;
        Ok(BlockHash(hash))
    }
}

impl Default for TxMerkleNode {
    fn default() -> TxMerkleNode {
        TxMerkleNode(sha256d::Hash::all_zeros())
    }
}

impl Deref for TxMerkleNode {
    type Target = [u8; 32];

    fn deref(&self) -> &[u8; 32] {
        self.0.as_byte_array()
    }
}

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

impl Decodable for TxMerkleNode {
    #[inline]
    fn consensus_decode_from_finite_reader<R: BufRead + ?Sized>(
        r: &mut R,
    ) -> Result<Self, encode::Error> {
        let hash: sha256d::Hash = Decodable::consensus_decode_from_finite_reader(r)?;
        Ok(TxMerkleNode(hash))
    }
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct BlockHeader {
    /// Block version, now repurposed for soft fork signalling.
    pub version: u32,
    /// Reference to the previous block in the chain.
    pub prev_blockhash: BlockHash,
    /// The root hash of the merkle tree of transactions in the block.
    pub merkle_root: TxMerkleNode,
    /// The timestamp of the block, as claimed by the miner.
    pub time: u32,
    /// The target value below which the blockhash must lie.
    pub bits: u32,
    /// The nonce, selected to obtain a low enough blockhash.
    pub nonce: u32,
}

impl BlockHeader {
    /* Modifiers to the version.  */
    pub const VERSION_AUXPOW: u32 = 1 << 8;
    /** Bits above are reserved for the auxpow chain ID.  */
    pub const VERSION_CHAIN_START: u32 = 1 << 16;

    pub fn is_null(&self) -> bool {
        self.bits == 0
    }

    pub fn is_auxpow(&self) -> bool {
        (self.version & Self::VERSION_AUXPOW) != 0
    }

    pub fn chain_id(&self) -> u32 {
        self.version >> 16
    }

    pub fn is_legacy(&self) -> bool {
        self.version == 1 || self.chain_id() == 0
    }

    pub fn block_hash(&self) -> BlockHash {
        let mut enc = BlockHash::engine();
        self.consensus_encode(&mut enc)
            .expect("engines don't error");
        BlockHash::from_engine(enc)
    }
}

impl Encodable for BlockHeader {
    fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, Error> {
        let mut len = 0;
        len += self.version.consensus_encode(w)?;
        len += self.prev_blockhash.consensus_encode(w)?;
        len += self.merkle_root.consensus_encode(w)?;
        len += self.time.consensus_encode(w)?;
        len += self.bits.consensus_encode(w)?;
        len += self.nonce.consensus_encode(w)?;
        Ok(len)
    }
}

impl Decodable for BlockHeader {
    #[inline]
    fn consensus_decode_from_finite_reader<R: BufRead + ?Sized>(
        r: &mut R,
    ) -> Result<Self, encode::Error> {
        Ok(BlockHeader {
            version: Decodable::consensus_decode_from_finite_reader(r)?,
            prev_blockhash: Decodable::consensus_decode_from_finite_reader(r)?,
            merkle_root: Decodable::consensus_decode_from_finite_reader(r)?,
            time: Decodable::consensus_decode_from_finite_reader(r)?,
            bits: Decodable::consensus_decode_from_finite_reader(r)?,
            nonce: Decodable::consensus_decode_from_finite_reader(r)?,
        })
    }
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct MerkleBranch {
    pub hash: Vec<TxMerkleNode>,
    pub side_mask: u32,
}

impl Encodable for MerkleBranch {
    fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, Error> {
        let mut len = 0;
        len += consensus_encode_vec(&self.hash, w)?;
        len += self.side_mask.consensus_encode(w)?;
        Ok(len)
    }
}

impl Decodable for MerkleBranch {
    fn consensus_decode_from_finite_reader<R: BufRead + ?Sized>(
        r: &mut R,
    ) -> Result<Self, encode::Error> {
        Ok(MerkleBranch {
            hash: consensus_decode_from_vec(r)?,
            side_mask: Decodable::consensus_decode_from_finite_reader(r)?,
        })
    }
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct MerkleTx {
    pub coinbase_tx: Transaction,
    pub parent_hash: TxMerkleNode,
    pub coinbase_branch: MerkleBranch,
    pub blockchain_branch: MerkleBranch,
    pub parent_block: BlockHeader,
}

impl Encodable for MerkleTx {
    fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, Error> {
        let mut len = 0;
        len += self.coinbase_tx.consensus_encode(w)?;
        len += self.parent_hash.consensus_encode(w)?;
        len += self.coinbase_branch.consensus_encode(w)?;
        len += self.blockchain_branch.consensus_encode(w)?;
        len += self.parent_block.consensus_encode(w)?;
        Ok(len)
    }
}

impl Decodable for MerkleTx {
    #[inline]
    fn consensus_decode_from_finite_reader<R: BufRead + ?Sized>(
        r: &mut R,
    ) -> Result<Self, encode::Error> {
        Ok(MerkleTx {
            coinbase_tx: Decodable::consensus_decode_from_finite_reader(r)?,
            parent_hash: Decodable::consensus_decode_from_finite_reader(r)?,
            coinbase_branch: Decodable::consensus_decode_from_finite_reader(r)?,
            blockchain_branch: Decodable::consensus_decode_from_finite_reader(r)?,
            parent_block: Decodable::consensus_decode_from_finite_reader(r)?,
        })
    }
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Block {
    /// The block header
    pub header: BlockHeader,
    /// auxpow (if this is a merge-minded block)
    pub auxpow: Option<MerkleTx>,
    /// List of transactions contained in the block
    pub txdata: Vec<Transaction>,
}

impl Block {
    pub fn block_hash(&self) -> BlockHash {
        self.header.block_hash()
    }

    /// Checks if merkle root of header matches merkle root of the transaction list.
    pub fn check_merkle_root(&self) -> bool {
        match self.compute_merkle_root() {
            Some(merkle_root) => self.header.merkle_root == merkle_root,
            None => false,
        }
    }

    pub fn compute_merkle_root(&self) -> Option<TxMerkleNode> {
        let hashes = self
            .txdata
            .iter()
            .map(|obj| obj.compute_txid().to_raw_hash());
        merkle_tree::calculate_root(hashes).map(|h| h.into())
    }
}

impl Encodable for Block {
    fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, Error> {
        let mut len = 0;
        len += self.header.consensus_encode(w)?;
        if let Some(ref auxpow) = self.auxpow {
            len += auxpow.consensus_encode(w)?;
        }
        len += consensus_encode_vec(&self.txdata, w)?;
        Ok(len)
    }
}

impl Decodable for Block {
    fn consensus_decode_from_finite_reader<R: BufRead + ?Sized>(
        r: &mut R,
    ) -> Result<Self, encode::Error> {
        let mut blk = Block {
            header: Decodable::consensus_decode_from_finite_reader(r)?,
            auxpow: None,
            txdata: Vec::new(),
        };
        if blk.header.is_auxpow() {
            blk.auxpow = Some(Decodable::consensus_decode_from_finite_reader(r)?);
        }
        blk.txdata = consensus_decode_from_vec(r)?;
        Ok(blk)
    }
}

impl From<BlockHeader> for BlockHash {
    fn from(header: BlockHeader) -> BlockHash {
        header.block_hash()
    }
}

impl From<&BlockHeader> for BlockHash {
    fn from(header: &BlockHeader) -> BlockHash {
        header.block_hash()
    }
}

impl From<Block> for BlockHash {
    fn from(block: Block) -> BlockHash {
        block.block_hash()
    }
}

impl From<&Block> for BlockHash {
    fn from(block: &Block) -> BlockHash {
        block.block_hash()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use hex::test_hex_unwrap as hex;
    use hex::DisplayHex;
    use std::str::FromStr;

    #[test]
    fn test_blockhash() {
        let hash = "4339bc72f0820b2a28c5dabda3de47605cdcaad845516de82d685d51233e6c4d";
        let blockhash = BlockHash::from_str(hash).unwrap();
        let mut data: [u8; 32] = *blockhash.as_ref();
        assert_eq!(blockhash.to_string(), hash);
        data.reverse();
        assert_eq!(data.to_lower_hex_string(), hash);
    }

    #[test]
    fn test_first_block() {
        // block height 0
        let data = hex!("010000000000000000000000000000000000000000000000000000000000000000000000696ad20e2dd4365c7459b4a4a5af743d5e92c6da3229e6532cd605f6533f2a5b24a6a152f0ff0f1e678601000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1004ffff001d0104084e696e746f6e646fffffffff010058850c020000004341040184710fa689ad5023690c80f3a49c8f13f8d45b8c857fbcbc8bc4a8e4d3eb4b10f4d4604fa08dce601aaf0f470216fe1b51850b4acf21b179c45070ac7b03a9ac00000000");
        let mut rd = &data[..];
        let blk = Block::consensus_decode_from_finite_reader(&mut rd).unwrap();
        println!("Block: {:?}", blk);
        assert_eq!(blk.header.prev_blockhash, BlockHash::default());
    }

    #[test]
    fn test_block() {
        // https://dogechain.info/block/fb5f5b5b7d70e660c2c67bca8d3328afae32ae8bb4c8d6cbc42d96ff876b0859
        let data = hex!(" ");
        let mut rd = &data[..];
        let blk = Block::consensus_decode_from_finite_reader(&mut rd).unwrap();
        println!("Block: {:?}\ntxdata: {:?}", blk.header, blk.txdata.len());

        // println!("Block root: {:?}", blk.auxpow);

        let mut buf = Vec::new();
        blk.consensus_encode(&mut buf).unwrap();
        assert_eq!(buf, data);

        // println!("Tx 0: {:?}", blk.txdata[0]);
        // println!("Tx m: {:?}", blk.clone().auxpow.unwrap());

        assert_eq!(
            blk.block_hash().to_string(),
            "fb5f5b5b7d70e660c2c67bca8d3328afae32ae8bb4c8d6cbc42d96ff876b0859"
        );
        assert_eq!(blk.header.version, 6422788);
        assert_eq!(
            blk.header.prev_blockhash.to_string(),
            "bc896aaf584ecbb178281ee2fd2b09fed410cdb9d8bf50853c132ca69f80a10d"
        );
        assert_eq!(
            blk.header.merkle_root.to_string(),
            "ab3b8b98d5135a2388a11a4828867f880dfa24b0bb38041f6e062886bd872683"
        );
        assert_eq!(blk.header.time, 1692251695); // 2023-08-17 05:54:55 UTC
        assert_eq!(blk.header.bits, 0x1a013c1b);
        assert_eq!(blk.header.nonce, 0);
        assert_eq!(blk.txdata.len(), 10);
        assert!(blk.header.is_auxpow());
        assert!(blk.check_merkle_root());

        let coins: Vec<u64> = blk
            .txdata
            .iter()
            .flat_map(|tx| tx.output.iter().map(|out| out.value))
            .collect();

        assert_eq!(
            coins,
            vec![
                1001062713226,
                9999907990000,
                27488096948744,
                9999991000000,
                206330249879929,
                31070000000,
                4818071366605,
                100757668,
                235200003058,
                2037075800,
                18461069000,
                22300000000,
                174339870968,
                89798911754,
                8107180185,
                114852164857,
                1000000000,
                1045842885
            ]
        );

        for (j, tx) in blk.txdata.iter().enumerate() {
            for (i, inp) in tx.input.iter().enumerate() {
                let script = inp.script.clone();
                println!("Input {}-{}: {} {:?}", j, i, inp.prevout.vout, script);
            }
            println!("Tx: {}", tx.to_bytes().as_hex());
            // for (i, out) in tx.output.iter().enumerate() {
            //     let script = ScriptBuf::from(out.script.clone());
            //     println!("Output {}-{}: {} {:?}", j, i, out.value, script);
            // }
        }
    }
}
