/**
 * Key Management
 * @countermeasure/web-components
 *
 * Key generation, import/export, and derivation utilities.
 * Compatible with FluxEncrypt's key formats.
 */

import { toBase64, fromBase64, toHex, fromHex } from './encoding'
import { CryptoError, type KeyPair, type DerivedKeyResult, type KeyDerivationConfig } from './types'

// ============================================================================
// CONSTANTS
// ============================================================================

const AES_KEY_LENGTH = 256
const AES_KEY_BYTES = AES_KEY_LENGTH / 8
const IV_LENGTH = 12
const DEFAULT_PBKDF2_ITERATIONS = 100000
const DEFAULT_SALT_LENGTH = 16

// ============================================================================
// SYMMETRIC KEY GENERATION
// ============================================================================

/**
 * Generate a random AES-256 key.
 * Returns the key as a hex string (64 characters).
 */
export async function generateSymmetricKey(): Promise<string> {
  try {
    const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: AES_KEY_LENGTH }, true, [
      'encrypt',
      'decrypt',
    ])

    const rawKey = await crypto.subtle.exportKey('raw', key)
    return toHex(new Uint8Array(rawKey))
  } catch (error) {
    throw new CryptoError('Failed to generate symmetric key', 'KEY_GENERATION_FAILED', error)
  }
}

/**
 * Import a symmetric key from a hex string.
 */
export async function importSymmetricKey(hexKey: string): Promise<CryptoKey> {
  try {
    const keyBytes = fromHex(hexKey)

    if (keyBytes.length !== AES_KEY_BYTES) {
      throw new CryptoError(
        `Invalid key length: expected ${String(AES_KEY_BYTES)} bytes, got ${String(keyBytes.length)}`,
        'INVALID_KEY_LENGTH'
      )
    }

    return await crypto.subtle.importKey(
      'raw',
      keyBytes as unknown as Uint8Array<ArrayBuffer>,
      { name: 'AES-GCM', length: AES_KEY_LENGTH },
      false,
      ['encrypt', 'decrypt']
    )
  } catch (error) {
    if (error instanceof CryptoError) {
      throw error
    }
    throw new CryptoError('Failed to import symmetric key', 'KEY_IMPORT_FAILED', error)
  }
}

/**
 * Generate a random initialization vector (nonce) for AES-GCM.
 * Returns 12 bytes as required by GCM.
 */
export function generateIV(): Uint8Array {
  return crypto.getRandomValues(new Uint8Array(IV_LENGTH))
}

/**
 * Generate random bytes.
 */
export function randomBytes(length: number): Uint8Array {
  return crypto.getRandomValues(new Uint8Array(length))
}

// ============================================================================
// ASYMMETRIC KEY GENERATION (RSA)
// ============================================================================

/**
 * Generate an RSA key pair for hybrid encryption.
 * Uses RSA-OAEP with SHA-256, compatible with FluxEncrypt.
 */
export async function generateKeyPair(keySize: 2048 | 4096 = 4096): Promise<KeyPair> {
  try {
    const keyPair = await crypto.subtle.generateKey(
      {
        name: 'RSA-OAEP',
        modulusLength: keySize,
        publicExponent: new Uint8Array([1, 0, 1]), // 65537
        hash: 'SHA-256',
      },
      true,
      ['encrypt', 'decrypt']
    )

    return {
      publicKey: keyPair.publicKey,
      privateKey: keyPair.privateKey,
    }
  } catch (error) {
    throw new CryptoError('Failed to generate RSA key pair', 'KEY_GENERATION_FAILED', error)
  }
}

// ============================================================================
// KEY EXPORT (PEM FORMAT)
// ============================================================================

/**
 * Export a public key to PEM format (SPKI).
 */
export async function exportPublicKey(key: CryptoKey): Promise<string> {
  try {
    const spki = await crypto.subtle.exportKey('spki', key)
    const base64 = toBase64(new Uint8Array(spki))
    return formatPem(base64, 'PUBLIC KEY')
  } catch (error) {
    throw new CryptoError('Failed to export public key', 'KEY_EXPORT_FAILED', error)
  }
}

/**
 * Export a private key to PEM format (PKCS8).
 */
export async function exportPrivateKey(key: CryptoKey): Promise<string> {
  try {
    const pkcs8 = await crypto.subtle.exportKey('pkcs8', key)
    const base64 = toBase64(new Uint8Array(pkcs8))
    return formatPem(base64, 'PRIVATE KEY')
  } catch (error) {
    throw new CryptoError('Failed to export private key', 'KEY_EXPORT_FAILED', error)
  }
}

/**
 * Format base64 data as PEM with proper line breaks.
 */
function formatPem(base64: string, label: string): string {
  const lines: string[] = []
  for (let i = 0; i < base64.length; i += 64) {
    lines.push(base64.slice(i, i + 64))
  }
  return `-----BEGIN ${label}-----\n${lines.join('\n')}\n-----END ${label}-----`
}

// ============================================================================
// KEY IMPORT (PEM FORMAT)
// ============================================================================

/**
 * Import a public key from PEM format.
 */
export async function importPublicKey(pem: string): Promise<CryptoKey> {
  try {
    const base64 = parsePem(pem, 'PUBLIC KEY')
    const spki = fromBase64(base64)

    return await crypto.subtle.importKey(
      'spki',
      spki as unknown as Uint8Array<ArrayBuffer>,
      {
        name: 'RSA-OAEP',
        hash: 'SHA-256',
      },
      true,
      ['encrypt']
    )
  } catch (error) {
    if (error instanceof CryptoError) {
      throw error
    }
    throw new CryptoError('Failed to import public key', 'KEY_IMPORT_FAILED', error)
  }
}

/**
 * Import a private key from PEM format.
 */
export async function importPrivateKey(pem: string): Promise<CryptoKey> {
  try {
    const base64 = parsePem(pem, 'PRIVATE KEY')
    const pkcs8 = fromBase64(base64)

    return await crypto.subtle.importKey(
      'pkcs8',
      pkcs8 as unknown as Uint8Array<ArrayBuffer>,
      {
        name: 'RSA-OAEP',
        hash: 'SHA-256',
      },
      true,
      ['decrypt']
    )
  } catch (error) {
    if (error instanceof CryptoError) {
      throw error
    }
    throw new CryptoError('Failed to import private key', 'KEY_IMPORT_FAILED', error)
  }
}

/**
 * Parse PEM format and extract base64 content.
 */
function parsePem(pem: string, expectedLabel: string): string {
  const beginMarker = `-----BEGIN ${expectedLabel}-----`
  const endMarker = `-----END ${expectedLabel}-----`

  const beginIndex = pem.indexOf(beginMarker)
  const endIndex = pem.indexOf(endMarker)

  if (beginIndex === -1 || endIndex === -1) {
    throw new CryptoError(`Invalid PEM format: missing ${expectedLabel} markers`, 'INVALID_KEY')
  }

  const base64Content = pem.slice(beginIndex + beginMarker.length, endIndex).replace(/\s/g, '')

  return base64Content
}

// ============================================================================
// KEY DERIVATION (PBKDF2)
// ============================================================================

/**
 * Derive a key from a password using PBKDF2.
 * Compatible with FluxEncrypt's key derivation.
 */
export async function deriveKeyFromPassword(
  password: string,
  salt?: Uint8Array,
  config: KeyDerivationConfig = {}
): Promise<DerivedKeyResult> {
  const {
    iterations = DEFAULT_PBKDF2_ITERATIONS,
    saltLength = DEFAULT_SALT_LENGTH,
    hash = 'SHA-256',
  } = config

  try {
    // Generate salt if not provided
    const derivationSalt = salt ?? randomBytes(saltLength)

    // Import password as key material
    const passwordKey = await crypto.subtle.importKey(
      'raw',
      new TextEncoder().encode(password),
      'PBKDF2',
      false,
      ['deriveBits', 'deriveKey']
    )

    // Derive the key
    const derivedKey = await crypto.subtle.deriveKey(
      {
        name: 'PBKDF2',
        salt: derivationSalt as unknown as Uint8Array<ArrayBuffer>,
        iterations,
        hash,
      },
      passwordKey,
      { name: 'AES-GCM', length: AES_KEY_LENGTH },
      true,
      ['encrypt', 'decrypt']
    )

    return {
      key: derivedKey,
      salt: derivationSalt,
    }
  } catch (error) {
    throw new CryptoError('Failed to derive key from password', 'KEY_DERIVATION_FAILED', error)
  }
}

/**
 * Export a derived key as a hex string.
 */
export async function exportDerivedKey(key: CryptoKey): Promise<string> {
  try {
    const rawKey = await crypto.subtle.exportKey('raw', key)
    return toHex(new Uint8Array(rawKey))
  } catch (error) {
    throw new CryptoError('Failed to export derived key', 'KEY_EXPORT_FAILED', error)
  }
}
