// Solana Pay integration: reference, URL builder, on-chain watcher.

const SOLPAY_RECIPIENT = '64hJV68KJkhwHDAn6MX3fAD2QwbcztiJAnh7XC2UyVSA';
// Go through our Supabase edge function proxy — avoids browser CORS/403s.
const SOL_RPC_URLS = [
  'https://vcesinuozlwbnthozcfk.supabase.co/functions/v1/solpay-rpc',
];
let SOL_RPC_URL = SOL_RPC_URLS[0];
// Accept the payment if received >= expected * (1 - SOL_TOLERANCE).
const SOL_TOLERANCE = 0.01; // 1% tolerance on the low side
const LAMPORTS_PER_SOL = 1_000_000_000;

function generateReference() {
  return solanaWeb3.Keypair.generate().publicKey.toBase58();
}

function buildSolanaPayUrl({ recipient = SOLPAY_RECIPIENT, amount, reference, label = 'DavesShop', message }) {
  // Solana Pay URL scheme.
  const params = new URLSearchParams();
  params.set('amount', String(amount));
  params.set('reference', reference);
  params.set('label', label);
  if (message) params.set('message', message);
  return `solana:${recipient}?${params.toString()}`;
}

async function solanaRpc(method, params) {
  let lastErr = null;
  for (let i = 0; i < SOL_RPC_URLS.length; i++) {
    const url = SOL_RPC_URLS[i];
    try {
      const res = await fetch(url, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }),
      });
      if (!res.ok) {
        lastErr = new Error(`${url}: HTTP ${res.status}`);
        continue;
      }
      const json = await res.json();
      if (json.error) {
        lastErr = new Error(json.error.message || `RPC error in ${method}`);
        continue;
      }
      SOL_RPC_URL = url;
      return json.result;
    } catch (e) {
      lastErr = e;
    }
  }
  throw lastErr || new Error(`All RPCs failed for ${method}`);
}

async function findPaymentByReference(reference) {
  const sigs = await solanaRpc('getSignaturesForAddress', [reference, { limit: 5 }]);
  if (!sigs || sigs.length === 0) return null;
  return sigs[sigs.length - 1];
}

// Fetch recent signatures for the recipient, newest first.
async function findRecentRecipientSignatures(recipient, limit = 10) {
  const sigs = await solanaRpc('getSignaturesForAddress', [recipient, { limit }]);
  return sigs || [];
}

async function fetchTx(signature) {
  return await solanaRpc('getTransaction', [
    signature,
    { maxSupportedTransactionVersion: 0, commitment: 'confirmed', encoding: 'json' },
  ]);
}

function verifyTxAmount(tx, recipient, expectedSol, tolerance = SOL_TOLERANCE) {
  try {
    const keys = (tx.transaction.message.accountKeys || []).map(k =>
      typeof k === 'string' ? k : k.pubkey
    );
    const idx = keys.indexOf(recipient);
    if (idx < 0) return { ok: false, received: 0, reason: 'recipient_not_in_tx' };
    const pre = tx.meta.preBalances[idx];
    const post = tx.meta.postBalances[idx];
    const receivedSol = Math.max(0, (post - pre) / LAMPORTS_PER_SOL);
    const minAccepted = expectedSol * (1 - tolerance);
    return { ok: receivedSol + 1e-9 >= minAccepted, received: receivedSol, minAccepted };
  } catch (e) {
    return { ok: false, received: 0, reason: e.message };
  }
}

/**
 * Poll for a payment referencing `reference` that sends >= expectedSol (minus tolerance)
 * to `recipient`. Fires callbacks as state changes. Returns a cancel function.
 */
function watchReference({
  reference,
  recipient = SOLPAY_RECIPIENT,
  expectedSol,
  tolerance = SOL_TOLERANCE,
  intervalMs = 4000,
  onUpdate = () => {},
}) {
  let cancelled = false;
  let detectedSig = null;
  let verifiedReported = false;
  const run = async () => {
    if (cancelled) return;
    try {
      const sig = detectedSig ? { signature: detectedSig } : await findPaymentByReference(reference);
      if (sig) {
        if (!detectedSig) {
          detectedSig = sig.signature;
          onUpdate({ type: 'detected', signature: sig.signature, confirmationStatus: sig.confirmationStatus });
        }
        // Poll for the full tx (confirmed+)
        const tx = await fetchTx(detectedSig);
        if (tx && tx.meta) {
          const check = verifyTxAmount(tx, recipient, expectedSol, tolerance);
          if (check.ok && !verifiedReported) {
            verifiedReported = true;
            onUpdate({ type: 'verified', signature: detectedSig, received: check.received });
            cancelled = true;
            return;
          }
          if (!check.ok) {
            onUpdate({ type: 'amount_mismatch', signature: detectedSig, received: check.received, expected: expectedSol, minAccepted: check.minAccepted });
          }
        } else {
          onUpdate({ type: 'confirming', signature: detectedSig });
        }
      }
    } catch (e) {
      onUpdate({ type: 'error', error: e.message });
    }
    if (!cancelled) setTimeout(run, intervalMs);
  };
  setTimeout(run, 400);
  return () => { cancelled = true; };
}

async function fetchSolRate() {
  try {
    const r = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd');
    if (!r.ok) return null;
    const j = await r.json();
    return j?.solana?.usd || null;
  } catch {
    return null;
  }
}

/**
 * Poll the recipient address for an incoming SOL transfer matching expectedSol
 * (within tolerance) and newer than `startTimeSec` (unix seconds).
 * Returns a cancel function.
 */
function watchRecipientPayment({
  recipient = SOLPAY_RECIPIENT,
  expectedSol,
  tolerance = SOL_TOLERANCE,
  startTimeSec,
  intervalMs = 4000,
  onUpdate = () => {},
}) {
  let cancelled = false;
  let claimed = null; // signature that was already reported as detected
  const seen = new Set();

  const run = async () => {
    if (cancelled) return;
    try {
      const sigs = await findRecentRecipientSignatures(recipient, 15);
      for (const s of sigs) {
        if (cancelled) return;
        if (!s?.signature || seen.has(s.signature)) continue;
        seen.add(s.signature);
        // Ignore anything older than the moment the customer opened this checkout.
        if (startTimeSec && s.blockTime && s.blockTime < startTimeSec - 30) continue;
        // Only inspect once confirmed.
        if (s.confirmationStatus && s.confirmationStatus === 'processed') continue;
        if (s.err) continue;

        const tx = await fetchTx(s.signature);
        if (!tx || !tx.meta) continue;
        const check = verifyTxAmount(tx, recipient, expectedSol, tolerance);
        if (check.ok) {
          if (!claimed) {
            claimed = s.signature;
            onUpdate({ type: 'detected', signature: s.signature, confirmationStatus: s.confirmationStatus });
          }
          onUpdate({ type: 'verified', signature: s.signature, received: check.received });
          cancelled = true;
          return;
        } else if (check.received > 0) {
          // A transfer to the recipient happened, but amount was short — warn once per sig.
          onUpdate({ type: 'amount_mismatch', signature: s.signature, received: check.received, expected: expectedSol, minAccepted: check.minAccepted });
        }
      }
    } catch (e) {
      onUpdate({ type: 'error', error: e.message });
    }
    if (!cancelled) setTimeout(run, intervalMs);
  };
  setTimeout(run, 400);
  return () => { cancelled = true; };
}

Object.assign(window, {
  SOLPAY_RECIPIENT, SOL_TOLERANCE,
  generateReference, buildSolanaPayUrl,
  findPaymentByReference, findRecentRecipientSignatures, fetchTx, verifyTxAmount,
  watchReference, watchRecipientPayment,
  fetchSolRate,
});
