/**
 * Client-side encryption utilities
 * 
 * Zero-knowledge privacy flow:
 * 1. KDF derives master key from firebaseUID + firebaseIDToken
 * 2. Device key stored in localStorage
 * 3. Master key encrypted with device key → stored in IndexedDB
 * 4. Journal entries encrypted with master key
 * 5. Only ciphertext sent to server
 */

import { getSodium } from '../utils/sodium'

/**
 * Derive master encryption key from Firebase credentials using PBKDF2
 * 
 * Flow:
 * - Input: firebaseUID + firebaseIDToken + constant salt
 * - Output: 32-byte key for encryption
 */
export async function deriveSecretKey(
  firebaseUID: string,
  salt: string
): Promise<Uint8Array> {
  // Use native Web Crypto API for key derivation (PBKDF2)
  // Derives from UID only — stable across sessions
  const password = firebaseUID
  const encoding = new TextEncoder()
  const passwordBuffer = encoding.encode(password)
  const saltBuffer = encoding.encode(salt)

  // Import the password as a key
  const baseKey = await crypto.subtle.importKey(
    'raw',
    passwordBuffer,
    { name: 'PBKDF2' },
    false,
    ['deriveBits']
  )

  // Derive key using PBKDF2-SHA256
  const derivedBits = await crypto.subtle.deriveBits(
    {
      name: 'PBKDF2',
      salt: saltBuffer,
      iterations: 100000,
      hash: 'SHA-256',
    },
    baseKey,
    256 // 256 bits = 32 bytes
  )

  return new Uint8Array(derivedBits)
}

/**
 * Generate device key (256 bits) for encrypting the master key
 * Stored in localStorage, persists across sessions on same device
 */
export async function generateDeviceKey(): Promise<Uint8Array> {
  // Use native crypto.getRandomValues for device key generation
  // This is safe because device key doesn't need libsodium
  const deviceKey = new Uint8Array(32) // 256 bits
  crypto.getRandomValues(deviceKey)
  return deviceKey
}

/**
 * Encrypt master key with device key for storage
 * Result stored in IndexedDB
 */
export async function encryptSecretKey(
  secretKey: Uint8Array,
  deviceKey: Uint8Array
): Promise<{
  ciphertext: string
  nonce: string
}> {
  const sodium = await getSodium()
  
  const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES)
  const ciphertext = sodium.crypto_secretbox_easy(secretKey, nonce, deviceKey)

  return {
    ciphertext: sodium.to_base64(ciphertext),
    nonce: sodium.to_base64(nonce),
  }
}

/**
 * Decrypt master key using device key
 * Retrieves encrypted key from IndexedDB and decrypts with device key
 */
export async function decryptSecretKey(
  ciphertext: string,
  nonce: string,
  deviceKey: Uint8Array
): Promise<Uint8Array> {
  const sodium = await getSodium()

  const ciphertextBytes = sodium.from_base64(ciphertext)
  const nonceBytes = sodium.from_base64(nonce)

  try {
    return sodium.crypto_secretbox_open_easy(ciphertextBytes, nonceBytes, deviceKey)
  } catch {
    throw new Error('Failed to decrypt secret key - device key mismatch or corrupted data')
  }
}

/**
 * Encrypt journal entry content
 * Used before sending to server
 * Converts string content to Uint8Array before encryption
 */
export async function encryptEntry(
  entryContent: string,
  secretKey: Uint8Array
): Promise<{
  ciphertext: string
  nonce: string
}> {
  const sodium = await getSodium()

  const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES)
  const contentBytes = sodium.from_string(entryContent)
  const ciphertext = sodium.crypto_secretbox_easy(contentBytes, nonce, secretKey)

  return {
    ciphertext: sodium.to_base64(ciphertext),
    nonce: sodium.to_base64(nonce),
  }
}

/**
 * Decrypt journal entry content
 * Used when fetching from server
 */
export async function decryptEntry(
  ciphertext: string,
  nonce: string,
  secretKey: Uint8Array
): Promise<string> {
  const sodium = await getSodium()

  const ciphertextBytes = sodium.from_base64(ciphertext)
  const nonceBytes = sodium.from_base64(nonce)

  try {
    const plaintext = sodium.crypto_secretbox_open_easy(ciphertextBytes, nonceBytes, secretKey)
    return sodium.to_string(plaintext)
  } catch {
    throw new Error('Failed to decrypt entry - corrupted data or wrong key')
  }
}

/**
 * IndexedDB operations for storing encrypted secret key
 */
const DB_NAME = 'GratefulJournal'
const DB_VERSION = 1
const STORE_NAME = 'encryption'

export async function initializeIndexedDB(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION)

    request.onerror = () => reject(request.error)
    request.onsuccess = () => resolve(request.result)

    request.onupgradeneeded = (event) => {
      const db = (event.target as IDBOpenDBRequest).result
      if (!db.objectStoreNames.contains(STORE_NAME)) {
        db.createObjectStore(STORE_NAME)
      }
    }
  })
}

export async function saveEncryptedSecretKey(
  ciphertext: string,
  nonce: string
): Promise<void> {
  const db = await initializeIndexedDB()

  return new Promise((resolve, reject) => {
    const tx = db.transaction(STORE_NAME, 'readwrite')
    const store = tx.objectStore(STORE_NAME)
    const request = store.put(
      { ciphertext, nonce },
      'secretKey'
    )

    request.onerror = () => reject(request.error)
    request.onsuccess = () => resolve()
  })
}

export async function getEncryptedSecretKey(): Promise<{
  ciphertext: string
  nonce: string
} | null> {
  const db = await initializeIndexedDB()

  return new Promise((resolve, reject) => {
    const tx = db.transaction(STORE_NAME, 'readonly')
    const store = tx.objectStore(STORE_NAME)
    const request = store.get('secretKey')

    request.onerror = () => reject(request.error)
    request.onsuccess = () => {
      resolve(request.result || null)
    }
  })
}

export async function clearEncryptedSecretKey(): Promise<void> {
  const db = await initializeIndexedDB()

  return new Promise((resolve, reject) => {
    const tx = db.transaction(STORE_NAME, 'readwrite')
    const store = tx.objectStore(STORE_NAME)
    const request = store.delete('secretKey')

    request.onerror = () => reject(request.error)
    request.onsuccess = () => resolve()
  })
}

/**
 * localStorage operations for device key
 */
const DEVICE_KEY_STORAGE_KEY = 'gj_device_key'
const KDF_SALT_STORAGE_KEY = 'gj_kdf_salt'

export async function saveDeviceKey(deviceKey: Uint8Array): Promise<void> {
  const sodium = await getSodium()
  const base64Key = sodium.to_base64(deviceKey)
  localStorage.setItem(DEVICE_KEY_STORAGE_KEY, base64Key)
}

export async function getDeviceKey(): Promise<Uint8Array | null> {
  const sodium = await getSodium()
  const stored = localStorage.getItem(DEVICE_KEY_STORAGE_KEY)
  if (!stored) return null
  try {
    return sodium.from_base64(stored)
  } catch (error) {
    console.error('Failed to retrieve device key:', error)
    return null
  }
}

export function clearDeviceKey(): void {
  localStorage.removeItem(DEVICE_KEY_STORAGE_KEY)
}

export function saveSalt(salt: string): void {
  localStorage.setItem(KDF_SALT_STORAGE_KEY, salt)
}

export function getSalt(): string | null {
  return localStorage.getItem(KDF_SALT_STORAGE_KEY)
}

export function generateSalt(): string {
  // Use a constant salt for deterministic KDF
  // This is safe because the password already includes firebase credentials
  return 'grateful-journal-v1'
}
