import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto'

// AES-256-GCM at-rest encryption for Stalwart host credentials (app passwords).
// The key comes from SCHEDULING_CREDENTIAL_KEY (64 hex chars = 32 bytes); in
// production this is sourced from KMS/sealed-secrets. We store ciphertext + iv +
// authTag separately (all base64) so the GCM auth tag is verified on every open —
// a tampered ciphertext throws rather than returning garbage. Secrets are NEVER
// logged: this module deals only in opaque buffers.
const ALGO = 'aes-256-gcm'
const IV_BYTES = 12 // GCM standard nonce length

export interface SealedSecret {
  encryptedSecret: string // base64 ciphertext
  iv: string // base64 nonce
  authTag: string // base64 GCM tag
}

@Injectable()
export class CredentialCipher {
  private readonly key: Buffer

  constructor(config: ConfigService) {
    const hex = config.get<string>('SCHEDULING_CREDENTIAL_KEY') ?? ''
    const key = Buffer.from(hex, 'hex')
    if (key.length !== 32) {
      throw new Error(
        'SCHEDULING_CREDENTIAL_KEY must be 32 bytes (64 hex chars). Generate with: openssl rand -hex 32',
      )
    }
    this.key = key
  }

  seal(plaintext: string): SealedSecret {
    const iv = randomBytes(IV_BYTES)
    const cipher = createCipheriv(ALGO, this.key, iv)
    const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()])
    return {
      encryptedSecret: enc.toString('base64'),
      iv: iv.toString('base64'),
      authTag: cipher.getAuthTag().toString('base64'),
    }
  }

  open(sealed: SealedSecret): string {
    const decipher = createDecipheriv(ALGO, this.key, Buffer.from(sealed.iv, 'base64'))
    decipher.setAuthTag(Buffer.from(sealed.authTag, 'base64'))
    const dec = Buffer.concat([
      decipher.update(Buffer.from(sealed.encryptedSecret, 'base64')),
      decipher.final(),
    ])
    return dec.toString('utf8')
  }
}
