const crypto = require('crypto');

const ENVELOPE_SCHEMA = 'plais-validator-key-envelope/v1';
const KEY_ALGORITHM = 'rsa-oaep-sha256-dpapi-current-user-v1';

function stableValue(value) {
  if (Array.isArray(value)) return value.map(stableValue);
  if (value && typeof value === 'object') {
    return Object.keys(value).sort().reduce((out, key) => {
      out[key] = stableValue(value[key]);
      return out;
    }, {});
  }
  return value;
}

function stableBuffer(value) {
  return Buffer.from(JSON.stringify(stableValue(value)), 'utf8');
}

function parseDevicePublicKey(publicKeyBase64, expectedKeyId) {
  const der = Buffer.from(String(publicKeyBase64 || '').trim(), 'base64');
  const keyId = crypto.createHash('sha256').update(der).digest('hex');
  const publicKey = crypto.createPublicKey({ key: der, format: 'der', type: 'spki' });
  if (publicKey.asymmetricKeyType !== 'rsa' || Number(publicKey.asymmetricKeyDetails?.modulusLength || 0) < 3072) {
    throw new Error('Device provisioning public key must be RSA-3072 or stronger.');
  }
  if (!/^[a-f0-9]{64}$/.test(String(expectedKeyId || '')) || keyId !== expectedKeyId) {
    throw new Error('Device provisioning public key ID mismatch.');
  }
  return { publicKey, keyId };
}

function normalizeValidatorPrivateKey(value) {
  const normalized = String(value || '').trim().replace(/^0x/, '').toLowerCase();
  if (!/^[a-f0-9]{64}$/.test(normalized)) throw new Error('Validator private key format is invalid.');
  return normalized;
}

function encryptValidatorKeyEnvelope({ validatorPrivateKey, publicKeyBase64, deviceKeyId, context = {} }) {
  const privateKey = normalizeValidatorPrivateKey(validatorPrivateKey);
  const { publicKey, keyId } = parseDevicePublicKey(publicKeyBase64, deviceKeyId);
  const envelopeContext = stableValue({
    schemaVersion: ENVELOPE_SCHEMA,
    deviceKeyAlgorithm: KEY_ALGORITHM,
    deviceKeyId: keyId,
    ...context
  });
  const aad = stableBuffer(envelopeContext);
  const plaintext = Buffer.from(privateKey, 'utf8');
  const contentKey = crypto.randomBytes(32);
  const iv = crypto.randomBytes(12);
  try {
    const cipher = crypto.createCipheriv('aes-256-gcm', contentKey, iv);
    cipher.setAAD(aad);
    const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
    const authTag = cipher.getAuthTag();
    const wrappedContentKey = crypto.publicEncrypt({
      key: publicKey,
      oaepHash: 'sha256',
      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
    }, contentKey);
    return {
      schemaVersion: ENVELOPE_SCHEMA,
      encryption: 'aes-256-gcm+rsa-oaep-sha256',
      deviceKeyAlgorithm: KEY_ALGORITHM,
      deviceKeyId: keyId,
      context: envelopeContext,
      wrappedContentKey: wrappedContentKey.toString('base64'),
      iv: iv.toString('base64'),
      authTag: authTag.toString('base64'),
      ciphertext: ciphertext.toString('base64'),
      ciphertextSha256: crypto.createHash('sha256').update(ciphertext).digest('hex'),
      createdAt: new Date().toISOString()
    };
  } finally {
    plaintext.fill(0);
    contentKey.fill(0);
  }
}

function decryptValidatorKeyEnvelopeForTest(envelope, privateKey) {
  const aad = stableBuffer(envelope.context || {});
  const contentKey = crypto.privateDecrypt({
    key: privateKey,
    oaepHash: 'sha256',
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
  }, Buffer.from(envelope.wrappedContentKey, 'base64'));
  try {
    const decipher = crypto.createDecipheriv('aes-256-gcm', contentKey, Buffer.from(envelope.iv, 'base64'));
    decipher.setAAD(aad);
    decipher.setAuthTag(Buffer.from(envelope.authTag, 'base64'));
    return Buffer.concat([
      decipher.update(Buffer.from(envelope.ciphertext, 'base64')),
      decipher.final()
    ]).toString('utf8');
  } finally {
    contentKey.fill(0);
  }
}

module.exports = {
  ENVELOPE_SCHEMA,
  KEY_ALGORITHM,
  decryptValidatorKeyEnvelopeForTest,
  encryptValidatorKeyEnvelope,
  normalizeValidatorPrivateKey,
  parseDevicePublicKey,
  stableValue
};
