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

/**
 * Standalone encryption utility for API keys / OAuth tokens.
 *
 * v2 format: encrypt-then-MAC (AES-256-CBC + HMAC-SHA256) with separate
 * derived keys, written as "v2:" . base64(iv . hmac . ciphertext). The MAC
 * makes the ciphertext tamper-evident and lets decryption FAIL CLEANLY
 * (returns '') when the WordPress salt has rotated, instead of silently
 * emitting garbage. Legacy values (no "v2:" prefix, written before 1.1.0)
 * are still decrypted for backward compatibility.
 *
 * No external dependencies (WooCommerce, etc.).
 */
class SEO_Fury_Encryption {

    const CIPHER      = 'aes-256-cbc';
    const VERSION     = 'v2:';
    const MAC_LENGTH  = 32; // sha256 raw

    /**
     * Derive separate encryption and MAC keys from the WordPress auth salt.
     *
     * @return array{enc:string,mac:string}
     */
    private static function keys() {
        $base = wp_salt( 'auth' );
        return array(
            'enc' => hash( 'sha256', 'seo-fury-enc|' . $base, true ),
            'mac' => hash( 'sha256', 'seo-fury-mac|' . $base, true ),
        );
    }

    /**
     * Encrypt a plaintext value (authenticated v2 format).
     *
     * @param string $value Plaintext value to encrypt.
     * @return string "v2:"-prefixed base64 token, or empty string on failure.
     */
    public static function encrypt( $value ) {
        if ( $value === '' || $value === null ) {
            return '';
        }
        $keys       = self::keys();
        $iv_length  = openssl_cipher_iv_length( self::CIPHER );
        $iv         = openssl_random_pseudo_bytes( $iv_length );
        $ciphertext = openssl_encrypt( $value, self::CIPHER, $keys['enc'], OPENSSL_RAW_DATA, $iv );
        if ( false === $ciphertext ) {
            return '';
        }
        $mac = hash_hmac( 'sha256', $iv . $ciphertext, $keys['mac'], true );
        return self::VERSION . base64_encode( $iv . $mac . $ciphertext );
    }

    /**
     * Decrypt a value produced by encrypt(), or a legacy (pre-1.1.0) value.
     *
     * @param string $value Encrypted token.
     * @return string Decrypted plaintext, or empty string on any failure
     *                (including a rotated salt or a tampered ciphertext).
     */
    public static function decrypt( $value ) {
        if ( empty( $value ) ) {
            return '';
        }

        // Authenticated v2 format
        if ( strncmp( $value, self::VERSION, strlen( self::VERSION ) ) === 0 ) {
            $keys = self::keys();
            $data = base64_decode( substr( $value, strlen( self::VERSION ) ), true );
            if ( false === $data ) {
                return '';
            }
            $iv_length = openssl_cipher_iv_length( self::CIPHER );
            if ( strlen( $data ) <= $iv_length + self::MAC_LENGTH ) {
                return '';
            }
            $iv         = substr( $data, 0, $iv_length );
            $mac        = substr( $data, $iv_length, self::MAC_LENGTH );
            $ciphertext = substr( $data, $iv_length + self::MAC_LENGTH );
            $calc       = hash_hmac( 'sha256', $iv . $ciphertext, $keys['mac'], true );
            if ( ! hash_equals( $calc, $mac ) ) {
                return ''; // wrong key (salt rotated) or tampered
            }
            $plain = openssl_decrypt( $ciphertext, self::CIPHER, $keys['enc'], OPENSSL_RAW_DATA, $iv );
            return $plain !== false ? $plain : '';
        }

        // Legacy format (pre-1.1.0): AES-256-CBC, key = sha256(wp_salt('auth')), no MAC.
        return self::decrypt_legacy( $value );
    }

    /**
     * Decrypt the pre-1.1.0 unauthenticated format.
     */
    private static function decrypt_legacy( $value ) {
        $key  = hash( 'sha256', wp_salt( 'auth' ), true );
        $data = base64_decode( $value, true );
        if ( false === $data ) {
            return '';
        }
        $iv_length = openssl_cipher_iv_length( 'aes-256-cbc' );
        if ( strlen( $data ) <= $iv_length ) {
            return '';
        }
        $iv        = substr( $data, 0, $iv_length );
        $encrypted = substr( $data, $iv_length );
        $decrypted = openssl_decrypt( $encrypted, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv );
        return $decrypted !== false ? $decrypted : '';
    }
}
