<?php
/**
 * Encryption helpers for sensitive settings (API keys, secrets).
 *
 * Uses libsodium (bundled in PHP 7.2+) with a key derived from WordPress
 * salts (defined in wp-config.php, outside the database). A database dump
 * alone is therefore not enough to recover the plaintext values.
 *
 * Stored format: "gf_enc_v1:<base64(nonce + ciphertext)>"
 * Backward-compat: values without the prefix are returned as-is so that
 * existing plaintext settings keep working after the upgrade.
 *
 * @package Giraforms
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class GiraForms_Crypto
 */
class GiraForms_Encryption {

	/**
	 * Prefix that marks an encrypted value.
	 */
	const PREFIX = 'gf_enc_v1:';

	/**
	 * Option keys that must be encrypted at rest.
	 */
	const ENCRYPTED_KEYS = array(
		'turnstile_secret_key',
		'recaptcha_v2_secret_key',
		'recaptcha_v3_secret_key',
		'hcaptcha_secret_key',
		'brevo_api_key',
		'mailchimp_api_key',
		'stripe_secret_key',
		'stripe_test_secret_key',
		'stripe_live_secret_key',
		'stripe_test_webhook_secret',
		'stripe_live_webhook_secret',
	);

	/**
	 * Derive a 32-byte encryption key from WordPress salts.
	 *
	 * @return string 32 raw bytes.
	 */
	private static function get_key() {
		$salt = '';
		if ( defined( 'LOGGED_IN_KEY' ) ) {
			$salt .= LOGGED_IN_KEY;
		}
		if ( defined( 'SECURE_AUTH_KEY' ) ) {
			$salt .= SECURE_AUTH_KEY;
		}
		if ( '' === $salt ) {
			// Last-resort fallback — still better than plaintext.
			$salt = 'giraforms_fallback_' . ( defined( 'DB_NAME' ) ? DB_NAME : '' ) . ( defined( 'DB_HOST' ) ? DB_HOST : '' );
		}
		return hash( 'sha256', $salt, true );
	}

	/**
	 * Encrypt a string value.
	 *
	 * Returns an empty string for empty input.
	 * Returns the original value unchanged if libsodium is unavailable.
	 *
	 * @param string $value Plaintext value.
	 * @return string Encrypted string with prefix, or original value.
	 */
	public static function encrypt( $value ) {
		$value = (string) $value;
		if ( '' === $value ) {
			return '';
		}
		if ( ! function_exists( 'sodium_crypto_secretbox' ) ) {
			return $value;
		}
		$key    = self::get_key();
		$nonce  = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
		$cipher = sodium_crypto_secretbox( $value, $nonce, $key );
		sodium_memzero( $key );
		return self::PREFIX . base64_encode( $nonce . $cipher );
	}

	/**
	 * Decrypt a previously encrypted value.
	 *
	 * Returns the original value unchanged if it is not prefixed (plaintext
	 * migration path) or if libsodium is unavailable.
	 *
	 * @param string $value Encrypted or plaintext value.
	 * @return string Decrypted plaintext, or original value.
	 */
	public static function decrypt( $value ) {
		$value = (string) $value;
		if ( '' === $value || 0 !== strpos( $value, self::PREFIX ) ) {
			return $value;
		}
		if ( ! function_exists( 'sodium_crypto_secretbox_open' ) ) {
			return $value;
		}
		$encoded   = substr( $value, strlen( self::PREFIX ) );
		$raw       = base64_decode( $encoded, true );
		$nonce_len = SODIUM_CRYPTO_SECRETBOX_NONCEBYTES;
		if ( false === $raw || strlen( $raw ) <= $nonce_len ) {
			return '';
		}
		$nonce  = substr( $raw, 0, $nonce_len );
		$cipher = substr( $raw, $nonce_len );
		$key    = self::get_key();
		$plain  = sodium_crypto_secretbox_open( $cipher, $nonce, $key );
		sodium_memzero( $key );
		return false === $plain ? '' : $plain;
	}
}
