<?php
/**
 * Credential Encryption
 *
 * Handles secure encryption and decryption of integration credentials.
 *
 * @package Sequensy
 * @since 2.0.0
 */

declare(strict_types=1);

namespace Sequensy\Integrations;

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Class CredentialEncryption
 *
 * Provides AES-256-CBC encryption for sensitive credential storage.
 * Falls back to base64 encoding when OpenSSL is unavailable.
 */
class CredentialEncryption {

	/**
	 * Whether a warning has been logged this request.
	 *
	 * @var bool
	 */
	private static bool $warned = false;

	/**
	 * Encrypt credentials for storage.
	 *
	 * Uses AES-256-CBC encryption when available, falls back to base64.
	 *
	 * @param array $credentials Credentials to encrypt.
	 * @return string|false Encrypted string or false on failure.
	 */
	public static function encrypt( array $credentials ): string|false {
		$key = self::get_encryption_key();

		if ( empty( $key ) ) {
			// Fallback to base64 if no proper key available.
			self::log_warning( 'encryption key unavailable (SECURE_AUTH_KEY and AUTH_KEY not set)' );
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Encoding for storage, not obfuscation.
			return base64_encode( wp_json_encode( $credentials ) );
		}

		$json = wp_json_encode( $credentials );

		if ( function_exists( 'openssl_encrypt' ) ) {
			$iv        = openssl_random_pseudo_bytes( 16 );
			$encrypted = openssl_encrypt( $json, 'AES-256-CBC', $key, 0, $iv );

			if ( false === $encrypted ) {
				return false;
			}

			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Encoding encrypted binary data for storage.
			return base64_encode( $iv . $encrypted );
		}

		// Fallback if OpenSSL not available.
		self::log_warning( 'OpenSSL extension not available' );
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Encoding for storage, not obfuscation.
		return base64_encode( $json );
	}

	/**
	 * Decrypt credentials from storage.
	 *
	 * Handles both AES-256-CBC encrypted and base64-only data.
	 *
	 * @param string $encrypted Encrypted credentials string.
	 * @return array|false Decrypted credentials or false on failure.
	 */
	public static function decrypt( string $encrypted ): array|false {
		$key = self::get_encryption_key();

		if ( empty( $key ) ) {
			// Fallback decode.
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Decoding stored credentials.
			$decoded = base64_decode( $encrypted );
			return json_decode( $decoded, true );
		}

		if ( function_exists( 'openssl_decrypt' ) ) {
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Decoding stored credentials.
			$data = base64_decode( $encrypted );

			if ( false === $data || strlen( $data ) < 16 ) {
				// Try fallback decode for non-encrypted data.
				// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Decoding stored credentials.
				$decoded = base64_decode( $encrypted );
				return json_decode( $decoded, true );
			}

			$iv             = substr( $data, 0, 16 );
			$encrypted_data = substr( $data, 16 );

			$decrypted = openssl_decrypt( $encrypted_data, 'AES-256-CBC', $key, 0, $iv );

			if ( false === $decrypted ) {
				// Try fallback decode.
				// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Decoding stored credentials.
				$decoded = base64_decode( $encrypted );
				return json_decode( $decoded, true );
			}

			return json_decode( $decrypted, true );
		}

		// Fallback decode.
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Decoding stored credentials.
		$decoded = base64_decode( $encrypted );
		return json_decode( $decoded, true );
	}

	/**
	 * Get encryption key from WordPress salts.
	 *
	 * @return string Encryption key (32-byte hash) or empty string if unavailable.
	 */
	private static function get_encryption_key(): string {
		if ( defined( 'SECURE_AUTH_KEY' ) && SECURE_AUTH_KEY ) {
			return hash( 'sha256', SECURE_AUTH_KEY . 'sequensy_integration', true );
		}

		if ( defined( 'AUTH_KEY' ) && AUTH_KEY ) {
			return hash( 'sha256', AUTH_KEY . 'sequensy_integration', true );
		}

		return '';
	}

	/**
	 * Log a security warning about encryption fallback.
	 *
	 * Only logs once per request to avoid log spam.
	 *
	 * @param string $reason The reason encryption is unavailable.
	 * @return void
	 */
	private static function log_warning( string $reason ): void {
		// Only warn once per request to avoid log spam.
		if ( self::$warned ) {
			return;
		}

		self::$warned = true;

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log(
				sprintf(
					'Sequensy Pro Security Warning: Integration credentials are stored without encryption (%s). ' .
					'This is a security risk. Please configure WordPress security keys in wp-config.php.',
					$reason
				)
			);
		}
	}
}
