<?php

namespace AIPT\Utils;

/**
 * Encryption utility for secure API key storage
 * 
 * This class provides AES-256-GCM encryption for API keys
 * while maintaining backward compatibility with existing data.
 * 
 * Security Features:
 * - AES-256-GCM encryption with authentication
 * - Site-specific encryption keys
 * - Secure key derivation using PBKDF2
 * - Automatic backward compatibility detection
 * 
 * @since 1.0.0
 */
class Encryption {
    
    /**
     * Encryption method
     */
    private const CIPHER_METHOD = 'aes-256-gcm';
    
    /**
     * Key derivation iterations for PBKDF2
     */
    private const PBKDF2_ITERATIONS = 10000;
    
    /**
     * Salt length for key derivation
     */
    private const SALT_LENGTH = 32;
    
    /**
     * IV length for encryption
     */
    private const IV_LENGTH = 12; 
    
    /**
     * Tag length for authentication
     */
    private const TAG_LENGTH = 16; 
    
    /**
     * Prefix to identify encrypted data
     */
    private const ENCRYPTED_PREFIX = 'AIPT_ENC_';
    
    /**
     * Get the master key for encryption (legacy version for backward compatibility)
     * 
     * @return string Base64 encoded master key
     */
    private static function get_legacy_master_key(): string {

        $site_key = AUTH_KEY . SECURE_AUTH_KEY . LOGGED_IN_KEY . NONCE_KEY;
        $site_salt = AUTH_SALT . SECURE_AUTH_SALT . LOGGED_IN_SALT . NONCE_SALT;
        

        $site_identifier = get_site_url() . DB_NAME . $GLOBALS['table_prefix'];
        

        if (strlen($site_key) < 64) {
            $site_key .= 'AIPT_FALLBACK_DETERMINISTIC_' . hash('sha256', get_site_url() . ABSPATH);
        }
        

        $key_material = $site_key . $site_salt . $site_identifier;
        

        return hash('sha256', $key_material, true);
    }
    
    /**
     * Get the secure master key for encryption (new improved version)
     * 
     * @param bool|null $use_legacy_mode Force legacy mode (null = use global setting)
     * @return string Raw master key for KDF
     */
    private static function get_master_key($use_legacy_mode = null): string {

        $should_use_legacy = $use_legacy_mode !== null ? 
            $use_legacy_mode : 
            get_option('aipt_use_legacy_encryption', false);
        

        if ($should_use_legacy) {
            return self::get_legacy_master_key();
        }
        

        $secure_keys = AUTH_KEY . SECURE_AUTH_KEY . LOGGED_IN_KEY . NONCE_KEY;
        $secure_salts = AUTH_SALT . SECURE_AUTH_SALT . LOGGED_IN_SALT . NONCE_SALT;
        

        if (strlen($secure_keys) < 64) {
            $secure_keys .= 'AIPT_SECURE_FALLBACK_' . hash('sha256', ABSPATH . WP_CONTENT_DIR);
        }
        

        $key_material = $secure_keys . $secure_salts;
        

        $static_salt = hash('sha256', 'AIPT_MASTER_KEY_SALT_' . ABSPATH, true);
        
        return hash_pbkdf2('sha256', $key_material, $static_salt, 100000, 32, true);
    }
    
    /**
     * Ensure new installations use secure encryption
     * 
     * @return bool True if new installation setup completed
     */
    public static function setup_new_installation(): bool {
        try {

            $is_new_installation = !get_option('aipt_encryption_migrated', false) && 
                                  !get_option('aipt_use_legacy_encryption', false);
            
            if ($is_new_installation) {

                update_option('aipt_use_legacy_encryption', false);
                update_option('aipt_encryption_migrated', true);
                update_option('aipt_secure_encryption_migrated', true);
                return true;
            }
            
            return false;
            
        } catch (\Exception $e) {
            return false;
        }
    }
    
    /**
     * Derive encryption key from master key using PBKDF2
     * 
     * @param string $salt Random salt for key derivation
     * @param bool|null $use_legacy_mode Force legacy mode (null = use global setting)
     * @return string Derived encryption key
     */
    private static function derive_key(string $salt, $use_legacy_mode = null): string {
        $master_key = self::get_master_key($use_legacy_mode);
        return hash_pbkdf2('sha256', $master_key, $salt, self::PBKDF2_ITERATIONS, 32, true);
    }
    
    /**
     * Encrypt API key
     * 
     * @param string $plaintext The API key to encrypt
     * @param bool|null $use_legacy_mode Force legacy mode (null = use global setting)
     * @return string Encrypted and encoded string
     * @throws \Exception If encryption fails
     */
    public static function encrypt(string $plaintext, $use_legacy_mode = null): string {
        if (empty($plaintext)) {
            return '';
        }
        

        if (!extension_loaded('openssl')) {
            return $plaintext;
        }
        
        try {

            $salt = random_bytes(self::SALT_LENGTH);
            $iv = random_bytes(self::IV_LENGTH);
            

            $key = self::derive_key($salt, $use_legacy_mode);
            

            $tag = '';
            $ciphertext = openssl_encrypt(
                $plaintext,
                self::CIPHER_METHOD,
                $key,
                OPENSSL_RAW_DATA,
                $iv,
                $tag
            );
            
            if ($ciphertext === false) {
                $last_error = openssl_error_string();
                throw new \Exception('Encryption failed: ' . ($last_error ?: 'Unknown OpenSSL error'));
            }
            

            $encrypted_data = $salt . $iv . $tag . $ciphertext;
            

            return self::ENCRYPTED_PREFIX . base64_encode($encrypted_data);
            
        } catch (\Exception $e) {

            return $plaintext;
        }
    }
    
    /**
     * Decrypt API key
     * 
     * @param string $encrypted_data The encrypted string to decrypt
     * @param bool|null $use_legacy_mode Force legacy mode (null = use global setting)
     * @param bool $silent_mode Suppress error logging for migration attempts
     * @return string Decrypted API key
     * @throws \Exception If decryption fails
     */
    public static function decrypt(string $encrypted_data, $use_legacy_mode = null, bool $silent_mode = false): string {
        if (empty($encrypted_data)) {
            return '';
        }
        

        if (!self::is_encrypted($encrypted_data)) {

            return $encrypted_data;
        }
        

        if (!extension_loaded('openssl')) {

            return $encrypted_data;
        }
        
        try {

            $data = base64_decode(substr($encrypted_data, strlen(self::ENCRYPTED_PREFIX)));
            
            if ($data === false) {
                throw new \Exception('Invalid base64 encoding');
            }
            

            $salt = substr($data, 0, self::SALT_LENGTH);
            $iv = substr($data, self::SALT_LENGTH, self::IV_LENGTH);
            $tag = substr($data, self::SALT_LENGTH + self::IV_LENGTH, self::TAG_LENGTH);
            $ciphertext = substr($data, self::SALT_LENGTH + self::IV_LENGTH + self::TAG_LENGTH);
            

            $key = self::derive_key($salt, $use_legacy_mode);
            

            $plaintext = openssl_decrypt(
                $ciphertext,
                self::CIPHER_METHOD,
                $key,
                OPENSSL_RAW_DATA,
                $iv,
                $tag
            );
            
            if ($plaintext === false) {
                $last_error = openssl_error_string();
                throw new \Exception('Decryption failed: ' . ($last_error ?: 'Unknown OpenSSL error'));
            }
            
            return $plaintext;
            
        } catch (\Exception $e) {

            if (!$silent_mode) {
            }

            return $encrypted_data;
        }
    }
    
    /**
     * Check if data is encrypted
     * 
     * @param string $data Data to check
     * @return bool True if data is encrypted
     */
    public static function is_encrypted(string $data): bool {
        return strpos($data, self::ENCRYPTED_PREFIX) === 0;
    }
    
    /**
     * Encrypt API key for storage (backward compatible)
     * 
     * @param string $api_key The API key to encrypt
     * @param bool|null $use_legacy_mode Force legacy mode (null = use global setting)
     * @return string Encrypted API key or original if encryption fails
     */
    public static function encrypt_api_key(string $api_key, $use_legacy_mode = null): string {
        return self::encrypt($api_key, $use_legacy_mode);
    }
    
    /**
     * Decrypt API key from storage (backward compatible)
     * 
     * @param string $encrypted_key The encrypted API key
     * @param bool|null $use_legacy_mode Force legacy mode (null = use global setting)
     * @param bool $silent_mode Suppress error logging for migration attempts
     * @return string Decrypted API key or original if not encrypted
     */
    public static function decrypt_api_key(string $encrypted_key, $use_legacy_mode = null, bool $silent_mode = false): string {
        return self::decrypt($encrypted_key, $use_legacy_mode, $silent_mode);
    }
    
    /**
     * Migrate existing plain text API keys to encrypted format
     * 
     * @return bool True if migration was successful
     */
    public static function migrate_existing_keys(): bool {
        try {
            $migrated = false;
            $already_migrated = get_option('aipt_encryption_migrated', false);
            $providers = [
                'aipt_openai_api_key',
                'aipt_gemini_api_key',
                'aipt_claude_api_key',
                'aipt_openrouter_api_key',
            ];

            $stored_keys = [];
            foreach ($providers as $option_name) {
                $stored_keys[$option_name] = get_option($option_name, '');
            }

            if (!$already_migrated) {
                foreach ($stored_keys as $existing_key) {

                    if (is_string($existing_key) && self::is_encrypted($existing_key)) {
                        update_option('aipt_use_legacy_encryption', true);
                        break;
                    }
                }
            }

            foreach ($stored_keys as $option_name => $existing_key) {

                if (!is_string($existing_key) || empty($existing_key) || self::is_encrypted($existing_key)) {
                    continue;
                }

                $encrypted_key = self::encrypt($existing_key);
                if (self::is_encrypted($encrypted_key)) {
                    update_option($option_name, $encrypted_key);
                    $migrated = true;
                }
            }

            if (!$already_migrated) {
                update_option('aipt_encryption_migrated', true);
                if ($migrated) {
                }
            } elseif ($migrated) {
            }

            return true;

        } catch (\Exception $e) {
            return false;
        }
    }
    
    /**
     * Check if encryption migration has been completed
     * 
     * @return bool True if migration is completed
     */
    public static function is_migration_completed(): bool {
        return get_option('aipt_encryption_migrated', false);
    }
} 