/**
 * BYOEH Client-Side Encryption Service
 *
 * Provides AES-256-GCM encryption for Erwartungshorizonte.
 * The passphrase NEVER leaves the browser - only the encrypted data
 * and a hash of the derived key are sent to the server.
 *
 * Security Flow:
 * 1. User enters passphrase
 * 2. PBKDF2 derives a 256-bit key from passphrase + random salt
 * 3. AES-256-GCM encrypts the file content
 * 4. SHA-256 hash of derived key is created for server-side verification
 * 5. Encrypted blob + key hash + salt are uploaded (NOT the passphrase!)
 */

export interface EncryptionResult {
  encryptedData: ArrayBuffer
  keyHash: string
  salt: string
  iv: string
}

export interface DecryptionResult {
  decryptedData: ArrayBuffer
  success: boolean
  error?: string
}

/**
 * Convert ArrayBuffer to hex string
 */
function bufferToHex(buffer: Uint8Array): string {
  return Array.from(buffer)
    .map(b => b.toString(16).padStart(2, '0'))
    .join('')
}

/**
 * Convert hex string to ArrayBuffer
 */
function hexToBuffer(hex: string): Uint8Array {
  const bytes = new Uint8Array(hex.length / 2)
  for (let i = 0; i < hex.length; i += 2) {
    bytes[i / 2] = parseInt(hex.substr(i, 2), 16)
  }
  return bytes
}

/**
 * Derive an AES-256 key from passphrase using PBKDF2
 */
async function deriveKey(
  passphrase: string,
  salt: Uint8Array
): Promise<CryptoKey> {
  // Import passphrase as key material
  const keyMaterial = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(passphrase),
    'PBKDF2',
    false,
    ['deriveKey']
  )

  // Derive AES-256-GCM key using PBKDF2
  return crypto.subtle.deriveKey(
    {
      name: 'PBKDF2',
      salt: salt.buffer as ArrayBuffer,
      iterations: 100000, // High iteration count for security
      hash: 'SHA-256'
    },
    keyMaterial,
    { name: 'AES-GCM', length: 256 },
    true, // extractable for hashing
    ['encrypt', 'decrypt']
  )
}

/**
 * Create SHA-256 hash of the derived key for server verification
 */
async function hashKey(key: CryptoKey): Promise<string> {
  const rawKey = await crypto.subtle.exportKey('raw', key)
  const hashBuffer = await crypto.subtle.digest('SHA-256', rawKey)
  return bufferToHex(new Uint8Array(hashBuffer))
}

/**
 * Encrypt a file using AES-256-GCM
 *
 * @param file - File to encrypt
 * @param passphrase - User's passphrase (never sent to server)
 * @returns Encrypted data + metadata for upload
 */
export async function encryptFile(
  file: File,
  passphrase: string
): Promise<EncryptionResult> {
  // 1. Generate random 16-byte salt
  const salt = crypto.getRandomValues(new Uint8Array(16))

  // 2. Derive key from passphrase
  const key = await deriveKey(passphrase, salt)

  // 3. Generate random 12-byte IV (required for AES-GCM)
  const iv = crypto.getRandomValues(new Uint8Array(12))

  // 4. Read file content
  const fileBuffer = await file.arrayBuffer()

  // 5. Encrypt using AES-256-GCM
  const encrypted = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    fileBuffer
  )

  // 6. Create key hash for server-side verification
  const keyHash = await hashKey(key)

  // 7. Combine IV + ciphertext (IV is needed for decryption)
  const combined = new Uint8Array(iv.length + encrypted.byteLength)
  combined.set(iv, 0)
  combined.set(new Uint8Array(encrypted), iv.length)

  return {
    encryptedData: combined.buffer,
    keyHash,
    salt: bufferToHex(salt),
    iv: bufferToHex(iv)
  }
}

/**
 * Encrypt text content using AES-256-GCM
 *
 * @param text - Text to encrypt
 * @param passphrase - User's passphrase
 * @param saltHex - Salt as hex string
 * @returns Base64-encoded encrypted content
 */
export async function encryptText(
  text: string,
  passphrase: string,
  saltHex: string
): Promise<string> {
  const salt = hexToBuffer(saltHex)
  const key = await deriveKey(passphrase, salt)
  const iv = crypto.getRandomValues(new Uint8Array(12))

  const encrypted = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    new TextEncoder().encode(text)
  )

  // Combine IV + ciphertext
  const combined = new Uint8Array(iv.length + encrypted.byteLength)
  combined.set(iv, 0)
  combined.set(new Uint8Array(encrypted), iv.length)

  // Return as base64
  return btoa(String.fromCharCode(...combined))
}

/**
 * Decrypt text content using AES-256-GCM
 *
 * @param encryptedBase64 - Base64-encoded encrypted content (IV + ciphertext)
 * @param passphrase - User's passphrase
 * @param saltHex - Salt as hex string
 * @returns Decrypted text
 */
export async function decryptText(
  encryptedBase64: string,
  passphrase: string,
  saltHex: string
): Promise<string> {
  const salt = hexToBuffer(saltHex)
  const key = await deriveKey(passphrase, salt)

  // Decode base64
  const combined = Uint8Array.from(atob(encryptedBase64), c => c.charCodeAt(0))

  // Extract IV (first 12 bytes) and ciphertext
  const iv = combined.slice(0, 12)
  const ciphertext = combined.slice(12)

  // Decrypt
  const decrypted = await crypto.subtle.decrypt(
    { name: 'AES-GCM', iv },
    key,
    ciphertext
  )

  return new TextDecoder().decode(decrypted)
}

/**
 * Decrypt a file using AES-256-GCM
 *
 * @param encryptedData - Encrypted data (IV + ciphertext)
 * @param passphrase - User's passphrase
 * @param saltHex - Salt as hex string
 * @returns Decrypted file content
 */
export async function decryptFile(
  encryptedData: ArrayBuffer,
  passphrase: string,
  saltHex: string
): Promise<DecryptionResult> {
  try {
    const salt = hexToBuffer(saltHex)
    const key = await deriveKey(passphrase, salt)

    const combined = new Uint8Array(encryptedData)

    // Extract IV (first 12 bytes) and ciphertext
    const iv = combined.slice(0, 12)
    const ciphertext = combined.slice(12)

    // Decrypt
    const decrypted = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv },
      key,
      ciphertext
    )

    return {
      decryptedData: decrypted,
      success: true
    }
  } catch (error) {
    return {
      decryptedData: new ArrayBuffer(0),
      success: false,
      error: error instanceof Error ? error.message : 'Decryption failed'
    }
  }
}

/**
 * Verify a passphrase against stored key hash
 *
 * @param passphrase - Passphrase to verify
 * @param saltHex - Salt as hex string
 * @param expectedHash - Expected key hash
 * @returns true if passphrase is correct
 */
export async function verifyPassphrase(
  passphrase: string,
  saltHex: string,
  expectedHash: string
): Promise<boolean> {
  try {
    const salt = hexToBuffer(saltHex)
    const key = await deriveKey(passphrase, salt)
    const computedHash = await hashKey(key)
    return computedHash === expectedHash
  } catch {
    return false
  }
}

/**
 * Generate a key hash for a given passphrase and salt
 * Used when creating a new encrypted document
 *
 * @param passphrase - User's passphrase
 * @param saltHex - Salt as hex string
 * @returns Key hash for storage
 */
export async function generateKeyHash(
  passphrase: string,
  saltHex: string
): Promise<string> {
  const salt = hexToBuffer(saltHex)
  const key = await deriveKey(passphrase, salt)
  return hashKey(key)
}

/**
 * Generate a random salt for encryption
 *
 * @returns 16-byte salt as hex string
 */
export function generateSalt(): string {
  const salt = crypto.getRandomValues(new Uint8Array(16))
  return bufferToHex(salt)
}

/**
 * Check if Web Crypto API is available
 */
export function isEncryptionSupported(): boolean {
  return typeof crypto !== 'undefined' && typeof crypto.subtle !== 'undefined'
}
