use ghost_traits::hashing::GhostHasher;
use sp_arithmetic::traits::AtLeast8BitUnsigned;
use sp_runtime::traits::UniqueSaturatedInto;
use sp_std::vec::Vec;

pub fn generate_tree<H, I, S, F, E>(
    max_index: I,
    raw_values: S,
    generate_preimage: F,
) -> Result<Vec<H::Hash>, E>
where
    H: GhostHasher,
    S: IntoIterator,
    I: AtLeast8BitUnsigned + UniqueSaturatedInto<usize>,
    F: Fn(S::Item) -> Result<(usize, Vec<u8>), E>,
{
    let num_of_leaves: usize = (max_index + I::one()).unique_saturated_into();
    let padded_leaves = num_of_leaves.next_power_of_two();
    let total_capacity = (2 * padded_leaves) - 1;
    let mut merkle_tree = sp_std::vec![H::empty(); total_capacity];

    for item in raw_values.into_iter() {
        let (index, preimage) = generate_preimage(item)?;
        merkle_tree[index] = H::hash(&preimage);
    }

    let mut layer_start = 0;
    let mut current_layer_len = padded_leaves;
    let mut write_ptr = padded_leaves;

    let hash_len = H::hash_len();
    let mut combined = sp_std::vec![0u8; hash_len * 2];

    while current_layer_len > 1 {
        for i in (0..current_layer_len).step_by(2) {
            let layer_index = layer_start + i;
            let left_bytes = merkle_tree[layer_index].as_ref();
            let right_bytes = merkle_tree[layer_index | 1].as_ref();

            combined[..hash_len].copy_from_slice(left_bytes);
            combined[hash_len..].copy_from_slice(right_bytes);

            merkle_tree[write_ptr] = H::hash(&combined);
            write_ptr += 1;
        }
        layer_start += current_layer_len;
        current_layer_len >>= 1;
    }

    Ok(merkle_tree)
}

pub fn generate_proof<H, I>(merkle_tree: &[H::Hash], max_index: I, index: I) -> Vec<H::Hash>
where
    H: GhostHasher,
    I: AtLeast8BitUnsigned + UniqueSaturatedInto<usize>,
{
    let num_of_leaves: usize = (max_index + I::one()).unique_saturated_into();
    let mut current_index: usize = index.unique_saturated_into();
    let mut current_layer_len = num_of_leaves.next_power_of_two();

    let tree_height = current_layer_len.trailing_zeros() as usize;
    let mut proof = Vec::with_capacity(tree_height);

    let mut layer_start = 0;

    while current_layer_len > 1 {
        let sibling_idx_in_layer = current_index ^ 1;
        let sibling_hash = merkle_tree[layer_start + sibling_idx_in_layer];
        proof.push(sibling_hash);

        layer_start += current_layer_len;
        current_layer_len >>= 1;
        current_index >>= 1;
    }

    proof
}

pub fn verify_tree_proof<H, I>(
    preimage: &[u8],
    merkle_proof: &[H::Hash],
    merkle_root: H::Hash,
    index: I,
) -> bool
where
    H: GhostHasher,
    I: PartialOrd + UniqueSaturatedInto<usize>,
{
    let mut current_hash = H::hash(preimage);
    let mut current_index: usize = index.unique_saturated_into();

    if current_index >= (1 << merkle_proof.len()) {
        return false;
    }

    let hash_len = H::hash_len();
    let mut combined = sp_std::vec![0u8; hash_len * 2];

    for sibling in merkle_proof.iter() {
        let sibling_bytes = sibling.as_ref();
        let hash_bytes = current_hash.as_ref();

        if current_index % 2 == 0 {
            combined[..hash_len].copy_from_slice(hash_bytes);
            combined[hash_len..].copy_from_slice(sibling_bytes);
        } else {
            combined[..hash_len].copy_from_slice(sibling_bytes);
            combined[hash_len..].copy_from_slice(hash_bytes);
        }

        current_hash = H::hash(&combined);
        current_index >>= 1;
    }

    current_hash == merkle_root
}

#[cfg(test)]
mod tests {
    use super::*;
    use sp_core::H256;
    use sp_io::hashing::blake2_256;

    pub struct TestBlake2Hasher;
    impl GhostHasher for TestBlake2Hasher {
        type Hash = H256;
        type HashBytes = [u8; 32];

        fn hash(data: &[u8]) -> Self::Hash {
            H256::from(blake2_256(data))
        }
        fn empty() -> Self::Hash {
            H256::zero()
        }
        fn hash_len() -> usize {
            H256::len_bytes()
        }
    }

    #[test]
    fn test_generate_tree_perfect_power_of_two() {
        let max_index = 3u32;
        let raw_data = sp_std::vec![
            (0, sp_std::vec![1]),
            (1, sp_std::vec![2]),
            (2, sp_std::vec![3]),
            (3, sp_std::vec![4]),
        ];

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        assert_eq!(tree.len(), 7);

        let h0 = TestBlake2Hasher::hash(&vec![1]);
        let h1 = TestBlake2Hasher::hash(&vec![2]);
        let h2 = TestBlake2Hasher::hash(&vec![3]);
        let h3 = TestBlake2Hasher::hash(&vec![4]);

        let mut c1 = [0u8; 64];
        c1[..32].copy_from_slice(h0.as_ref());
        c1[32..].copy_from_slice(h1.as_ref());
        let parent_left = TestBlake2Hasher::hash(&c1);

        let mut c2 = [0u8; 64];
        c2[..32].copy_from_slice(h2.as_ref());
        c2[32..].copy_from_slice(h3.as_ref());
        let parent_right = TestBlake2Hasher::hash(&c2);

        let mut c_root = [0u8; 64];
        c_root[..32].copy_from_slice(parent_left.as_ref());
        c_root[32..].copy_from_slice(parent_right.as_ref());
        let expected_root = TestBlake2Hasher::hash(&c_root);

        assert_eq!(tree.last().unwrap(), &expected_root);
    }

    #[test]
    fn test_generate_tree_with_padding_hashing() {
        let max_index = 2u32;
        let raw_data = sp_std::vec![
            (0, sp_std::vec![1]),
            (1, sp_std::vec![2]),
            (2, sp_std::vec![3]),
        ];

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        assert_eq!(tree.len(), 7);
        assert_eq!(tree[3], H256::zero());

        let h2 = TestBlake2Hasher::hash(&vec![3]);
        let h3_empty = H256::zero();

        let mut combined_right = [0u8; 64];
        combined_right[..32].copy_from_slice(h2.as_ref());
        combined_right[32..].copy_from_slice(h3_empty.as_ref());
        let expected_parent_right = TestBlake2Hasher::hash(&combined_right);

        assert_eq!(tree[5], expected_parent_right);
    }

    #[test]
    fn test_generate_tree_determinism() {
        let max_index = 1u32;

        let data_1 = sp_std::vec![(0, sp_std::vec![100]), (1, sp_std::vec![200])];
        let data_2 = sp_std::vec![(0, sp_std::vec![100]), (1, sp_std::vec![200])];
        let data_3 = sp_std::vec![(0, sp_std::vec![101]), (1, sp_std::vec![200])];

        let tree_1 =
            generate_tree::<TestBlake2Hasher, u32, _, _, ()>(max_index, data_1, |(i, b)| {
                Ok((i, b))
            })
            .unwrap();
        let tree_2 =
            generate_tree::<TestBlake2Hasher, u32, _, _, ()>(max_index, data_2, |(i, b)| {
                Ok((i, b))
            })
            .unwrap();
        let tree_3 =
            generate_tree::<TestBlake2Hasher, u32, _, _, ()>(max_index, data_3, |(i, b)| {
                Ok((i, b))
            })
            .unwrap();

        assert_eq!(tree_1.last().unwrap(), tree_2.last().unwrap());
        assert_ne!(tree_1.last().unwrap(), tree_3.last().unwrap());
    }

    #[test]
    fn test_generate_tree_single_element() {
        let max_index = 0u32;
        let raw_data = sp_std::vec![(0, sp_std::vec![42])];

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        assert_eq!(tree.len(), 1);
        assert_eq!(tree[0], TestBlake2Hasher::hash(&vec![42]));
    }

    #[test]
    fn test_proof_and_verification_end_to_end() {
        let max_index = 3u32;
        let raw_data = sp_std::vec![
            (0, sp_std::vec![1]),
            (1, sp_std::vec![2]),
            (2, sp_std::vec![3]),
            (3, sp_std::vec![4]),
        ];

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        let root = *tree.last().unwrap();

        let target_index = 2u32;
        let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, target_index);

        assert_eq!(proof.len(), 2);

        let preimage = sp_std::vec![3];
        let is_valid =
            verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, target_index);

        assert!(is_valid);
    }

    #[test]
    fn test_proof_index_out_of_bounds() {
        let max_index = 3u32;
        let raw_data = sp_std::vec![
            (0, sp_std::vec![1]),
            (1, sp_std::vec![2]),
            (2, sp_std::vec![3]),
            (3, sp_std::vec![4]),
        ];

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        let root = *tree.last().unwrap();

        let target_index = 2u32;
        let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, target_index);

        assert_eq!(proof.len(), 2);

        let preimage = sp_std::vec![3];
        let is_valid =
            verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, target_index + 1);

        assert!(!is_valid);
    }

    #[test]
    fn test_proof_contains_correct_siblings() {
        let max_index = 3u32;
        let raw_data = sp_std::vec![
            (0, sp_std::vec![10]),
            (1, sp_std::vec![20]),
            (2, sp_std::vec![30]),
            (3, sp_std::vec![40]),
        ];

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        let h0 = tree[0];
        let h1 = tree[1];
        let h2 = tree[2];
        let h3 = tree[3];

        let parent_left = tree[4]; // hash(h0 + h1)
        let parent_right = tree[5]; // hash(h2 + h3)

        let proof_for_0 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 0);
        assert_eq!(proof_for_0[0], h1);
        assert_eq!(proof_for_0[1], parent_right);

        let proof_for_1 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 1);
        assert_eq!(proof_for_1[0], h0);
        assert_eq!(proof_for_1[1], parent_right);

        let proof_for_2 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 2);
        assert_eq!(proof_for_2[0], h3);
        assert_eq!(proof_for_2[1], parent_left);

        let proof_for_3 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 3);
        assert_eq!(proof_for_3[0], h2);
        assert_eq!(proof_for_3[1], parent_left);
    }

    #[test]
    fn test_proof_for_padded_tree_leaf() {
        let max_index = 2u32;
        let raw_data = sp_std::vec![
            (0, sp_std::vec![1]),
            (1, sp_std::vec![2]),
            (2, sp_std::vec![3]),
        ];

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 2);
        assert_eq!(proof[0], H256::zero());

        let root = *tree.last().unwrap();
        let is_valid =
            verify_tree_proof::<TestBlake2Hasher, u32>(&sp_std::vec![3], &proof, root, 2);
        assert!(is_valid);
    }

    #[test]
    fn test_proof_for_single_element_tree() {
        let max_index = 0u32;
        let raw_data = sp_std::vec![(0, sp_std::vec![99])];

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 0);
        assert!(proof.is_empty());
    }

    #[test]
    fn test_verify_tree_proof_with_h0_h3_and_random_values() {
        let max_index = 3u32;
        let leaf_0 = sp_std::vec![10u8];
        let leaf_1 = sp_std::vec![20u8];
        let leaf_2 = sp_std::vec![30u8];
        let leaf_3 = sp_std::vec![40u8];

        let raw_data = sp_std::vec![
            (0, leaf_0.clone()),
            (1, leaf_1.clone()),
            (2, leaf_2.clone()),
            (3, leaf_3.clone())
        ];

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        let root = *tree.last().unwrap();

        let h0 = tree[0];
        let h1 = tree[1];
        let h2 = tree[2];
        let h3 = tree[3];

        let proof_for_1 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 1);
        assert_eq!(proof_for_1[0], h0);

        let is_valid_1 = verify_tree_proof::<TestBlake2Hasher, u32>(&leaf_1, &proof_for_1, root, 1);
        assert!(is_valid_1);

        let proof_for_2 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 2);
        assert_eq!(proof_for_2[0], h3);

        let is_valid_2 = verify_tree_proof::<TestBlake2Hasher, u32>(&leaf_2, &proof_for_2, root, 2);
        assert!(is_valid_2);

        let proof_for_0 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 0);
        assert_eq!(proof_for_0[0], h1);
        assert!(verify_tree_proof::<TestBlake2Hasher, u32>(
            &leaf_0,
            &proof_for_0,
            root,
            0
        ));

        let proof_for_3 = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 3);
        assert_eq!(proof_for_3[0], h2);
        assert!(verify_tree_proof::<TestBlake2Hasher, u32>(
            &leaf_3,
            &proof_for_3,
            root,
            3
        ));

        let mut bad_proof = proof_for_1.clone();
        bad_proof[0] = H256::random();
        let is_valid_bad_proof =
            verify_tree_proof::<TestBlake2Hasher, u32>(&leaf_1, &bad_proof, root, 1);
        assert!(!is_valid_bad_proof);

        let bad_preimage = vec![99u8];
        let is_valid_bad_preimage =
            verify_tree_proof::<TestBlake2Hasher, u32>(&bad_preimage, &proof_for_1, root, 1);
        assert!(!is_valid_bad_preimage);
    }

    #[test]
    fn test_verify_tree_proof_deep_tree_depth_4() {
        let max_index = 15u32;

        let mut raw_data = sp_std::vec![];
        for i in 0..16 {
            raw_data.push((i, sp_std::vec![i as u8]));
        }

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        let root = *tree.last().unwrap();

        assert_eq!(tree.len(), 31);

        let target_index = 11u32;
        let preimage = vec![target_index as u8];

        let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, target_index);
        assert_eq!(proof.len(), 4);

        let h10 = tree[10];
        assert_eq!(proof[0], h10);

        let is_valid =
            verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, target_index);
        assert!(is_valid);

        let is_valid_wrong_index =
            verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, 4);
        assert!(!is_valid_wrong_index);
    }

    #[test]
    fn test_verification_index_at_upper_bound() {
        let max_index = 3u32;
        let raw_data = sp_std::vec![
            (0, sp_std::vec![1]),
            (1, sp_std::vec![2]),
            (2, sp_std::vec![3]),
            (3, sp_std::vec![4]),
        ];

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        let root = *tree.last().unwrap();

        let target_index = 3u32;
        let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, target_index);

        assert_eq!(proof.len(), 2);

        let preimage = sp_std::vec![4];
        let is_valid =
            verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, target_index);

        assert!(is_valid);
    }

    #[test]
    fn test_verification_fails_when_index_out_of_bounds() {
        let max_index = 3u32;
        let raw_data = sp_std::vec![
            (0, sp_std::vec![1]),
            (1, sp_std::vec![2]),
            (2, sp_std::vec![3]),
            (3, sp_std::vec![4]),
        ];

        let tree = generate_tree::<TestBlake2Hasher, u32, _, _, ()>(
            max_index,
            raw_data,
            |(index, bytes)| Ok((index, bytes)),
        )
        .unwrap();

        let root = *tree.last().unwrap();

        let proof = generate_proof::<TestBlake2Hasher, u32>(&tree, max_index, 3u32);

        let target_index = 4u32;
        let preimage = sp_std::vec![4];

        let is_valid =
            verify_tree_proof::<TestBlake2Hasher, u32>(&preimage, &proof, root, target_index);

        assert!(!is_valid);
    }
}
