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

use super::{BlockHeader, ChainData};

pub struct Hasher {}

impl Hasher {
  pub fn hash_chain_data(data: &ChainData) -> String {
    let mut hasher = Sha256::new();
    match data {
      ChainData::Transaction(tx) => {
        hasher.update(tx.to());
        hasher.update(tx.from());
        hasher.update(tx.value().to_be_bytes());
        hasher.update(tx.data());
      }
    }
    let res = hasher.finalize();
    hex::encode(res)
  }

  pub fn calculate_next_level(level: &[String]) -> Vec<String> {
    let mut next_level = Vec::new();

    for chunk in level.chunks(2) {
      let combined_hash = if chunk.len() == 2 {
        Self::hash_pair(&chunk[0], &chunk[1])
      } else {
        Self::hash_pair(&chunk[0], &chunk[0])
      };
      next_level.push(combined_hash);
    }
    next_level
  }

  pub fn calculate_merkle_root(tx_hashes: &[String]) -> String {
    if tx_hashes.is_empty() {
      return Self::hash_data("");
    }

    if tx_hashes.len() == 1 {
      return tx_hashes[0].clone();
    }

    let mut current_level = tx_hashes.to_vec();

    while current_level.len() > 1 {
      current_level = Self::calculate_next_level(&current_level);
    }

    return current_level[0].clone();
  }

  fn hash_pair(left: &str, right: &str) -> String {
    let combined = format!("{}{}", left, right);
    Self::hash_data(&combined)
  }

  fn hash_data(data: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(data.as_bytes());
    hex::encode(hasher.finalize())
  }

  pub fn calculate_block_hash(head: &BlockHeader) -> String {
    let mut hasher = sha2::Sha256::new();

    hasher.update(head.nonce().to_be_bytes());
    hasher.update(head.previous_hash());
    hasher.update(head.timestamp().to_be_bytes());
    hasher.update(head.merkle_root());

    let res = hasher.finalize();
    hex::encode(res)
  }
}
