<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Symmetric encryption for secrets at rest (e.g. the PayPal client secret).
 *
 * The key is derived from WordPress's wp-config salts (wp_salt), which live in
 * wp-config.php — NOT in the database. So a database-only leak (the common case)
 * cannot decrypt a stored secret; an attacker would also need filesystem access
 * to wp-config.php.
 *
 * Uses AES-256-GCM (authenticated encryption). Encrypted values carry a version
 * prefix so decrypt() can transparently pass through any legacy plain-text value
 * written before this was introduced.
 */
class BingoCR_Crypto {

	const PREFIX = 'bcrenc1:';
	const CIPHER = 'aes-256-gcm';

	/** 32-byte key for AES-256, derived from a wp-config salt (not stored in DB). */
	private static function key(): string {
		return hash( 'sha256', 'bingocr|' . wp_salt( 'secure_auth' ), true );
	}

	/** Whether OpenSSL + the GCM cipher are available on this host. */
	private static function available(): bool {
		return function_exists( 'openssl_encrypt' )
			&& in_array( self::CIPHER, openssl_get_cipher_methods(), true );
	}

	/**
	 * Encrypt a string for storage. Empty input returns ''. If OpenSSL/GCM is
	 * unavailable the original value is returned unchanged (graceful fallback so
	 * payments still work; the value simply stays as it was).
	 */
	public static function encrypt( string $plain ): string {
		if ( '' === $plain || ! self::available() ) {
			return $plain;
		}
		$iv  = random_bytes( 12 );
		$tag = '';
		$ct  = openssl_encrypt( $plain, self::CIPHER, self::key(), OPENSSL_RAW_DATA, $iv, $tag );
		if ( false === $ct ) {
			return $plain;
		}
		return self::PREFIX . base64_encode( $iv . $tag . $ct );
	}

	/**
	 * Decrypt a stored value. A value without the prefix is treated as legacy
	 * plain text and returned as-is, so existing installs keep working until the
	 * secret is next saved (at which point it is encrypted).
	 */
	public static function decrypt( string $stored ): string {
		if ( strpos( $stored, self::PREFIX ) !== 0 ) {
			return $stored; // legacy plain text, or empty
		}
		if ( ! self::available() ) {
			return '';
		}
		$raw = base64_decode( substr( $stored, strlen( self::PREFIX ) ), true );
		if ( false === $raw || strlen( $raw ) < 28 ) {
			return '';
		}
		$iv    = substr( $raw, 0, 12 );
		$tag   = substr( $raw, 12, 16 );
		$ct    = substr( $raw, 28 );
		$plain = openssl_decrypt( $ct, self::CIPHER, self::key(), OPENSSL_RAW_DATA, $iv, $tag );
		return false === $plain ? '' : $plain;
	}

	/** True when the stored value is an encrypted blob written by encrypt(). */
	public static function is_encrypted( string $stored ): bool {
		return strpos( $stored, self::PREFIX ) === 0;
	}

	/**
	 * Return a copy of a settings array safe to send to the browser: the PayPal
	 * client secret is stripped and replaced with a boolean "is one saved" flag.
	 */
	public static function redact_settings( array $settings ): array {
		$has = ! empty( $settings['paypal_client_secret'] );
		$settings['paypal_client_secret']     = '';
		$settings['paypal_client_secret_set'] = $has;
		return $settings;
	}
}
