/**
 * RSA-OAEP-SHA256 encryption using Web Crypto API.
 * Encrypts plaintext with a PEM-encoded RSA public key and returns base64 ciphertext.
 * Supports both PKCS#1 (-----BEGIN RSA PUBLIC KEY-----) and SPKI (-----BEGIN PUBLIC KEY-----) PEM formats.
 */

function pemToDer(pem: string): Uint8Array {
  const b64 = pem.replace(/-----[^-]+-----/g, '').replace(/\s/g, '');
  const binary = atob(b64);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes;
}

function derLength(len: number): number[] {
  if (len < 128) return [len];
  const bytes: number[] = [];
  let remaining = len;
  while (remaining > 0) {
    bytes.unshift(remaining & 0xff);
    remaining >>>= 8;
  }
  return [0x80 | bytes.length, ...bytes];
}

function derEncode(tag: number, content: number[]): number[] {
  const len = derLength(content.length);
  return [tag, ...len, ...content];
}

/**
 * Convert a PKCS#1 DER-encoded RSA public key to SPKI (SubjectPublicKeyInfo) DER format.
 * PKCS#1: SEQUENCE { modulus INTEGER, exponent INTEGER }
 * SPKI:   SEQUENCE { AlgorithmIdentifier SEQUENCE { OID 1.2.840.113549.1.1.1, NULL }, BIT STRING { 0x00, PKCS#1 DER } }
 */
function pkcs1DerToSpkiDer(pkcs1Der: Uint8Array): Uint8Array {
  const rsaOid = [0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01];
  const nullParam = [0x05, 0x00];
  const algIdContent = [...rsaOid, ...nullParam];
  const algId = derEncode(0x30, algIdContent);

  const bitStringContent = [0x00, ...Array.from(pkcs1Der)];
  const bitString = derEncode(0x03, bitStringContent);

  const spkiContent = [...algId, ...bitString];
  return new Uint8Array(derEncode(0x30, spkiContent));
}

function isPkcs1Pem(pem: string): boolean {
  return pem.includes('RSA PUBLIC KEY');
}

export async function encryptRsaOaep(plaintext: string, publicKeyPem: string): Promise<string> {
  let der: Uint8Array;

  if (isPkcs1Pem(publicKeyPem)) {
    const pkcs1Der = pemToDer(publicKeyPem);
    der = pkcs1DerToSpkiDer(pkcs1Der);
  } else {
    der = pemToDer(publicKeyPem);
  }

  const publicKey = await crypto.subtle.importKey(
    'spki',
    der.buffer as ArrayBuffer,
    { name: 'RSA-OAEP', hash: 'SHA-256' },
    false,
    ['encrypt'],
  );
  const encoded = new TextEncoder().encode(plaintext);
  const encrypted = await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, publicKey, encoded);
  return btoa(String.fromCharCode(...new Uint8Array(encrypted)));
}
