<?php
/**
 * Encryption utility class.
 *
 * Handles encryption and decryption of sensitive data like API keys.
 * Uses WordPress LOGGED_IN_KEY for encryption.
 *
 * @package Inqyra
 */

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

/**
 * Encryption utility class.
 */
class Inqyra_Encryption {

    /**
     * Encryption method.
     */
    const METHOD = 'aes-256-cbc';

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

    /**
     * Get the encryption key.
     *
     * @return string|false The 32-byte encryption key, or false if no WordPress security keys are configured.
     */
    private static function get_key(): string|false {
        // Use WordPress's LOGGED_IN_KEY as base, or AUTH_KEY as fallback
        $key = defined('LOGGED_IN_KEY') && LOGGED_IN_KEY ? LOGGED_IN_KEY : '';

        if (empty($key) && defined('AUTH_KEY') && AUTH_KEY) {
            $key = AUTH_KEY;
        }

        if (empty($key)) {
            if (!self::$key_warning_logged) {
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Critical security warning, intentionally always logged.
                error_log('[Inqyra] WARNING: WordPress security keys (LOGGED_IN_KEY/AUTH_KEY) are not defined. Cannot securely encrypt API keys. Please configure WordPress salt keys in wp-config.php.');
                self::$key_warning_logged = true;
            }
            return false;
        }

        // Create a proper 32-byte key for AES-256
        return hash('sha256', $key, true);
    }

    /**
     * Encrypt a string.
     *
     * @param string $data The data to encrypt.
     * @return string|false The encrypted data (base64 encoded) or false on failure.
     */
    public static function encrypt(string $data): string|false {
        if (empty($data)) {
            return '';
        }

        $key = self::get_key();
        if ($key === false) {
            return false;
        }

        $iv_length = openssl_cipher_iv_length(self::METHOD);
        $iv = openssl_random_pseudo_bytes($iv_length);

        $encrypted = openssl_encrypt($data, self::METHOD, $key, OPENSSL_RAW_DATA, $iv);

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

        // Prepend IV to encrypted data and base64 encode
        return base64_encode($iv . $encrypted);
    }

    /**
     * Decrypt a string.
     *
     * @param string $data The encrypted data (base64 encoded).
     * @return string|false The decrypted data or false on failure.
     */
    public static function decrypt(string $data): string|false {
        if (empty($data)) {
            return '';
        }

        $key = self::get_key();
        if ($key === false) {
            return false;
        }

        $data = base64_decode($data);

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

        $iv_length = openssl_cipher_iv_length(self::METHOD);

        // Check if data is long enough to contain IV
        if (strlen($data) < $iv_length) {
            return false;
        }

        $iv = substr($data, 0, $iv_length);
        $encrypted = substr($data, $iv_length);

        return openssl_decrypt($encrypted, self::METHOD, $key, OPENSSL_RAW_DATA, $iv);
    }

    /**
     * Encrypt API keys array.
     *
     * @param array $api_keys Array of provider => key pairs.
     * @return array Array of provider => encrypted_key pairs.
     */
    public static function encrypt_api_keys(array $api_keys): array {
        $encrypted = [];

        foreach ($api_keys as $provider => $key) {
            if (!empty($key)) {
                $encrypted[$provider] = self::encrypt($key);
            }
        }

        return $encrypted;
    }

    /**
     * Decrypt API keys array.
     *
     * @param array $api_keys Array of provider => encrypted_key pairs.
     * @return array Array of provider => decrypted_key pairs.
     */
    public static function decrypt_api_keys(array $api_keys): array {
        $decrypted = [];

        foreach ($api_keys as $provider => $key) {
            if (!empty($key)) {
                $decrypted_key = self::decrypt($key);
                // Only add if decryption succeeded
                if ($decrypted_key !== false) {
                    $decrypted[$provider] = $decrypted_key;
                }
            }
        }

        return $decrypted;
    }

    /**
     * Mask an API key for display.
     *
     * @param string $key The API key.
     * @param int    $visible_chars Number of visible characters at start and end.
     * @return string The masked key.
     */
    public static function mask_key(string $key, int $visible_chars = 4): string {
        if (empty($key)) {
            return '';
        }

        $length = strlen($key);

        if ($length <= ($visible_chars * 2)) {
            return str_repeat('*', $length);
        }

        $start = substr($key, 0, $visible_chars);
        $end = substr($key, -$visible_chars);
        $middle_length = $length - ($visible_chars * 2);

        return $start . str_repeat('*', min($middle_length, 20)) . $end;
    }

    /**
     * Check if a value looks like it's already encrypted.
     *
     * @param string $value The value to check.
     * @return bool True if the value appears to be encrypted.
     */
    public static function is_encrypted(string $value): bool {
        if (empty($value)) {
            return false;
        }

        // Check if it's valid base64
        $decoded = base64_decode($value, true);
        if ($decoded === false) {
            return false;
        }

        // Check if it's long enough to contain IV + at least 1 byte of data
        $iv_length = openssl_cipher_iv_length(self::METHOD);
        if (strlen($decoded) <= $iv_length) {
            return false;
        }

        // Check if the original value doesn't look like a typical API key format
        // Most API keys are alphanumeric with dashes/underscores, not pure base64
        return strlen($value) > 50 && preg_match('/^[A-Za-z0-9+\/=]+$/', $value);
    }
}
