/**
 * EOA Key Derivation from WebAuthn PRF Output
 *
 * Derives a deterministic secp256k1 EOA keypair from the passkey's PRF output
 * using HKDF-SHA256. The derived EOA becomes an owner on the user's Gnosis Safe,
 * bridging passkey-based identity to EVM wallet operations.
 *
 * The key is:
 * - Deterministic: same passkey always produces the same EOA address
 * - Never stored: derived on-demand in the browser from PRF output
 * - Domain-separated: different HKDF salt/info from encryption/signing/DID keys
 */

import { secp256k1 } from '@noble/curves/secp256k1';
import { hkdf } from '@noble/hashes/hkdf';
import { sha256 } from '@noble/hashes/sha256';
import { keccak_256 } from '@noble/hashes/sha3';

/**
 * Derive a secp256k1 EOA private key from PRF output using HKDF-SHA256.
 *
 * Uses domain-separated salt/info to ensure this key is independent from
 * the encryption, signing, and DID keys derived from the same PRF output.
 *
 * @param prfOutput - Raw PRF output from WebAuthn (typically 32 bytes)
 * @returns Object with privateKey (Uint8Array), publicKey (Uint8Array), and address (checksummed hex)
 */
export function deriveEOAFromPRF(prfOutput: Uint8Array): {
  privateKey: Uint8Array;
  publicKey: Uint8Array;
  address: string;
} {
  const encoder = new TextEncoder();

  // HKDF-SHA256 with domain-specific salt and info
  // These are deliberately different from the existing derivation salts:
  //   encryption: 'encryptid-encryption-key-v1' / 'AES-256-GCM'
  //   signing:    'encryptid-signing-key-v1'     / 'ECDSA-P256-seed'
  //   DID:        'encryptid-did-key-v1'         / 'Ed25519-seed'
  const privateKey = hkdf(
    sha256,
    prfOutput,
    encoder.encode('encryptid-eoa-key-v1'),        // salt
    encoder.encode('secp256k1-signing-key'),        // info
    32                                               // 32 bytes for secp256k1
  );

  // Validate the private key is in the valid range for secp256k1
  // (between 1 and curve order - 1). HKDF output is uniformly random
  // so this is astronomically unlikely to fail, but we check anyway.
  if (!isValidPrivateKey(privateKey)) {
    throw new Error('Derived EOA private key is outside valid secp256k1 range');
  }

  // Derive public key (uncompressed, 65 bytes: 04 || x || y)
  const publicKeyUncompressed = secp256k1.getPublicKey(privateKey, false);

  // Ethereum address = last 20 bytes of keccak256(publicKey[1:])
  // Skip the 0x04 prefix byte
  const publicKeyBytes = publicKeyUncompressed.slice(1);
  const hash = keccak_256(publicKeyBytes);
  const addressBytes = hash.slice(12); // last 20 bytes

  const address = toChecksumAddress(addressBytes);

  return { privateKey, publicKey: publicKeyUncompressed, address };
}

/**
 * Check if a 32-byte value is a valid secp256k1 private key.
 */
function isValidPrivateKey(key: Uint8Array): boolean {
  try {
    secp256k1.getPublicKey(key);
    return true;
  } catch {
    return false;
  }
}

/**
 * Convert raw address bytes to EIP-55 checksummed hex address.
 */
function toChecksumAddress(addressBytes: Uint8Array): string {
  const hex = Array.from(addressBytes)
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');

  const hashHex = Array.from(keccak_256(new TextEncoder().encode(hex)))
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');

  let checksummed = '0x';
  for (let i = 0; i < 40; i++) {
    checksummed += parseInt(hashHex[i], 16) >= 8
      ? hex[i].toUpperCase()
      : hex[i];
  }

  return checksummed;
}

/**
 * Zero out a private key buffer.
 * Call this after signing to minimize exposure window.
 */
export function zeroPrivateKey(key: Uint8Array): void {
  key.fill(0);
}
