use std::convert::Infallible;

use super::{MergeNumberHash, NumberHash};
use crate::Error;
use crate::helper::pos_height_in_tree;
use crate::leaf_index_to_mmr_size;
use crate::merge::Merge;
use crate::mmr::InclusionProof;
use crate::util::{MemMMR, MemStore};
use faster_hex::hex_string;
use proptest::prelude::*;
use rand::{Rng as _, seq::SliceRandom as _, thread_rng};

async fn test_mmr(count: u32, proof_elem: Vec<u32>) {
  let store = MemStore::default();
  let mut mmr = MemMMR::<MergeNumberHash>::new(0, store);
  let mut positions: Vec<u64> = Vec::new();
  for i in 0u32..count {
    let pos = mmr.push(&i.to_le_bytes()).await.unwrap();
    positions.push(pos);
  }
  let root = mmr.get_root().await.expect("get root");
  let proof = mmr
    .gen_proof(
      proof_elem
        .iter()
        .map(|elem| positions[*elem as usize])
        .collect(),
    )
    .await
    .expect("gen proof");
  mmr.commit().await.expect("commit changes");
  let result = proof
    .verify(
      &root,
      proof_elem
        .iter()
        .map(|elem| {
          (
            positions[*elem as usize],
            MergeNumberHash::leaf_hash(&elem.to_le_bytes()).unwrap(),
          )
        })
        .collect(),
    )
    .unwrap();
  assert!(result);
}

async fn test_gen_new_root_from_proof(count: u32) {
  let store = MemStore::default();
  let mut mmr = MemMMR::<MergeNumberHash>::new(0, store);
  let mut positions: Vec<u64> = Vec::new();
  for i in 0u32..count {
    let pos = mmr.push(&i.to_le_bytes()).await.unwrap();
    positions.push(pos);
  }
  let elem = count - 1;
  let pos = positions[elem as usize];
  let proof = mmr.gen_proof(vec![pos]).await.expect("gen proof");
  let new_elem = count;
  let new_pos = mmr.push(&new_elem.to_le_bytes()).await.unwrap();
  let root = mmr.get_root().await.expect("get root");
  mmr.commit().await.expect("commit changes");
  let calculated_root = proof
    .calculate_root_with_new_leaf(
      vec![(pos, NumberHash::from(elem))],
      new_pos,
      NumberHash::from(new_elem),
      leaf_index_to_mmr_size(new_elem.into()),
    )
    .unwrap();
  assert_eq!(calculated_root, root);
}

#[tokio::test]
async fn test_mmr_root() {
  let store = MemStore::default();
  let mut mmr = MemMMR::<MergeNumberHash>::new(0, store);
  for i in 0u32..11 {
    mmr.push(&i.to_le_bytes()).await.unwrap();
  }
  let root = mmr.get_root().await.expect("get root");
  let hex_root = hex_string(&root.0);
  assert_eq!(
    "f6794677f37a57df6a5ec36ce61036e43a36c1a009d05c81c9aa685dde1fd6e3",
    hex_root
  );
}

#[tokio::test]
async fn test_empty_mmr_root() {
  let store = MemStore::<NumberHash>::default();
  let mmr = MemMMR::<MergeNumberHash>::new(0, store);
  assert_eq!(Err(Error::GetRootOnEmpty), mmr.get_root().await);
}

#[tokio::test]
async fn test_mmr_3_peaks() {
  test_mmr(11, vec![5]).await;
}

#[tokio::test]
async fn test_mmr_2_peaks() {
  test_mmr(10, vec![5]).await;
}

#[tokio::test]
async fn test_mmr_1_peak() {
  test_mmr(8, vec![5]).await;
}

#[tokio::test]
async fn test_mmr_first_elem_proof() {
  test_mmr(11, vec![0]).await;
}

#[tokio::test]
async fn test_mmr_last_elem_proof() {
  test_mmr(11, vec![10]).await;
}

#[tokio::test]
async fn test_mmr_1_elem() {
  test_mmr(1, vec![0]).await;
}

#[tokio::test]
async fn test_mmr_2_elems() {
  test_mmr(2, vec![0]).await;
  test_mmr(2, vec![1]).await;
}

#[tokio::test]
async fn test_mmr_2_leaves_merkle_proof() {
  test_mmr(11, vec![3, 7]).await;
  test_mmr(11, vec![3, 4]).await;
}

#[tokio::test]
async fn test_mmr_2_sibling_leaves_merkle_proof() {
  test_mmr(11, vec![4, 5]).await;
  test_mmr(11, vec![5, 6]).await;
  test_mmr(11, vec![6, 7]).await;
}

#[tokio::test]
async fn test_mmr_3_leaves_merkle_proof() {
  test_mmr(11, vec![4, 5, 6]).await;
  test_mmr(11, vec![3, 5, 7]).await;
  test_mmr(11, vec![3, 4, 5]).await;
  test_mmr(100, vec![3, 5, 13]).await;
}

#[tokio::test]
async fn test_gen_root_from_proof() {
  test_gen_new_root_from_proof(11).await;
}

#[tokio::test]
async fn test_gen_proof_with_duplicate_leaves() {
  test_mmr(10, vec![5, 5]).await;
}

async fn test_invalid_proof_verification(
  leaf_count: u32,
  positions_to_verify: Vec<u64>,
  tampered_positions: Vec<usize>,
  handrolled_proof_positions: Option<Vec<u64>>,
) {
  #[derive(Clone, PartialEq)]
  enum MyItem {
    Number(u32),
    Merged(Box<MyItem>, Box<MyItem>),
  }

  impl std::fmt::Debug for MyItem {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
      match self {
        MyItem::Number(x) => f.write_fmt(format_args!("{x}")),
        MyItem::Merged(a, b) => {
          f.write_fmt(format_args!("Merged({a:#?}, {b:#?})"))
        }
      }
    }
  }

  #[derive(Debug)]
  struct MyMerge;

  impl Merge for MyMerge {
    type Item = MyItem;
    type Error = Infallible;

    fn leaf_hash(data: &[u8]) -> Result<Self::Item, Self::Error> {
      let num = data
        .get(..4)
        .and_then(|b| b.try_into().ok())
        .map_or(0, u32::from_le_bytes);
      Ok(MyItem::Number(num))
    }

    fn merge_pos(
      _pos: u64,
      left: &Self::Item,
      right: &Self::Item,
    ) -> Result<Self::Item, Self::Error> {
      Ok(MyItem::Merged(
        Box::new(left.clone()),
        Box::new(right.clone()),
      ))
    }
  }

  let store = MemStore::default();
  let mut mmr = MemMMR::<MyMerge>::new(0, store);
  for i in 0u32..leaf_count {
    mmr.push(&i.to_le_bytes()).await.unwrap();
  }
  let root = mmr.get_root().await.unwrap();

  let mut entries_to_verify: Vec<(u64, MyItem)> = Vec::new();
  for pos in &positions_to_verify {
    let elem = mmr.batch().get_elem(*pos).await.unwrap().unwrap();
    entries_to_verify.push((*pos, elem));
  }

  let mut tampered_entries_to_verify = entries_to_verify.clone();
  for &proof_pos in &tampered_positions {
    tampered_entries_to_verify[proof_pos] = (
      tampered_entries_to_verify[proof_pos].0,
      MyItem::Number(31337),
    );
  }

  let handrolled_proof: Option<InclusionProof<MyMerge>> =
    if let Some(handrolled_proof_positions) = handrolled_proof_positions {
      let mut proof_elems: Vec<MyItem> = Vec::new();
      for pos in &handrolled_proof_positions {
        let elem = mmr.batch().get_elem(*pos).await.unwrap().unwrap();
        proof_elems.push(elem);
      }
      Some(InclusionProof::new(mmr.mmr_size(), proof_elems))
    } else {
      None
    };

  if let Some(handrolled_proof) = handrolled_proof {
    let handrolled_proof_result =
      handrolled_proof.verify(&root, tampered_entries_to_verify.clone());
    assert!(
      handrolled_proof_result.is_err() || !handrolled_proof_result.unwrap()
    );
  }

  match mmr.gen_proof(positions_to_verify.clone()).await {
    Ok(proof) => {
      assert!(proof.verify(&root, entries_to_verify).unwrap());
      assert!(!proof.verify(&root, tampered_entries_to_verify).unwrap());
    }
    Err(Error::NodeProofsNotSupported) => {
      assert!(
        positions_to_verify
          .iter()
          .any(|pos| pos_height_in_tree(*pos) > 0)
      );
    }
    Err(e) => panic!("Unexpected error: {e}"),
  }
}

#[tokio::test]
async fn test_generic_proofs() {
  test_invalid_proof_verification(7, vec![5], vec![0], Some(vec![2, 9, 10]))
    .await;
  test_invalid_proof_verification(7, vec![1, 2], vec![0], Some(vec![5, 9, 10]))
    .await;
  test_invalid_proof_verification(7, vec![1, 5], vec![0], Some(vec![0, 9, 10]))
    .await;
  test_invalid_proof_verification(
    7,
    vec![1, 6],
    vec![0],
    Some(vec![0, 5, 9, 10]),
  )
  .await;
  test_invalid_proof_verification(7, vec![5, 6], vec![0], Some(vec![2, 9, 10]))
    .await;
  test_invalid_proof_verification(
    7,
    vec![1, 5, 6],
    vec![0],
    Some(vec![0, 9, 10]),
  )
  .await;
  test_invalid_proof_verification(
    7,
    vec![1, 5, 7],
    vec![0],
    Some(vec![0, 8, 10]),
  )
  .await;
  test_invalid_proof_verification(
    7,
    vec![5, 6, 7],
    vec![0],
    Some(vec![2, 8, 10]),
  )
  .await;
  test_invalid_proof_verification(
    7,
    vec![5, 6, 7, 8, 9, 10],
    vec![0],
    Some(vec![2]),
  )
  .await;
  test_invalid_proof_verification(
    7,
    vec![1, 5, 7, 8, 9, 10],
    vec![0],
    Some(vec![0]),
  )
  .await;
  test_invalid_proof_verification(
    7,
    vec![0, 1, 5, 7, 8, 9, 10],
    vec![0],
    Some(vec![]),
  )
  .await;
  test_invalid_proof_verification(
    7,
    vec![0, 1, 5, 6, 7, 8, 9, 10],
    vec![0],
    Some(vec![]),
  )
  .await;
  test_invalid_proof_verification(
    7,
    vec![0, 1, 2, 5, 6, 7, 8, 9, 10],
    vec![0],
    Some(vec![]),
  )
  .await;
  test_invalid_proof_verification(
    7,
    vec![0, 1, 2, 3, 7, 8, 9, 10],
    vec![0],
    Some(vec![4]),
  )
  .await;
  test_invalid_proof_verification(
    7,
    vec![0, 2, 3, 7, 8, 9, 10],
    vec![0],
    Some(vec![1, 4]),
  )
  .await;
  test_invalid_proof_verification(
    7,
    vec![0, 3, 7, 8, 9, 10],
    vec![0],
    Some(vec![1, 4]),
  )
  .await;
  test_invalid_proof_verification(
    7,
    vec![0, 2, 3, 7, 8, 9, 10],
    vec![0],
    Some(vec![1, 4]),
  )
  .await;
}

proptest! {
    #[test]
    fn test_random_mmr(count in 10u32..500u32) {
        tokio::runtime::Runtime::new().unwrap().block_on(async {
            let mut leaves: Vec<u32> = (0..count).collect();
            let mut rng = thread_rng();
            leaves.shuffle(&mut rng);
            let leaves_count = rng.gen_range(1..count - 1);
            leaves.truncate(leaves_count as usize);
            test_mmr(count, leaves).await;
        });
    }

    #[test]
    fn test_random_gen_root_with_new_leaf(count in 1u32..500u32) {
        tokio::runtime::Runtime::new().unwrap().block_on(async {
            test_gen_new_root_from_proof(count).await;
        });
    }
}
