/* ═══════════════════════════════════════════════════════════════════════════
   WCAHT Crypto Library — Ed25519, Base58, TX Builder
   ═══════════════════════════════════════════════════════════════════════════
   Shared between popup, background, and content scripts.
   Uses tweetnacl (bundled) for Ed25519 keypair + signing.
*/

// ── Base58 ──────────────────────────────────────────────────────────────────
const B58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';

export function b58encode(bytes) {
  let num = BigInt(0);
  for (const b of bytes) num = num * 256n + BigInt(b);
  let str = '';
  while (num > 0n) { str = B58_ALPHABET[Number(num % 58n)] + str; num /= 58n; }
  for (const b of bytes) { if (b === 0) str = '1' + str; else break; }
  return str || '1';
}

export function b58decode(str) {
  let num = BigInt(0);
  for (const c of str) {
    const i = B58_ALPHABET.indexOf(c);
    if (i < 0) throw new Error('Invalid base58 char: ' + c);
    num = num * 58n + BigInt(i);
  }
  const hex = num.toString(16).padStart(2, '0');
  const padded = hex.length % 2 ? '0' + hex : hex;
  const bytes = [];
  for (let i = 0; i < padded.length; i += 2) bytes.push(parseInt(padded.substr(i, 2), 16));
  for (const c of str) { if (c === '1') bytes.unshift(0); else break; }
  return new Uint8Array(bytes);
}

// ── Hex helpers ─────────────────────────────────────────────────────────────
export function hexToBytes(hex) {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
  return bytes;
}

export function bytesToHex(bytes) {
  return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}

// ── Little-endian encoders ──────────────────────────────────────────────────
export function u32le(n) {
  const buf = new ArrayBuffer(4);
  new DataView(buf).setUint32(0, n, true);
  return new Uint8Array(buf);
}

export function u64le(n) {
  const buf = new ArrayBuffer(8);
  const view = new DataView(buf);
  view.setUint32(0, n & 0xFFFFFFFF, true);
  view.setUint32(4, Math.floor(n / 0x100000000) & 0xFFFFFFFF, true);
  return new Uint8Array(buf);
}

export function u128le(n) {
  const big = BigInt(n);
  const bytes = new Uint8Array(16);
  let v = big;
  for (let i = 0; i < 16; i++) { bytes[i] = Number(v & 0xFFn); v >>= 8n; }
  return bytes;
}

// ── Keypair helpers ─────────────────────────────────────────────────────────
export function generateKeypair(nacl) {
  const kp = nacl.sign.keyPair();
  return {
    publicKey: b58encode(kp.publicKey),
    secretKey: b58encode(kp.secretKey),
    _raw: kp,
  };
}

export function keypairFromSecret(nacl, secretB58) {
  const secretBytes = b58decode(secretB58);
  let kp;
  if (secretBytes.length === 64)      kp = nacl.sign.keyPair.fromSecretKey(secretBytes);
  else if (secretBytes.length === 32) kp = nacl.sign.keyPair.fromSeed(secretBytes);
  else throw new Error('Invalid secret key length: ' + secretBytes.length);
  return {
    publicKey: b58encode(kp.publicKey),
    secretKey: b58encode(kp.secretKey),
    _raw: kp,
  };
}

/**
 * Resolve keypair from ANY format (base58, hex, JSON array, JSON object).
 */
export function resolveKeypairFromAny(nacl, raw) {
  const s = (typeof raw === 'string') ? raw.trim() : null;

  if (s && (s.startsWith('[') || s.startsWith('{'))) {
    return resolveKeypairFromAny(nacl, JSON.parse(s));
  }

  if (Array.isArray(raw)) {
    const bytes = new Uint8Array(raw);
    if (bytes.length === 64) return nacl.sign.keyPair.fromSecretKey(bytes);
    if (bytes.length === 32) return nacl.sign.keyPair.fromSeed(bytes);
    throw new Error('Byte array must be 32 or 64 bytes, got ' + bytes.length);
  }

  if (raw && typeof raw === 'object') {
    if (raw.keypair)          return resolveKeypairFromAny(nacl, raw.keypair);
    if (raw.secret_key_bytes) return resolveKeypairFromAny(nacl, raw.secret_key_bytes);
    if (raw.secretKey)        return resolveKeypairFromAny(nacl, raw.secretKey);
    if (raw.private_key)      return resolveKeypairFromAny(nacl, raw.private_key);
    throw new Error('Unrecognised keypair object format');
  }

  if (s) {
    if (/^[0-9a-fA-F]{128}$/.test(s)) {
      const bytes = new Uint8Array(s.match(/.{2}/g).map(h => parseInt(h, 16)));
      return nacl.sign.keyPair.fromSecretKey(bytes);
    }
    if (/^[0-9a-fA-F]{64}$/.test(s)) {
      const bytes = new Uint8Array(s.match(/.{2}/g).map(h => parseInt(h, 16)));
      return nacl.sign.keyPair.fromSeed(bytes);
    }
    const bytes = b58decode(s);
    if (bytes.length === 64) return nacl.sign.keyPair.fromSecretKey(bytes);
    if (bytes.length === 32) return nacl.sign.keyPair.fromSeed(bytes);
    throw new Error('base58 must decode to 32 or 64 bytes, got ' + bytes.length);
  }

  throw new Error('Cannot resolve key from provided input');
}

// ── Blake3 (via WCAHT node /api/wallet/blake3 or the node's RPC) ────────────
export async function blake3hash(nodeUrl, msgBytes) {
  // Try dashboard proxy first, then fall back to node RPC
  const urls = [
    'https://dashboard.denvion.com/api/wallet/blake3',
  ];

  for (const url of urls) {
    try {
      const resp = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ data: Array.from(msgBytes) }),
      });
      if (resp.ok) {
        const j = await resp.json();
        return j.hash;
      }
    } catch {}
  }

  // Fallback: use SubtleCrypto SHA-256 as placeholder hash
  // (Node should provide blake3, but this prevents crashes)
  const hashBuf = await crypto.subtle.digest('SHA-256', msgBytes);
  return bytesToHex(new Uint8Array(hashBuf));
}

// ── TX Builder ──────────────────────────────────────────────────────────────
const TX_FEE = 5000;
const COMPUTE_UNITS = 1000;

export async function buildTransaction(nacl, nodeUrl, wallet, toAddress, amountKak) {
  // 1. Get recent blockhash from node
  const bhResp = await fetch(nodeUrl + '/blockchain/recent_blockhash');
  if (!bhResp.ok) throw new Error('Failed to fetch blockhash');
  const bhData = await bhResp.json();
  const recentBlockhash = bhData.recent_blockhash;
  const lastValidSlot = bhData.last_valid_slot;
  const bhBytes = hexToBytes(recentBlockhash);

  // 2. Build TX::v2 canonical bytes
  const ts = Date.now();
  const fromStr = new TextEncoder().encode(wallet.publicKey);
  const toStr = new TextEncoder().encode(toAddress);

  const parts = [];
  parts.push(new TextEncoder().encode('TX::v2|'));
  parts.push(bhBytes);
  parts.push(new TextEncoder().encode('|'));
  parts.push(u32le(fromStr.length));
  parts.push(fromStr);
  parts.push(u32le(toStr.length));
  parts.push(toStr);
  parts.push(u128le(amountKak));
  parts.push(u64le(TX_FEE));
  parts.push(u64le(COMPUTE_UNITS));
  parts.push(u32le(0)); // read_set len
  parts.push(u32le(0)); // write_set len
  parts.push(u32le(0)); // kv_access_list len
  parts.push(u64le(0)); // priority_fee
  parts.push(u64le(lastValidSlot));
  parts.push(u64le(ts));
  parts.push(new Uint8Array([0, 0, 0, 0])); // flags

  // Concatenate
  const totalLen = parts.reduce((s, p) => s + p.length, 0);
  const msg = new Uint8Array(totalLen);
  let offset = 0;
  for (const p of parts) { msg.set(p, offset); offset += p.length; }

  // 3. Sign
  const secretBytes = b58decode(wallet.secretKey);
  let kp;
  if (secretBytes.length === 64) kp = nacl.sign.keyPair.fromSecretKey(secretBytes);
  else kp = nacl.sign.keyPair.fromSeed(secretBytes);

  const signed = nacl.sign.detached(msg, kp.secretKey);
  const signature = b58encode(signed);

  // 4. Blake3 hash
  const txHash = await blake3hash(nodeUrl, msg);

  // 5. Build transaction object
  return {
    recent_blockhash: recentBlockhash,
    last_valid_slot: lastValidSlot,
    from: wallet.publicKey,
    to: toAddress,
    amount: amountKak,
    compute_units: COMPUTE_UNITS,
    fee: TX_FEE,
    signature,
    public_key: wallet.publicKey,
    timestamp: ts,
    tx_hash: txHash,
    transaction_type: 'Transfer',
    processor: null,
    wasm: null,
    priority_fee: 0,
    idempotency_key: null,
    origin: 'Local',
    read_set: [],
    write_set: [],
    kv_access_list: [],
  };
}

export async function submitTransaction(nodeUrl, tx) {
  const rpcBody = {
    jsonrpc: '2.0',
    id: 1,
    method: 'sendTransaction',
    params: [JSON.stringify(tx)],
  };

  const resp = await fetch(nodeUrl + '/rpc', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(rpcBody),
  });
  return resp.json();
}

export async function getBalance(nodeUrl, address) {
  const resp = await fetch(nodeUrl + '/balance/' + address);
  if (!resp.ok) throw new Error('Balance fetch failed');
  const text = await resp.text();
  // Node returns either plain number or JSON {balance: N}
  try {
    const j = JSON.parse(text);
    return j.balance || 0;
  } catch {
    return parseInt(text.trim(), 10) || 0;
  }
}

export async function getNodeStatus(nodeUrl) {
  const resp = await fetch(nodeUrl + '/status');
  if (!resp.ok) throw new Error('Status fetch failed');
  return resp.json();
}
