const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');

function stableValue(value) {
  if (value === null || typeof value !== 'object') return value;
  if (Array.isArray(value)) return value.map(stableValue);

  return Object.keys(value).sort().reduce((acc, key) => {
    const current = value[key];
    if (typeof current !== 'undefined') acc[key] = stableValue(current);
    return acc;
  }, {});
}

function sha256(value) {
  return crypto
    .createHash('sha256')
    .update(typeof value === 'string' ? value : JSON.stringify(stableValue(value)))
    .digest('hex');
}

class ChainBlock {
  constructor({
    height,
    previousHash,
    proofIds,
    transactionIds,
    rewardIds,
    proofHashes,
    txHashes,
    rewardHashes,
    merkleRoot,
    blockHash,
    status,
    createdAt
  } = {}) {
    this.id = uuidv4();
    this.height = Number(height || 1);
    this.previousHash = previousHash || 'GENESIS';
    this.proofIds = Array.isArray(proofIds) ? proofIds : [];
    this.transactionIds = Array.isArray(transactionIds) ? transactionIds : [];
    this.rewardIds = Array.isArray(rewardIds) ? rewardIds : [];
    this.proofHashes = Array.isArray(proofHashes) ? proofHashes : [];
    this.txHashes = Array.isArray(txHashes) ? txHashes : [];
    this.rewardHashes = Array.isArray(rewardHashes) ? rewardHashes : [];
    this.merkleRoot = merkleRoot || ChainBlock.calculateMerkleRoot([...this.proofHashes, ...this.txHashes, ...this.rewardHashes]);
    this.blockHash = blockHash || this.calculateBlockHash();
    this.status = status || 'SEALED_MOCK';
    this.createdAt = createdAt || new Date().toISOString();
  }

  calculateBlockHash() {
    const payload = {
      height: this.height,
      previousHash: this.previousHash,
      proofIds: this.proofIds,
      transactionIds: this.transactionIds,
      merkleRoot: this.merkleRoot,
      createdAt: this.createdAt
    };
    if (this.rewardIds.length || this.rewardHashes.length) {
      payload.rewardIds = this.rewardIds;
      payload.rewardHashes = this.rewardHashes;
    }
    return sha256(payload);
  }

  static calculateMerkleRoot(leaves = []) {
    const normalized = leaves.length ? leaves.map(item => sha256(item || '')) : [sha256('EMPTY_BLOCK')];

    let level = normalized;
    while (level.length > 1) {
      const next = [];
      for (let index = 0; index < level.length; index += 2) {
        const left = level[index];
        const right = level[index + 1] || left;
        next.push(sha256(`${left}${right}`));
      }
      level = next;
    }

    return level[0];
  }
}

module.exports = ChainBlock;
