# frozen_string_literal: true

require "openssl"
require "base64"

module SymmetricEncryptorService
  CIPHER = "aes-256-gcm"
  IV_LENGTH = 12 # GCM standard IV length is 12 bytes (96 bits)
  AUTH_TAG_LENGTH = 16 # GCM standard auth tag length is 16 bytes (128 bits)

  # Encrypts a plaintext string and returns a URL-safe, Base64 encoded string.
  # The output string contains the initialization vector (IV), the authentication
  # tag, and the ciphertext, making it self-contained for decryption.
  #
  # @param plaintext [String] The data to encrypt.
  # @param key [String] The 32-byte raw encryption key.
  # @return [String] A URL-safe Base64 encoded string.
  def self.encrypt(plaintext)
    key = Rails.application.credentials.login_psk
    cipher = OpenSSL::Cipher.new(CIPHER)
    cipher.encrypt
    cipher.key = key

    # The IV (Initialization Vector) or "nonce" must be unique for every
    # encryption performed with the same key.
    iv = cipher.random_iv

    # Encrypt the data
    encrypted_data = cipher.update(plaintext) + cipher.final

    # The authentication tag is generated by GCM and ensures the data's integrity.
    auth_tag = cipher.auth_tag

    # We combine the IV, auth_tag, and the encrypted data into a single string.
    # This makes it easy to pass around, for example, as a token in a URL.
    # Format: [IV (12 bytes)][Auth Tag (16 bytes)][Ciphertext]
    combined_data = iv + auth_tag + encrypted_data

    # Use URL-safe Base64 encoding to avoid issues with special characters in links.
    Base64.urlsafe_encode64(combined_data)
  end

  # Decrypts a URL-safe, Base64 encoded string back to its original plaintext.
  # It will raise an OpenSSL::Cipher::CipherError if the key is incorrect,
  # or if the data has been tampered with (i.e., the auth tag is invalid).
  #
  # @param encrypted_string [String] The URL-safe Base64 string from the encrypt method.
  # @param key [String] The same 32-byte raw encryption key used to encrypt.
  # @return [String] The original plaintext data.
  # @raise [OpenSSL::Cipher::CipherError] If decryption fails due to tampering or wrong key.
  def self.decrypt(encrypted_string)
    key = Rails.application.credentials.login_psk
    key = Base64.strict_decode64(key)
    # Decode the data from URL-safe Base64.
    combined_data = Base64.urlsafe_decode64(encrypted_string)

    # Extract the components in the same order we combined them.
    iv = combined_data.slice(0, IV_LENGTH)
    auth_tag = combined_data.slice(IV_LENGTH, AUTH_TAG_LENGTH)
    encrypted_data = combined_data.slice(IV_LENGTH + AUTH_TAG_LENGTH..-1)

    # Initialize the cipher for decryption.
    decipher = OpenSSL::Cipher.new(CIPHER)
    decipher.decrypt
    decipher.key = key
    decipher.iv = iv

    # IMPORTANT: Set the authentication tag *before* trying to decrypt.
    # The cipher will use this to verify the data's integrity.
    decipher.auth_tag = auth_tag

    # If the auth_tag is invalid (data tampered with) or the key is wrong,
    # the next line will raise an OpenSSL::Cipher::CipherError.
    decipher.update(encrypted_data) + decipher.final
  end
end
