use super::new_blake2b;
use crate::MMR;
use crate::leaf_index_to_pos;
use crate::merge::Merge;
use crate::mmr::InclusionProof;
use crate::mmr_store::MMRStoreReadOps as _;
use crate::util::MemStore;
use bytes::{Bytes, BytesMut};
use std::convert::Infallible;
use std::fmt;

#[derive(Clone)]
struct Header {
  number: u64,
  parent_hash: Bytes,
  difficulty: u64,
  chain_root: Bytes,
}

impl Header {
  fn default() -> Self {
    Header {
      number: 0,
      parent_hash: vec![0; 32].into(),
      difficulty: 0,
      chain_root: vec![0; 32].into(),
    }
  }

  fn hash(&self) -> Bytes {
    let mut hasher = new_blake2b();
    let mut hash = [0u8; 32];
    hasher.update(&self.number.to_le_bytes());
    hasher.update(&self.parent_hash);
    hasher.update(&self.difficulty.to_le_bytes());
    hasher.update(&self.chain_root);
    hasher.finalize(&mut hash);
    hash.to_vec().into()
  }
}

#[derive(Eq, PartialEq, Clone, Default)]
struct HashWithTD {
  hash: Bytes,
  td: u64,
}

impl HashWithTD {
  fn serialize(&self) -> Bytes {
    let mut data = BytesMut::from(self.hash.as_ref());
    data.extend(&self.td.to_le_bytes());
    data.into()
  }

  fn deserialize(mut data: Bytes) -> Self {
    assert_eq!(data.len(), 40);
    let mut td_bytes = [0u8; 8];
    td_bytes.copy_from_slice(&data[32..]);
    let td = u64::from_le_bytes(td_bytes);
    data.truncate(32);
    HashWithTD { hash: data, td }
  }
}

impl fmt::Debug for HashWithTD {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(
      f,
      "HashWithTD {{ hash: {}, td: {} }}",
      faster_hex::hex_string(&self.hash),
      self.td
    )
  }
}

struct MergeHashWithTD;

impl Merge for MergeHashWithTD {
  type Item = HashWithTD;
  type Error = Infallible;

  fn leaf_hash(data: &[u8]) -> Result<Self::Item, Self::Error> {
    let mut hasher = new_blake2b();
    let mut hash = [0u8; 32];
    hasher.update(&[0x00]);
    hasher.update(data);
    hasher.finalize(&mut hash);

    let td = if data.len() >= 40 {
      let mut td_bytes = [0u8; 8];
      td_bytes.copy_from_slice(&data[32..40]);
      u64::from_le_bytes(td_bytes)
    } else {
      0
    };

    Ok(HashWithTD {
      hash: hash.to_vec().into(),
      td,
    })
  }

  fn merge_pos(
    _pos: u64,
    left: &Self::Item,
    right: &Self::Item,
  ) -> Result<Self::Item, Self::Error> {
    let mut hasher = new_blake2b();
    let mut hash = [0u8; 32];
    hasher.update(&left.serialize());
    hasher.update(&right.serialize());
    hasher.finalize(&mut hash);
    let td = left.td + right.td;
    Ok(HashWithTD {
      hash: hash.to_vec().into(),
      td,
    })
  }
}

struct Prover {
  headers: Vec<(Header, u64)>,
  positions: Vec<u64>,
  store: MemStore<HashWithTD>,
}

impl Prover {
  fn new() -> Prover {
    let store = MemStore::default();
    Prover {
      headers: Vec::new(),
      positions: Vec::new(),
      store,
    }
  }

  async fn gen_blocks(&mut self, count: u64) {
    let mut mmr = MMR::<MergeHashWithTD, _>::new(
      self.positions.len() as u64,
      self.store.clone(),
    );
    let mut previous = if let Some(pos) = self.positions.last() {
      mmr.store().get_elem(*pos).await.unwrap().unwrap()
    } else {
      let genesis = Header::default();
      let previous = HashWithTD {
        hash: genesis.hash(),
        td: genesis.difficulty,
      };
      self.headers.push((genesis, previous.td));
      let pos = mmr.push(&previous.serialize()).await.unwrap();
      self.positions.push(pos);
      previous
    };
    let last_number = self.headers.last().unwrap().0.number;
    for i in (last_number + 1)..=(last_number + count) {
      let block = Header {
        number: i,
        parent_hash: previous.hash.clone(),
        difficulty: i,
        chain_root: mmr.get_root().await.unwrap().serialize(),
      };
      previous = HashWithTD {
        hash: block.hash(),
        td: block.difficulty,
      };
      let pos = mmr.push(&previous.serialize()).await.unwrap();
      self.positions.push(pos);
      self.headers.push((block, previous.td));
    }
    mmr.commit().await.unwrap();
  }

  fn get_header(&self, number: u64) -> (Header, u64) {
    self.headers[number as usize].clone()
  }

  async fn gen_proof(
    &self,
    number: u64,
    later_number: u64,
  ) -> InclusionProof<MergeHashWithTD> {
    assert!(number < later_number);
    let pos = self.positions[number as usize];
    let later_pos = self.positions[later_number as usize];
    let mut mmr = MMR::<MergeHashWithTD, MemStore<HashWithTD>>::new(
      later_pos,
      self.store.clone(),
    );
    assert_eq!(
      mmr.get_root().await.unwrap().serialize(),
      self.headers[later_number as usize].0.chain_root
    );
    mmr.gen_proof(vec![pos]).await.unwrap()
  }

  fn get_pos(&self, number: u64) -> u64 {
    self.positions[number as usize]
  }
}

#[tokio::test]
async fn test_insert_header() {
  let mut prover = Prover::new();
  prover.gen_blocks(30).await;
  let h1 = 11;
  let h2 = 19;

  let prove_elem = {
    let (header, td) = prover.get_header(h1);
    MergeHashWithTD::leaf_hash(
      &HashWithTD {
        hash: header.hash(),
        td,
      }
      .serialize(),
    )
    .unwrap()
  };
  let root = {
    let (later_header, _later_td) = prover.get_header(h2);
    HashWithTD::deserialize(later_header.chain_root)
  };
  let proof = prover.gen_proof(h1, h2).await;
  let pos = leaf_index_to_pos(h1);
  assert_eq!(pos, prover.get_pos(h1));
  assert_eq!(
    prove_elem,
    prover.store.get_elem(pos).await.unwrap().unwrap()
  );
  let result = proof.verify(&root, vec![(pos, prove_elem)]).unwrap();
  assert!(result);
}
