import { ethers } from 'ethers';
import { buildMerkleRoot } from '@dbis/checkpoint-core';

export type ReceiptMeta = {
  logCount: number;
  receiptHash: string;
};

export function receiptLeafHash(txHash: string, meta: ReceiptMeta): string {
  return ethers.keccak256(
    ethers.AbiCoder.defaultAbiCoder().encode(
      ['bytes32', 'bytes32', 'uint32'],
      [txHash, meta.receiptHash, meta.logCount]
    )
  );
}

export function receiptsRoot(txHashes: string[], metas: ReceiptMeta[]): string {
  if (txHashes.length === 0) return ethers.ZeroHash;
  const hashes = txHashes.map((h, i) => receiptLeafHash(h, metas[i]!));
  return buildMerkleRoot(hashes);
}

export async function fetchReceiptMeta(
  provider: ethers.JsonRpcProvider,
  txHash: string
): Promise<ReceiptMeta> {
  const receipt = await provider.getTransactionReceipt(txHash);
  if (!receipt) {
    return { logCount: 0, receiptHash: ethers.ZeroHash };
  }
  const logCount = receipt.logs.length;
  const receiptHash = ethers.keccak256(
    ethers.AbiCoder.defaultAbiCoder().encode(
      ['bytes32', 'uint256', 'uint8', 'uint32'],
      [receipt.blockHash, receipt.gasUsed, receipt.status ?? 0, logCount]
    )
  );
  return { logCount, receiptHash };
}

export async function attachReceiptMeta(
  provider: ethers.JsonRpcProvider,
  leaves: Array<{ txHash: string; logCount?: number; receiptHash?: string }>
): Promise<void> {
  for (const leaf of leaves) {
    const meta = await fetchReceiptMeta(provider, leaf.txHash);
    leaf.logCount = meta.logCount;
    leaf.receiptHash = meta.receiptHash;
  }
}
