/**
 * Validates that the hand-rolled ABI encoders in app/lib/forum-xmtp.ts produce
 * identical bytes to ethers' canonical Interface encoding.
 */
const { ethers, Interface } = require('ethers');

const iface = new Interface([
  'function getGroup(string) view returns (bytes,address,uint256,uint256)',
  'function registerGroup(string,bytes)',
  'function isRegistered(string) view returns (bool)',
]);

// ---- Replicate the encoders from forum-xmtp.ts ----
function encodeStringArg(s) {
  const offset = '0000000000000000000000000000000000000000000000000000000000000020';
  const bytes = new TextEncoder().encode(s);
  const len = bytes.length.toString(16).padStart(64, '0');
  let hex = '';
  for (const b of bytes) hex += b.toString(16).padStart(2, '0');
  const padded = (hex + '0'.repeat(64)).slice(0, Math.ceil(hex.length / 64) * 64);
  return offset + len + padded;
}

function encodeStringBytesArgs(s, bytes) {
  const strBytes = new TextEncoder().encode(s);
  const strLen = strBytes.length;
  const strChunks = Math.ceil(strLen / 32);
  const off1 = (0x40).toString(16).padStart(64, '0');
  const off2 = (0x40 + 0x20 + strChunks * 32).toString(16).padStart(64, '0');
  const strLenHex = strLen.toString(16).padStart(64, '0');
  let strData = '';
  for (const b of strBytes) strData += b.toString(16).padStart(2, '0');
  strData = (strData + '0'.repeat(64)).slice(0, strChunks * 64);
  const bLen = bytes.length;
  const bChunks = Math.ceil(bLen / 32);
  const bLenHex = bLen.toString(16).padStart(64, '0');
  let bData = '';
  for (const b of bytes) bData += b.toString(16).padStart(2, '0');
  bData = (bData + '0'.repeat(64)).slice(0, bChunks * 64);
  return off1 + off2 + strLenHex + strData + bLenHex + bData;
}

const cases = [
  { fn: 'getGroup',     mine: '0xabef281e' + encodeStringArg('general'),
    canonical: iface.encodeFunctionData('getGroup', ['general']) },
  { fn: 'getGroup',     mine: '0xabef281e' + encodeStringArg('announcements'),
    canonical: iface.encodeFunctionData('getGroup', ['announcements']) },
  { fn: 'isRegistered', mine: '0xc822d7f0' + encodeStringArg('technical'),
    canonical: iface.encodeFunctionData('isRegistered', ['technical']) },
  { fn: 'registerGroup',
    mine: '0x98f9cdb3' + encodeStringBytesArgs('general', new Uint8Array([0xde, 0xad, 0xbe, 0xef])),
    canonical: iface.encodeFunctionData('registerGroup', ['general', '0xdeadbeef']) },
  { fn: 'registerGroup-larger',
    mine: '0x98f9cdb3' + encodeStringBytesArgs('governance', new Uint8Array(33).fill(0xab)),
    canonical: iface.encodeFunctionData('registerGroup', ['governance', '0x' + 'ab'.repeat(33)]) },
];

let pass = 0, fail = 0;
for (const c of cases) {
  const ok = c.mine.toLowerCase() === c.canonical.toLowerCase();
  if (ok) { pass++; console.log(`✓ ${c.fn}`); }
  else { fail++; console.log(`✗ ${c.fn}\n  mine:      ${c.mine}\n  canonical: ${c.canonical}`); }
}

// ---- Test the getGroup return decoder against canonical encoding ----
function decodeGetGroupReturn(result) {
  if (!result || result === '0x') return null;
  const hex = result.slice(2);
  if (hex.length < 64 * 4) return null;
  const bytesOffset = parseInt(hex.slice(0, 64), 16) * 2;
  if (bytesOffset + 64 > hex.length) return null;
  const bytesLen = parseInt(hex.slice(bytesOffset, bytesOffset + 64), 16);
  if (bytesLen === 0) return null;
  return '0x' + hex.slice(bytesOffset + 64, bytesOffset + 64 + bytesLen * 2);
}

const expectedBytes = '0xfeedfacecafebeef';
const encoded = ethers.AbiCoder.defaultAbiCoder().encode(
  ['bytes', 'address', 'uint256', 'uint256'],
  [expectedBytes, '0xfA15Ee28939B222B0448261A22156070f0A7813C', 1730000000n, 1730000000n],
);
const decoded = decodeGetGroupReturn(encoded);
if (decoded === expectedBytes) { console.log(`✓ decoder roundtrip (${expectedBytes})`); pass++; }
else { console.log(`✗ decoder roundtrip\n  expected: ${expectedBytes}\n  got: ${decoded}`); fail++; }

const decodedEmpty = decodeGetGroupReturn('0x');
if (decodedEmpty === null) { console.log('✓ decoder empty -> null'); pass++; }
else { console.log(`✗ decoder empty: ${decodedEmpty}`); fail++; }

console.log(`\n${pass} passed, ${fail} failed`);
process.exit(fail > 0 ? 1 : 0);
