// End-to-end test for the marketplace single-product MPP buy flow.
//
// Fill in the CONFIG block, then run:
//   cp scripts/test-mpp-buy.ts /tmp/tempo-transfer/ && \
//     (cd /tmp/tempo-transfer && ./node_modules/.bin/tsx test-mpp-buy.ts)
//
// (The script must run inside the isolated /tmp/tempo-transfer/ install — the
// repo's @noble/hashes version conflicts with viem's ESM imports here. To
// rebuild that dir: mkdir -p /tmp/tempo-transfer && cd /tmp/tempo-transfer \
//   && npm init -y && npm install viem@2.47.6 mppx tsx)
//
// What it does:
//   1. Reads the bot wallet's USDC balance + treasury + (optionally) seller.
//   2. POSTs to /api/marketplace/buy/:productId via the mppx client.
//   3. If the product has a seller with a wallet, the on-chain charge splits
//      80/20 — verify both wallets move in the same block.
//   4. Follows the first download URL to confirm the JWT-gated download works.

// ─── CONFIG ──────────────────────────────────────────────────────────────────

const RELAY = 'https://api.bloby.bot';
const PRODUCT_ID = '<FILL_ME — e.g. sebastians-skill>';

const BOT = {
  relayToken: '<FILL_ME — 64-hex-char relay token>',
  privateKey: '<FILL_ME — bot wallet private key, with or without 0x prefix>',
  wallet:     '<FILL_ME — bot wallet address>',
};

// Optional: seller wallet to monitor for the 80% split landing.
// Leave null if the product has no seller (then 100% goes to treasury).
const SELLER_WALLET: string | null = null;

const TREASURY = '0x084A80e395FaE8Aaf58F591f47F63DA1EeF06bbC';

// ─────────────────────────────────────────────────────────────────────────────

import { Mppx, tempo } from 'mppx/client';
import { privateKeyToAccount } from 'viem/accounts';
import { createPublicClient, http, formatUnits } from 'viem';
import { tempo as tempoChain } from 'viem/chains';

const USDC = '0x20c000000000000000000000b9537d11c60e8b50' as const;
const RPC = 'https://rpc.tempo.xyz';

const erc20Abi = [{
  name: 'balanceOf',
  type: 'function',
  stateMutability: 'view',
  inputs: [{ name: 'account', type: 'address' }],
  outputs: [{ name: '', type: 'uint256' }],
}] as const;

function normalizePk(pk: string): `0x${string}` {
  const t = pk.trim();
  return (t.startsWith('0x') ? t : `0x${t}`) as `0x${string}`;
}

async function balanceOf(client: any, address: `0x${string}`) {
  return client.readContract({
    address: USDC, abi: erc20Abi, functionName: 'balanceOf', args: [address],
  });
}

async function main() {
  for (const [k, v] of [['PRODUCT_ID', PRODUCT_ID], ['BOT.relayToken', BOT.relayToken], ['BOT.privateKey', BOT.privateKey], ['BOT.wallet', BOT.wallet]]) {
    if (typeof v !== 'string' || v.startsWith('<')) {
      console.error(`Fill in ${k} before running.`);
      process.exit(1);
    }
  }

  const account = privateKeyToAccount(normalizePk(BOT.privateKey));
  if (account.address.toLowerCase() !== BOT.wallet.toLowerCase()) {
    console.error(`privateKey derives ${account.address}, but BOT.wallet is ${BOT.wallet}`);
    process.exit(1);
  }

  const publicClient = createPublicClient({ chain: tempoChain, transport: http(RPC) });

  const [botBefore, treasuryBefore, sellerBefore] = await Promise.all([
    balanceOf(publicClient, account.address),
    balanceOf(publicClient, TREASURY as `0x${string}`),
    SELLER_WALLET ? balanceOf(publicClient, SELLER_WALLET as `0x${string}`) : Promise.resolve(0n),
  ]);

  console.log(`Wallet:           ${account.address}`);
  console.log(`Balance before:   ${formatUnits(botBefore, 6)} USDC`);
  console.log(`Treasury before:  ${formatUnits(treasuryBefore, 6)} USDC`);
  if (SELLER_WALLET) console.log(`Seller before:    ${formatUnits(sellerBefore, 6)} USDC`);
  console.log('');

  const mppx = Mppx.create({
    polyfill: false,
    methods: [tempo({ account })],
  });

  const url = `${RELAY}/api/marketplace/buy/${PRODUCT_ID}`;
  console.log(`POST ${url}`);

  const res = await mppx.fetch(url, {
    method: 'POST',
    headers: {
      'X-Bloby-Token': BOT.relayToken,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({}),
  });

  const body = await res.text();
  const receipt = res.headers.get('Payment-Receipt');
  console.log(`Status:           ${res.status}`);
  console.log(`Body:             ${body}`);
  console.log(`Payment-Receipt:  ${receipt ?? '(none)'}`);
  console.log('');

  if (!res.ok) {
    process.exit(1);
  }

  const parsed = JSON.parse(body);
  if (parsed.skills?.length) {
    console.log(`Bought ${parsed.skills.length} skill(s) via "${parsed.paidVia}":`);
    for (const skill of parsed.skills) {
      console.log(`  - ${skill.name}@${skill.version} → ${skill.url}`);
    }
    console.log('');

    // Verify the first download URL actually returns a tarball
    const dlUrl = parsed.skills[0].url;
    console.log(`Probing download: HEAD ${dlUrl}`);
    const dl = await fetch(dlUrl, { method: 'GET' });
    const contentLength = dl.headers.get('Content-Length');
    console.log(`  status: ${dl.status}  content-type: ${dl.headers.get('Content-Type')}  size: ${contentLength ?? '?'} bytes`);
    // drain body to release connection
    if (dl.body) await dl.arrayBuffer();
    console.log('');
  }

  const [botAfter, treasuryAfter, sellerAfter] = await Promise.all([
    balanceOf(publicClient, account.address),
    balanceOf(publicClient, TREASURY as `0x${string}`),
    SELLER_WALLET ? balanceOf(publicClient, SELLER_WALLET as `0x${string}`) : Promise.resolve(0n),
  ]);
  console.log(`Balance after:    ${formatUnits(botAfter, 6)} USDC  (Δ ${formatUnits(botAfter - botBefore, 6)})`);
  console.log(`Treasury after:   ${formatUnits(treasuryAfter, 6)} USDC  (Δ +${formatUnits(treasuryAfter - treasuryBefore, 6)})`);
  if (SELLER_WALLET) console.log(`Seller after:     ${formatUnits(sellerAfter, 6)} USDC  (Δ +${formatUnits(sellerAfter - sellerBefore, 6)})`);
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});
