<?php
/**
 * Settings secret encryption helper.
 *
 * @package Affilun\Application\Settings
 */

declare(strict_types=1);

namespace Affilun\Application\Settings;

/**
 * Encrypts/decrypts the non-autoload settings secrets payload.
 */
final class SettingsSecretBox {

	/**
	 * Envelope prefix.
	 */
	private const PREFIX = 'enc:v1:';

	/**
	 * Encrypt an array payload into a string envelope.
	 *
	 * @param array<string,mixed> $payload Secret payload.
	 */
	public static function encryptArray( array $payload ): string {
		$json = function_exists( 'wp_json_encode' )
			? wp_json_encode( $payload )
			// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- Pure-PHP fallback when WordPress is unavailable.
			: json_encode( $payload );
		if ( ! is_string( $json ) ) {
			$json = '{}';
		}

		$iv     = random_bytes( 12 );
		$tag    = '';
		$cipher = openssl_encrypt(
			$json,
			'aes-256-gcm',
			self::key(),
			OPENSSL_RAW_DATA,
			$iv,
			$tag
		);
		if ( ! is_string( $cipher ) || strlen( $tag ) !== 16 ) {
			return self::PREFIX;
		}

		return self::PREFIX
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Binary encryption envelope encoding, not code obfuscation.
			. base64_encode( $iv ) . ':'
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Binary encryption envelope encoding, not code obfuscation.
			. base64_encode( $tag ) . ':'
			// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Binary encryption envelope encoding, not code obfuscation.
			. base64_encode( $cipher );
	}

	/**
	 * Decrypt a stored payload.
	 *
	 * @param mixed $stored Stored option value.
	 *
	 * @return array<string,mixed>|null
	 */
	public static function decryptArray( $stored ): ?array {
		if ( is_array( $stored ) ) {
			return $stored;
		}
		if ( ! is_string( $stored ) || ! str_starts_with( $stored, self::PREFIX ) ) {
			return null;
		}

		$encoded = substr( $stored, strlen( self::PREFIX ) );
		$parts   = explode( ':', $encoded );
		if ( count( $parts ) !== 3 ) {
			return null;
		}

		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Decodes binary encryption envelope, not executable code.
		$iv = base64_decode( $parts[0], true );
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Decodes binary encryption envelope, not executable code.
		$tag = base64_decode( $parts[1], true );
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Decodes binary encryption envelope, not executable code.
		$cipher = base64_decode( $parts[2], true );
		if ( ! is_string( $iv ) || ! is_string( $tag ) || ! is_string( $cipher ) ) {
			return null;
		}

		$json = openssl_decrypt(
			$cipher,
			'aes-256-gcm',
			self::key(),
			OPENSSL_RAW_DATA,
			$iv,
			$tag
		);
		if ( ! is_string( $json ) ) {
			return null;
		}

		$data = json_decode( $json, true );
		return is_array( $data ) ? $data : null;
	}

	/**
	 * Resolve binary encryption key.
	 */
	private static function key(): string {
		return hash( 'sha256', self::secretMaterial(), true );
	}

	/**
	 * Resolve host secret material outside the database.
	 */
	private static function secretMaterial(): string {
		if ( defined( 'AFFILUN_SETTINGS_SECRET_KEY' ) ) {
			$value = (string) constant( 'AFFILUN_SETTINGS_SECRET_KEY' );
			if ( $value !== '' ) {
				return $value;
			}
		}
		if ( function_exists( 'wp_salt' ) ) {
			$salt = (string) wp_salt( 'auth' );
			if ( $salt !== '' ) {
				return $salt;
			}
		}
		foreach ( array( 'AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'NONCE_KEY' ) as $constant ) {
			if ( defined( $constant ) ) {
				$value = (string) constant( $constant );
				if ( $value !== '' ) {
					return $value;
				}
			}
		}
		return 'affilun-settings-secret-development-key';
	}
}
