<?php

declare(strict_types=1);

namespace AIPT\Utils;

use AIPT\Utils\Credentials\CredentialResolver;
use AIPT\Utils\Credentials\CredentialSource;
use AIPT\Utils\Credentials\LocalCredentialProvider;
use AIPT\Utils\Encryption;

/**
 * Centralized API Key Manager
 * 
 * This class provides a secure, centralized way to manage API keys
 * with automatic encryption/decryption and backward compatibility.
 * 
 * Features:
 * - Automatic encryption for storage
 * - Automatic decryption for usage  
 * - Backward compatibility with existing plain text keys
 * - Secure key retrieval and storage
 * - Migration support for existing installations
 * - Graceful fallback to plain text when encryption/decryption fails
 *
 * Since 2.6.0: read methods delegate to CredentialResolver chain (Connectors → Local).
 * Write methods remain legacy-local for backward compatibility.
 * 
 * @since 1.0.0
 */
class ApiKeyManager {
    
    /**
     * Get OpenAI API key (decrypted, with plaintext fallback)
     * 
     * @return string Decrypted OpenAI API key or plaintext if decryption fails
     */
    public static function get_openai_key(): string {
        $key = CredentialResolver::instance()->getKey( 'openai' );
        return is_string( $key ) ? $key : '';
    }
    
    /**
     * Get Gemini API key (decrypted, with plaintext fallback)
     * 
     * @return string Decrypted Gemini API key or plaintext if decryption fails
     */
    public static function get_gemini_key(): string {
        $key = CredentialResolver::instance()->getKey( 'gemini' );
        return is_string( $key ) ? $key : '';
    }
    
    /**
     * Get OpenRouter API key (decrypted, with plaintext fallback)
     * 
     * @return string Decrypted OpenRouter API key or plaintext if decryption fails
     */
    public static function get_openrouter_key(): string {
        $key = CredentialResolver::instance()->getKey( 'openrouter' );
        return is_string( $key ) ? $key : '';
    }
    
    /**
     * Get Claude API key (decrypted, with plaintext fallback)
     * 
     * @return string Decrypted Claude API key or plaintext if decryption fails
     */
    public static function get_claude_key(): string {
        $key = CredentialResolver::instance()->getKey( 'claude' );
        return is_string( $key ) ? $key : '';
    }

    /**
     * Whether local-DB writes for the given provider are allowed.
     *
     * Resolver source priority is env → constant → Connectors DB → Local DB.
     * When something higher in the chain owns the credential, writing to
     * aipt_*_api_key produces a stale orphan that the resolver will never
     * return. Block the write so direct callers cannot bypass the ApiTab UI
     * gate (which already disables editing for non-local sources).
     *
     * @since 2.6.0
     *
     * @param string $provider AIPT provider id (openai/gemini/claude/openrouter).
     * @return bool True when local write is allowed, false when blocked.
     */
    private static function localWriteAllowed( string $provider ): bool {
        $source = CredentialResolver::instance()->getSource( $provider );
        return ! in_array(
            $source,
            [ CredentialSource::ENV, CredentialSource::CONSTANT, CredentialSource::DATABASE_CONNECTORS ],
            true
        );
    }

	public static function flush_credential_caches(): void {
		CredentialResolver::instance()->flushMemoization();
		\AIPT\Api\AiAvailability\AiAvailabilityController::clearCache();
	}
    
    /**
     * Set OpenAI API key (tries encryption, falls back to plaintext)
     * 
     * @param string $api_key Plain text API key
     * @return bool True if successfully stored (encrypted or plaintext)
     */
    public static function set_openai_key(string $api_key): bool {
        if (!self::localWriteAllowed('openai')) {
            return false;
        }
        if (empty($api_key)) {
            $result = update_option('aipt_openai_api_key', '');
            if ($result) {
                self::flush_credential_caches();
            }
            return $result;
        }
        

        try {
            $encrypted_key = Encryption::encrypt_api_key($api_key);
            

            if (Encryption::is_encrypted($encrypted_key) && $encrypted_key !== $api_key) {

                $test_decrypt = Encryption::decrypt_api_key($encrypted_key);
                if ($test_decrypt === $api_key) {

                    $result = update_option('aipt_openai_api_key', $encrypted_key);
                    if ($result) {
                        self::flush_credential_caches();
                        return true;
                    }
                }
            }
        } catch (\Exception $e) {
        }
        

        $result = update_option('aipt_openai_api_key', $api_key);
        if ($result) {
            self::flush_credential_caches();
        }
        return $result;
    }
    
    /**
     * Set Gemini API key (tries encryption, falls back to plaintext)
     * 
     * @param string $api_key Plain text API key
     * @return bool True if successfully stored (encrypted or plaintext)
     */
    public static function set_gemini_key(string $api_key): bool {
        if (!self::localWriteAllowed('gemini')) {
            return false;
        }
        if (empty($api_key)) {
            $result = update_option('aipt_gemini_api_key', '');
            if ($result) {
                self::flush_credential_caches();
            }
            return $result;
        }
        

        try {
            $encrypted_key = Encryption::encrypt_api_key($api_key);
            

            if (Encryption::is_encrypted($encrypted_key) && $encrypted_key !== $api_key) {

                $test_decrypt = Encryption::decrypt_api_key($encrypted_key);
                if ($test_decrypt === $api_key) {

                    $result = update_option('aipt_gemini_api_key', $encrypted_key);
                    if ($result) {
                        self::flush_credential_caches();
                        return true;
                    }
                }
            }
        } catch (\Exception $e) {
        }
        

        $result = update_option('aipt_gemini_api_key', $api_key);
        if ($result) {
            self::flush_credential_caches();
        }
        return $result;
    }
    
    /**
     * Set OpenRouter API key (tries encryption, falls back to plaintext)
     * 
     * @param string $api_key Plain text API key
     * @return bool True if successfully stored (encrypted or plaintext)
     */
    public static function set_openrouter_key(string $api_key): bool {
        if (!self::localWriteAllowed('openrouter')) {
            return false;
        }
        if (empty($api_key)) {
            $result = update_option('aipt_openrouter_api_key', '');
            if ($result) {
                self::flush_credential_caches();
            }
            return $result;
        }
        

        try {
            $encrypted_key = Encryption::encrypt_api_key($api_key);
            

            if (Encryption::is_encrypted($encrypted_key) && $encrypted_key !== $api_key) {

                $test_decrypt = Encryption::decrypt_api_key($encrypted_key);
                if ($test_decrypt === $api_key) {

                    $result = update_option('aipt_openrouter_api_key', $encrypted_key);
                    if ($result) {
                        self::flush_credential_caches();
                        return true;
                    }
                }
            }
        } catch (\Exception $e) {
        }
        

        $result = update_option('aipt_openrouter_api_key', $api_key);
        if ($result) {
            self::flush_credential_caches();
        }
        return $result;
    }
    
    /**
     * Set Claude API key (tries encryption, falls back to plaintext)
     * 
     * @param string $api_key Plain text API key
     * @return bool True if successfully stored (encrypted or plaintext)
     */
    public static function set_claude_key(string $api_key): bool {
        if (!self::localWriteAllowed('claude')) {
            return false;
        }
        if (empty($api_key)) {
            $result = update_option('aipt_claude_api_key', '');
            if ($result) {
                self::flush_credential_caches();
            }
            return $result;
        }
        
        try {
            $encrypted_key = Encryption::encrypt_api_key($api_key);
            
            if (Encryption::is_encrypted($encrypted_key) && $encrypted_key !== $api_key) {
                $test_decrypt = Encryption::decrypt_api_key($encrypted_key);
                if ($test_decrypt === $api_key) {
                    $result = update_option('aipt_claude_api_key', $encrypted_key);
                    if ($result) {
                        self::flush_credential_caches();
                        return true;
                    }
                }
            }
        } catch (\Exception $e) {
        }
        
        $result = update_option('aipt_claude_api_key', $api_key);
        if ($result) {
            self::flush_credential_caches();
        }
        return $result;
    }
    
    /**
     * Get API key for current provider (decrypted)
     * 
     * @return string Decrypted API key for current provider
     */
    public static function get_current_api_key(): string {
        $api_provider = get_option('aipt_api_provider', 'openai');

        $key = CredentialResolver::instance()->getKey(
            is_string($api_provider) ? self::normalize_provider($api_provider) : 'openai'
        );

        return is_string( $key ) ? $key : '';
    }
    
    /**
     * Set API key for specific provider (encrypts before storage)
     * 
     * @param string $provider API provider ('openai', 'gemini', or 'openrouter')
     * @param string $api_key Plain text API key
     * @return bool True if successfully stored
     */
    public static function set_api_key(string $provider, string $api_key): bool {
        if ($provider === 'gemini') {
            return self::set_gemini_key($api_key);
        } elseif ($provider === 'openrouter') {
            return self::set_openrouter_key($api_key);
        } elseif ($provider === 'claude') {
            return self::set_claude_key($api_key);
        } else {
            return self::set_openai_key($api_key);
        }
    }
    
    /**
     * Check if API key exists for provider
     * 
     * @param string $provider API provider ('openai', 'gemini', or 'openrouter')
     * @return bool True if API key exists and is not empty
     */
    public static function has_api_key(string $provider): bool {
        return CredentialResolver::instance()->isConfigured( self::normalize_provider( $provider ) );
    }
    
    /**
     * Check if any API key is configured
     * 
     * @return bool True if at least one API key is configured
     */
    public static function has_any_api_key(): bool {
        return CredentialResolver::instance()->hasAnyConfiguredProvider();
    }
    
    /**
     * Check if current provider has API key
     * 
     * @return bool True if current provider has API key
     */
    public static function has_current_api_key(): bool {
        $api_provider = get_option('aipt_api_provider', 'openai');

        return CredentialResolver::instance()->isConfigured(
            is_string($api_provider) ? self::normalize_provider($api_provider) : 'openai'
        );
    }
    
    /**
     * Get raw encrypted API key (for internal use only)
     * 
     * @param string $provider API provider ('openai', 'gemini', or 'openrouter')
     * @return string Raw encrypted API key from database
     */
    public static function get_raw_api_key(string $provider): string {
        if ($provider === 'gemini') {
            return get_option('aipt_gemini_api_key', '');
        } elseif ($provider === 'openrouter') {
            return get_option('aipt_openrouter_api_key', '');
        } elseif ($provider === 'claude') {
            return get_option('aipt_claude_api_key', '');
        } else {
            return get_option('aipt_openai_api_key', '');
        }
    }
    
    /**
     * Check if API key is encrypted
     * 
     * @param string $provider API provider ('openai', 'gemini', or 'openrouter')
     * @return bool True if API key is encrypted
     */
    public static function is_api_key_encrypted(string $provider): bool {
        $raw_key = self::get_raw_api_key($provider);
        return Encryption::is_encrypted($raw_key);
    }
    
    /**
     * Get API key for display in settings (with plaintext fallback support)
     *
     * Since 2.6.0, Connectors-managed credentials return a source badge while
     * local and unconfigured sources continue to use the legacy local option display.
     * 
     * @param string $provider API provider ('openai', 'gemini', or 'openrouter')
     * @return string API key for display (handles encryption failures gracefully)
     */
    public static function get_api_key_for_display(string $provider): string {
        $source = CredentialResolver::instance()->getSource( self::normalize_provider( $provider ) );

        if (
            CredentialSource::ENV === $source
            || CredentialSource::CONSTANT === $source
            || CredentialSource::DATABASE_CONNECTORS === $source
        ) {
            return CredentialSource::badgeText( $source );
        }

        $raw_key = self::get_raw_api_key($provider);
        
        if (empty($raw_key)) {
            return '';
        }
        

        if (strpos($raw_key, 'ENCRYPTED_FALLBACK_NEEDS_REENTRY') !== false) {
            return ''; 
        }
        

        if (!Encryption::is_encrypted($raw_key)) {
            return $raw_key;
        }
        
        return self::get_local_api_key( $provider );
    }
    
    /**
     * Get masked API key for display purposes
     * 
     * @param string $provider API provider ('openai', 'gemini', or 'openrouter')
     * @return string Masked API key (shows only first 8 and last 4 characters)
     */
    public static function get_masked_api_key(string $provider): string {
        $api_key = self::get_local_api_key( $provider );
        
        if (empty($api_key)) {
            return '';
        }
        
        $length = strlen($api_key);
        if ($length <= 12) {
            return substr($api_key, 0, 2) . str_repeat('*', $length - 4) . substr($api_key, -2);
        }
        
        return substr($api_key, 0, 8) . str_repeat('*', $length - 12) . substr($api_key, -4);
    }
    
    /**
     * Validate API key format (basic validation)
     * 
     * @param string $provider API provider ('openai', 'gemini', or 'openrouter')
     * @param string $api_key API key to validate
     * @return bool True if format appears valid
     */
    public static function validate_api_key_format(string $provider, string $api_key): bool {
        if (empty($api_key)) {
            return false;
        }
        
        if ($provider === 'openai') {
            return preg_match('/^sk-[a-zA-Z0-9]{48}$/', $api_key) === 1;
        } elseif ($provider === 'gemini') {
            return preg_match('/^AI[a-zA-Z0-9_-]{37}$/', $api_key) === 1;
        } elseif ($provider === 'openrouter') {
            return preg_match('/^sk-or-[a-zA-Z0-9]{32,}$/', $api_key) === 1;
        } elseif ($provider === 'claude') {

            return preg_match('/^sk-ant-(api03-|admin-?)[a-zA-Z0-9_-]{40,}$/', $api_key) === 1;
        }
        
        return false;
    }
    
    /**
     * Clear API key for provider
     * 
     * @param string $provider API provider ('openai', 'gemini', or 'openrouter')
     * @return bool True if successfully cleared
     */
    public static function clear_api_key(string $provider): bool {
        if ($provider === 'gemini') {
            $result = update_option('aipt_gemini_api_key', '');
        } elseif ($provider === 'openrouter') {
            $result = update_option('aipt_openrouter_api_key', '');
        } elseif ($provider === 'claude') {
            $result = update_option('aipt_claude_api_key', '');
        } else {
            $result = update_option('aipt_openai_api_key', '');
        }

        if ($result) {
            self::flush_credential_caches();
        }

        return $result;
    }
    
    /**
     * Clear all API keys
     * 
     * @return bool True if all keys successfully cleared
     */
    public static function clear_all_api_keys(): bool {
        $openai_cleared = self::clear_api_key('openai');
        $gemini_cleared = self::clear_api_key('gemini');
        $openrouter_cleared = self::clear_api_key('openrouter');
        $claude_cleared = self::clear_api_key('claude');
        
        return $openai_cleared && $gemini_cleared && $openrouter_cleared && $claude_cleared;
    }
    
    /**
     * Migrate existing plain text keys to encrypted format
     * This method ensures backward compatibility during updates
     * 
     * @return bool True if migration was successful or not needed
     */
    public static function migrate_keys_if_needed(): bool {

        if (Encryption::is_migration_completed()) {
            return true;
        }
        

        return Encryption::migrate_existing_keys();
    }
    
    /**
     * Get encryption status for API keys
     * 
     * @return array Status of encryption for all providers
     */
    public static function get_encryption_status(): array {
        return [
            'openai_encrypted' => self::is_api_key_encrypted('openai'),
            'gemini_encrypted' => self::is_api_key_encrypted('gemini'),
            'openrouter_encrypted' => self::is_api_key_encrypted('openrouter'),
            'claude_encrypted' => self::is_api_key_encrypted('claude'),
            'migration_completed' => Encryption::is_migration_completed(),
            'openssl_available' => extension_loaded('openssl')
        ];
    }

    /**
     * Get the resolved legacy-local API key for diagnostic display helpers.
     *
     * @param string $provider API provider ('openai', 'gemini', 'claude', or 'openrouter')
     * @return string Local API key or empty string when unavailable
     */
    private static function get_local_api_key(string $provider): string {
        $key = (new LocalCredentialProvider())->getKey( self::normalize_provider( $provider ) );

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

    /**
     * Normalize provider IDs to the legacy ApiKeyManager fallback behavior.
     *
     * @param string $provider API provider candidate
     * @return string Supported AIPT provider ID
     */
    private static function normalize_provider(string $provider): string {
        return in_array( $provider, array( 'openai', 'gemini', 'claude', 'openrouter' ), true )
            ? $provider
            : 'openai';
    }
} 
