<?php
/**
 * AES-256-GCM encryption helper for API key storage.
 *
 * Key derivation:  hash('sha256', wp_salt('secure_auth') . wplens_ip_salt)
 *
 * @package WPLens
 */

namespace WPLens\Utils;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

final class Encryption {

	private const CIPHER = 'aes-256-gcm';

	public static function available(): bool {
		return function_exists( 'openssl_encrypt' ) && in_array( self::CIPHER, openssl_get_cipher_methods(), true );
	}

	private static function key(): string {
		$site_salt = (string) get_option( 'wplens_ip_salt', '' );
		$wp_salt   = function_exists( 'wp_salt' ) ? wp_salt( 'secure_auth' ) : '';
		return hash( 'sha256', $wp_salt . '|lens|' . $site_salt, true );
	}

	public static function encrypt( string $plaintext ): string {
		if ( '' === $plaintext ) {
			return '';
		}
		if ( ! self::available() ) {
			// Fallback: base64 only — flagged in settings UI.
			return 'plain:' . base64_encode( $plaintext );
		}

		$iv  = random_bytes( 12 );
		$tag = '';

		$cipher = openssl_encrypt(
			$plaintext,
			self::CIPHER,
			self::key(),
			OPENSSL_RAW_DATA,
			$iv,
			$tag
		);

		if ( false === $cipher ) {
			return '';
		}

		return 'gcm:' . base64_encode( $iv . $tag . $cipher );
	}

	public static function decrypt( string $ciphertext ): string {
		if ( '' === $ciphertext ) {
			return '';
		}

		if ( strpos( $ciphertext, 'plain:' ) === 0 ) {
			$decoded = base64_decode( substr( $ciphertext, 6 ), true );
			return is_string( $decoded ) ? $decoded : '';
		}

		if ( strpos( $ciphertext, 'gcm:' ) !== 0 || ! self::available() ) {
			return '';
		}

		$decoded = base64_decode( substr( $ciphertext, 4 ), true );
		if ( ! is_string( $decoded ) || strlen( $decoded ) < 28 ) {
			return '';
		}

		$iv     = substr( $decoded, 0, 12 );
		$tag    = substr( $decoded, 12, 16 );
		$cipher = substr( $decoded, 28 );

		$plain = openssl_decrypt(
			$cipher,
			self::CIPHER,
			self::key(),
			OPENSSL_RAW_DATA,
			$iv,
			$tag
		);

		return is_string( $plain ) ? $plain : '';
	}

	/**
	 * Mask a key for display, e.g. "sk-abcd...xyz9".
	 */
	public static function mask( string $value ): string {
		$len = strlen( $value );
		if ( $len <= 8 ) {
			return str_repeat( '•', max( 0, $len ) );
		}
		// Fixed-length mask: 7 leading + 4 dots + 4 trailing. Reveals the OpenAI
		// key prefix (e.g. "sk-prj-") so the user can recognise their own key,
		// without producing a bullet string that overflows the settings UI.
		$lead_len  = min( 7, max( 4, $len - 8 ) );
		$trail_len = min( 4, $len - $lead_len );
		return substr( $value, 0, $lead_len ) . str_repeat( '•', 4 ) . substr( $value, -$trail_len );
	}
}
