<?php
/**
 * Answyr — at-rest secret encryption.
 *
 * Encrypts provider API keys before they touch the options table. The key is
 * derived from WordPress's own salts (AUTH_KEY/AUTH_SALT), so secrets are tied
 * to the site and not stored in plaintext. Uses libsodium when available, with
 * an OpenSSL (AES-256-GCM) fallback.
 *
 * @package Answyr
 */

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

final class Answyr_Crypto {

	private const PREFIX_SODIUM  = 'ss1:';
	private const PREFIX_OPENSSL = 'ss2:';

	/** 32-byte key derived from site salts. */
	private static function key(): string {
		$material = ( defined( 'AUTH_KEY' ) ? AUTH_KEY : '' ) . ( defined( 'AUTH_SALT' ) ? AUTH_SALT : '' );
		if ( '' === $material ) {
			$material = wp_salt( 'auth' );
		}
		return hash( 'sha256', 'answyr|' . $material, true );
	}

	public static function encrypt( string $plaintext ): string {
		if ( '' === $plaintext ) {
			return '';
		}

		if ( function_exists( 'sodium_crypto_secretbox' ) ) {
			$nonce  = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
			$cipher = sodium_crypto_secretbox( $plaintext, $nonce, self::key() );
			return self::PREFIX_SODIUM . base64_encode( $nonce . $cipher );
		}

		$iv     = random_bytes( 12 );
		$tag    = '';
		$cipher = openssl_encrypt( $plaintext, 'aes-256-gcm', self::key(), OPENSSL_RAW_DATA, $iv, $tag );
		return self::PREFIX_OPENSSL . base64_encode( $iv . $tag . $cipher );
	}

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

		if ( str_starts_with( $stored, self::PREFIX_SODIUM ) ) {
			$raw = base64_decode( substr( $stored, strlen( self::PREFIX_SODIUM ) ), true );
			if ( false === $raw || strlen( $raw ) <= SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ) {
				return '';
			}
			$nonce  = substr( $raw, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
			$cipher = substr( $raw, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
			$plain  = sodium_crypto_secretbox_open( $cipher, $nonce, self::key() );
			return false === $plain ? '' : $plain;
		}

		if ( str_starts_with( $stored, self::PREFIX_OPENSSL ) ) {
			$raw = base64_decode( substr( $stored, strlen( self::PREFIX_OPENSSL ) ), true );
			if ( false === $raw || strlen( $raw ) <= 28 ) {
				return '';
			}
			$iv     = substr( $raw, 0, 12 );
			$tag    = substr( $raw, 12, 16 );
			$cipher = substr( $raw, 28 );
			$plain  = openssl_decrypt( $cipher, 'aes-256-gcm', self::key(), OPENSSL_RAW_DATA, $iv, $tag );
			return false === $plain ? '' : $plain;
		}

		// Legacy/plaintext value — return as-is so a one-time re-save upgrades it.
		return $stored;
	}
}
