/**
 * Symmetric Encryption (AES-256-GCM)
 * @countermeasure/web-components
 *
 * AES-256-GCM symmetric encryption for secrets.
 * Compatible with FluxEncrypt's SymmetricCipher.
 */

import { stringToBytes, bytesToString, encodeEncryptedData, decodeEncryptedData } from './encoding'
import { encodeOutput, decodeInput } from './encoding-helpers'
import { importSymmetricKey, generateIV } from './keys'
import { CryptoError, type SymmetricCipherConfig, type EncodingFormat } from './types'

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

const AES_GCM_TAG_LENGTH = 128 // bits

// ============================================================================
// SYMMETRIC CIPHER CLASS
// ============================================================================

/**
 * AES-256-GCM symmetric cipher for encrypting secrets.
 * Mirrors FluxEncrypt's SymmetricCipher API.
 *
 * Usage:
 * ```typescript
 * const cipher = await createSymmetricCipher(hexKey)
 * const encrypted = await cipher.encrypt('my-secret')
 * const decrypted = await cipher.decrypt(encrypted)
 * ```
 */
export class SymmetricCipherImpl {
  private key: CryptoKey
  private encoding: EncodingFormat

  private constructor(key: CryptoKey, encoding: EncodingFormat) {
    this.key = key
    this.encoding = encoding
  }

  /**
   * Create a new SymmetricCipher from a hex-encoded key.
   */
  static async create(
    hexKey: string,
    config: SymmetricCipherConfig = {}
  ): Promise<SymmetricCipherImpl> {
    const key = await importSymmetricKey(hexKey)
    return new SymmetricCipherImpl(key, config.encoding ?? 'base64')
  }

  /**
   * Create a new SymmetricCipher from a CryptoKey.
   */
  static fromKey(key: CryptoKey, config: SymmetricCipherConfig = {}): SymmetricCipherImpl {
    return new SymmetricCipherImpl(key, config.encoding ?? 'base64')
  }

  /**
   * Encrypt plaintext data.
   * Returns base64-encoded ciphertext by default.
   */
  async encrypt(plaintext: string | Uint8Array): Promise<string> {
    try {
      const data = typeof plaintext === 'string' ? stringToBytes(plaintext) : plaintext
      const iv = generateIV()

      const ciphertext = await crypto.subtle.encrypt(
        {
          name: 'AES-GCM',
          iv: iv as unknown as Uint8Array<ArrayBuffer>,
          tagLength: AES_GCM_TAG_LENGTH,
        },
        this.key,
        data as unknown as BufferSource
      )

      // Combine version + IV + ciphertext (includes auth tag)
      const combined = encodeEncryptedData(iv, new Uint8Array(ciphertext))

      return encodeOutput(combined, this.encoding)
    } catch (error) {
      if (error instanceof CryptoError) {
        throw error
      }
      throw new CryptoError('Encryption failed', 'ENCRYPTION_FAILED', error)
    }
  }

  /**
   * Decrypt ciphertext data.
   * Expects base64-encoded input by default.
   */
  async decrypt(ciphertext: string): Promise<string> {
    try {
      const data = decodeInput(ciphertext, this.encoding)
      const { iv, ciphertext: encryptedData } = decodeEncryptedData(data)

      const plaintext = await crypto.subtle.decrypt(
        {
          name: 'AES-GCM',
          iv: iv as unknown as Uint8Array<ArrayBuffer>,
          tagLength: AES_GCM_TAG_LENGTH,
        },
        this.key,
        encryptedData as unknown as BufferSource
      )

      return bytesToString(new Uint8Array(plaintext))
    } catch (error) {
      if (error instanceof CryptoError) {
        throw error
      }
      throw new CryptoError('Decryption failed', 'DECRYPTION_FAILED', error)
    }
  }

  /**
   * Encrypt raw bytes and return raw bytes.
   */
  async encryptBytes(plaintext: Uint8Array): Promise<Uint8Array> {
    try {
      const iv = generateIV()

      const ciphertext = await crypto.subtle.encrypt(
        {
          name: 'AES-GCM',
          iv: iv as unknown as Uint8Array<ArrayBuffer>,
          tagLength: AES_GCM_TAG_LENGTH,
        },
        this.key,
        plaintext as unknown as BufferSource
      )

      return encodeEncryptedData(iv, new Uint8Array(ciphertext))
    } catch (error) {
      if (error instanceof CryptoError) {
        throw error
      }
      throw new CryptoError('Encryption failed', 'ENCRYPTION_FAILED', error)
    }
  }

  /**
   * Decrypt raw bytes and return raw bytes.
   */
  async decryptBytes(ciphertext: Uint8Array): Promise<Uint8Array> {
    try {
      const { iv, ciphertext: encryptedData } = decodeEncryptedData(ciphertext)

      const plaintext = await crypto.subtle.decrypt(
        {
          name: 'AES-GCM',
          iv: iv as unknown as Uint8Array<ArrayBuffer>,
          tagLength: AES_GCM_TAG_LENGTH,
        },
        this.key,
        encryptedData as unknown as BufferSource
      )

      return new Uint8Array(plaintext)
    } catch (error) {
      if (error instanceof CryptoError) {
        throw error
      }
      throw new CryptoError('Decryption failed', 'DECRYPTION_FAILED', error)
    }
  }

  /**
   * Get the current encoding format.
   */
  getEncoding(): EncodingFormat {
    return this.encoding
  }
}

// ============================================================================
// FACTORY FUNCTIONS
// ============================================================================

/**
 * Create a symmetric cipher from a hex-encoded key.
 * This is the primary way to create a cipher, matching FluxEncrypt's API.
 *
 * @param hexKey - 64-character hex string (32 bytes / 256 bits)
 * @param config - Optional configuration
 */
export async function createSymmetricCipher(
  hexKey: string,
  config: SymmetricCipherConfig = {}
): Promise<SymmetricCipherImpl> {
  return SymmetricCipherImpl.create(hexKey, config)
}

/**
 * Create a symmetric cipher from a CryptoKey.
 */
export function createSymmetricCipherFromKey(
  key: CryptoKey,
  config: SymmetricCipherConfig = {}
): SymmetricCipherImpl {
  return SymmetricCipherImpl.fromKey(key, config)
}

// ============================================================================
// CONVENIENCE FUNCTIONS
// ============================================================================

/**
 * Encrypt a secret with a hex key (one-shot operation).
 */
export async function encryptSecret(hexKey: string, plaintext: string): Promise<string> {
  const cipher = await createSymmetricCipher(hexKey)
  return cipher.encrypt(plaintext)
}

/**
 * Decrypt a secret with a hex key (one-shot operation).
 */
export async function decryptSecret(hexKey: string, ciphertext: string): Promise<string> {
  const cipher = await createSymmetricCipher(hexKey)
  return cipher.decrypt(ciphertext)
}
