'use strict';

const { VersionedTransaction } = require('@solana/web3.js');
// bs58 v6 under CommonJS exports only { default } — see lib/wallet.js.
const bs58 = require('bs58').default;

/**
 * Trade execution.
 *
 * Two submission paths — plain RPC (default) and Jito bundles (opt-in).
 *
 * NFR-15: with ENABLE_JITO_BUNDLES false, this is byte-for-byte the original
 * behavior — connection.sendRawTransaction via the operator's own RPC. The Jito
 * branch is not entered and the Jito client is never constructed.
 *
 * The transaction ID is computed LOCALLY (bs58 of the transaction's own first
 * signature) immediately after signing, BEFORE either submission path runs.
 * This lets both paths confirm identically afterward via the same
 * connection.confirmTransaction call.
 */

/** Local txid from the signed transaction itself — no network round trip. */
function computeTransactionId(signedTx) {
  const sig = signedTx.signatures[0];
  if (!sig) throw new Error('Transaction has no signature to derive an ID from');
  return bs58.encode(sig);
}

function signSwapTransaction(swapTransactionBase64, keypair) {
  const buf = Buffer.from(swapTransactionBase64, 'base64');
  const tx = VersionedTransaction.deserialize(buf);
  // Jupiter's transaction is signed, never structurally modified (NFR-14).
  tx.sign([keypair]);
  return tx;
}

/**
 * Executes one swap leg.
 *
 * DRY_RUN (default ON) runs the entire path — quote, build, sign, derive txid —
 * and stops immediately before submission. Nothing is broadcast and no funds
 * move. This is additive to the spec: a from-zero build should not discover a
 * logic error on its first real submission.
 */
async function executeLeg({
  connection,
  keypair,
  jupiter,
  jito,
  quote,
  dryRun,
  enableJitoBundles,
  jitoTipLamports,
  priorityLamports,
  logger = console,
}) {
  const built = await jupiter.buildSwapTransaction({
    quoteResponse: quote,
    userPublicKey: keypair.publicKey.toBase58(),
    priorityLamports,
  });

  const signedTx = signSwapTransaction(built.swapTransaction, keypair);

  // Computed before either submission path — both confirm identically after.
  const txid = computeTransactionId(signedTx);

  if (dryRun) {
    logger.info(
      `[DRY_RUN] Would submit ${enableJitoBundles ? 'Jito bundle' : 'via RPC'} — ` +
        `txid ${txid} (nothing was broadcast)`
    );
    return { txid, submitted: false, dryRun: true, path: enableJitoBundles ? 'jito' : 'rpc' };
  }

  if (enableJitoBundles) {
    // Tip account fetched live, per attempt (NFR-13). Tip is a separate
    // transaction in the same bundle; Jupiter's tx is untouched (NFR-14).
    const tipAccount = await jito.pickTipAccount();
    const { blockhash } = await connection.getLatestBlockhash('confirmed');
    const tipTx = jito.buildTipTransaction({
      payer: keypair,
      tipAccount,
      tipLamports: jitoTipLamports,
      blockhash,
    });

    const bundleId = await jito.sendJitoBundle([signedTx, tipTx]);
    logger.info(
      `Submitted Jito bundle ${bundleId} — txid ${txid}, tip ${jitoTipLamports} lamports ` +
        `paid to ${tipAccount} (paid win or lose)`
    );
    await confirm(connection, txid);
    return { txid, submitted: true, path: 'jito', bundleId, tipAccount, tipLamports: jitoTipLamports };
  }

  // Default path — unchanged from pre-Jito behavior.
  await connection.sendRawTransaction(signedTx.serialize(), {
    skipPreflight: false,
    maxRetries: 2,
  });
  logger.info(`Submitted via RPC — txid ${txid}`);
  await confirm(connection, txid);
  return { txid, submitted: true, path: 'rpc' };
}

/** Identical confirmation for both paths. */
async function confirm(connection, txid) {
  const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
  return connection.confirmTransaction(
    { signature: txid, blockhash, lastValidBlockHeight },
    'confirmed'
  );
}

module.exports = { executeLeg, computeTransactionId, signSwapTransaction, confirm };
