/**
 * Web Crypto API implementation of the platform crypto interface.
 * Compatible with the Electron (Node.js crypto) implementation:
 * - RSA-OAEP 2048 with SHA-256 for public key encryption
 * - Ed25519 for message signing/verification
 * - AES-256-GCM for symmetric encryption
 * - scrypt for password-based key derivation (via scrypt-js)
 * - SHA-256 for hashing
 *
 * Keys are exchanged as PEM strings (SPKI public, PKCS8 private)
 * to maintain interoperability with the Electron app.
 */
import { scrypt as scryptAsync } from 'scrypt-js';

const encoder = new TextEncoder();
const decoder = new TextDecoder();

// --- Hex / ArrayBuffer helpers ---

function bufToHex(buf) {
  return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
}

function hexToBuf(hex) {
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < hex.length; i += 2) {
    bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
  }
  return bytes.buffer;
}

// --- PEM encode / decode ---

function pemEncode(der, type) {
  const b64 = btoa(String.fromCharCode(...new Uint8Array(der)));
  const lines = b64.match(/.{1,64}/g) || [];
  return `-----BEGIN ${type}-----\n${lines.join('\n')}\n-----END ${type}-----`;
}

function pemDecode(pem) {
  const b64 = pem.replace(/-----[A-Z ]+-----/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.buffer;
}

// --- Key Generation ---

async function generateKeys() {
  // Generate RSA-OAEP 2048 key pair
  const rsaKeyPair = await crypto.subtle.generateKey(
    { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },
    true,
    ['encrypt', 'decrypt']
  );
  const rsaPubDer = await crypto.subtle.exportKey('spki', rsaKeyPair.publicKey);
  const rsaPrivDer = await crypto.subtle.exportKey('pkcs8', rsaKeyPair.privateKey);

  // Generate Ed25519 key pair
  const edKeyPair = await crypto.subtle.generateKey('Ed25519', true, ['sign', 'verify']);
  const edPubDer = await crypto.subtle.exportKey('spki', edKeyPair.publicKey);
  const edPrivDer = await crypto.subtle.exportKey('pkcs8', edKeyPair.privateKey);

  return {
    rsaPub: pemEncode(rsaPubDer, 'PUBLIC KEY'),
    rsaPriv: pemEncode(rsaPrivDer, 'PRIVATE KEY'),
    edPub: pemEncode(edPubDer, 'PUBLIC KEY'),
    edPriv: pemEncode(edPrivDer, 'PRIVATE KEY'),
  };
}

// --- Random bytes (returns hex string) ---

function randomBytes(size) {
  const bytes = new Uint8Array(size);
  crypto.getRandomValues(bytes);
  return bufToHex(bytes);
}

// --- SHA-256 (returns hex string) ---

async function sha256(data) {
  const digest = await crypto.subtle.digest('SHA-256', encoder.encode(data));
  return bufToHex(digest);
}

// --- Ed25519 Sign / Verify ---

async function signMessage(privateKeyPem, message) {
  const keyData = pemDecode(privateKeyPem);
  const key = await crypto.subtle.importKey('pkcs8', keyData, 'Ed25519', false, ['sign']);
  const sig = await crypto.subtle.sign('Ed25519', key, encoder.encode(message));
  return bufToHex(sig);
}

async function verifySignature(publicKeyPem, message, signatureHex) {
  const keyData = pemDecode(publicKeyPem);
  const key = await crypto.subtle.importKey('spki', keyData, 'Ed25519', false, ['verify']);
  const sigBuf = hexToBuf(signatureHex);
  return crypto.subtle.verify('Ed25519', key, sigBuf, encoder.encode(message));
}

// --- scrypt Key Derivation ---

async function deriveAuthKeys(password, salt) {
  const passwordBuf = encoder.encode(password);
  const saltBuf = encoder.encode(salt);

  // Match Node.js scrypt params: keylen=64, N=16384, r=8, p=1 (Node defaults)
  const N = 16384, r = 8, p = 1, dkLen = 64;
  const derivedKey = await scryptAsync(passwordBuf, saltBuf, N, r, p, dkLen);

  const dak = bufToHex(derivedKey.slice(0, 32));
  const dek = derivedKey.slice(32, 64);
  return { dak, dek };
}

// --- AES-256-GCM Encrypt ---

async function encryptData(plaintext, keyInput) {
  const keyBuf = typeof keyInput === 'string' ? hexToBuf(keyInput) : keyInput;
  const key = await crypto.subtle.importKey('raw', keyBuf, 'AES-GCM', false, ['encrypt']);
  const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit IV

  // Encode plaintext as UTF-8 if string
  const dataBuf = typeof plaintext === 'string' ? encoder.encode(plaintext) : new Uint8Array(plaintext);
  const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv, tagLength: 128 }, key, dataBuf);

  // Web Crypto appends the auth tag (16 bytes) to the ciphertext
  const encBytes = new Uint8Array(encrypted);
  const ciphertext = encBytes.slice(0, encBytes.length - 16);
  const tag = encBytes.slice(encBytes.length - 16);

  return {
    content: bufToHex(ciphertext),
    iv: bufToHex(iv),
    tag: bufToHex(tag),
  };
}

// --- AES-256-GCM Decrypt ---

async function decryptData(ciphertextHex, keyInput, ivHex, tagHex, options = {}) {
  const keyBuf = typeof keyInput === 'string' ? hexToBuf(keyInput) : keyInput;
  const key = await crypto.subtle.importKey('raw', keyBuf, 'AES-GCM', false, ['decrypt']);
  const iv = new Uint8Array(hexToBuf(ivHex));

  // Web Crypto expects ciphertext + tag concatenated
  const cipherBytes = new Uint8Array(hexToBuf(ciphertextHex));
  const tagBytes = new Uint8Array(hexToBuf(tagHex));
  const combined = new Uint8Array(cipherBytes.length + tagBytes.length);
  combined.set(cipherBytes, 0);
  combined.set(tagBytes, cipherBytes.length);

  const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv, tagLength: 128 }, key, combined);

  if (options.encoding === 'buffer') {
    return decrypted;
  }
  return decoder.decode(decrypted);
}

// --- Batch Decrypt ---

async function decryptBatch(items) {
  return Promise.all(items.map(async ({ ciphertext, key, iv, tag }) => {
    try {
      const data = await decryptData(ciphertext, key, iv, tag);
      return { success: true, data };
    } catch {
      return { success: false, data: null };
    }
  }));
}

// --- Ed25519 Support Detection ---

let ed25519Supported = null;
async function checkEd25519Support() {
  if (ed25519Supported !== null) return ed25519Supported;
  try {
    await crypto.subtle.generateKey('Ed25519', false, ['sign', 'verify']);
    ed25519Supported = true;
  } catch {
    ed25519Supported = false;
  }
  return ed25519Supported;
}

// --- Batch Verify ---

async function verifyBatch(items) {
  const supported = await checkEd25519Support();
  if (!supported) {
    return items.map(() => ({ success: true, verified: null }));
  }
  return Promise.all(items.map(async ({ publicKey, message, signature }) => {
    try {
      const verified = await verifySignature(publicKey, message, signature);
      return { success: true, verified };
    } catch {
      return { success: false, verified: false };
    }
  }));
}

// --- RSA-OAEP Public Encrypt ---

async function publicEncrypt(publicKeyPem, data) {
  const keyData = pemDecode(publicKeyPem);
  const key = await crypto.subtle.importKey('spki', keyData, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['encrypt']);
  const dataBuf = typeof data === 'string' ? encoder.encode(data) : new Uint8Array(data);
  const encrypted = await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, key, dataBuf);
  return bufToHex(encrypted);
}

// --- RSA-OAEP Private Decrypt ---

async function privateDecrypt(privateKeyPem, encryptedHex) {
  const keyData = pemDecode(privateKeyPem);
  const key = await crypto.subtle.importKey('pkcs8', keyData, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['decrypt']);
  const encBuf = hexToBuf(encryptedHex);
  const decrypted = await crypto.subtle.decrypt({ name: 'RSA-OAEP' }, key, encBuf);
  return decoder.decode(decrypted);
}

export default {
  generateKeys,
  randomBytes,
  sha256,
  signMessage,
  verifySignature,
  deriveAuthKeys,
  encryptData,
  decryptData,
  decryptBatch,
  verifyBatch,
  publicEncrypt,
  privateDecrypt,
};
