/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 */

<?php

defined('BASEPATH') or exit('No direct script access allowed');

/**
 * Token Manager Library
 * 
 * Handles secure token storage and management with AES-256 encryption
 * 
 * @package     DeskMoloni
 * @author      Descomplicar®
 * @copyright   2025 Descomplicar
 * @version     3.0.0
 */
class TokenManager
{
    private $CI;
    
    // Encryption configuration
    private $cipher = 'AES-256-CBC';
    private $key_size = 32; // 256 bits
    private $iv_size = 16;  // 128 bits
    
    // Token storage keys
    private $access_token_key = 'desk_moloni_access_token_encrypted';
    private $refresh_token_key = 'desk_moloni_refresh_token_encrypted';
    private $token_expires_key = 'desk_moloni_token_expires';
    private $token_scope_key = 'desk_moloni_token_scope';
    private $encryption_key_option = 'desk_moloni_encryption_key';
    
    public function __construct()
    {
        $this->CI = &get_instance();
        
        // Ensure encryption key exists
        $this->ensure_encryption_key();
    }
    
    /**
     * Save OAuth tokens securely
     * 
     * @param array $token_data Token response from OAuth
     * @return bool Success status
     */
    public function save_tokens($token_data)
    {
        try {
            // Validate required fields
            if (!isset($token_data['access_token'])) {
                throw new Exception('Access token is required');
            }
            
            // Calculate expiration time with 60-second buffer
            $expires_in = isset($token_data['expires_in']) ? (int)$token_data['expires_in'] : 3600;
            $expires_at = time() + $expires_in - 60;
            
            // Encrypt and save access token
            $encrypted_access = $this->encrypt($token_data['access_token']);
            update_option($this->access_token_key, $encrypted_access);
            
            // Encrypt and save refresh token if provided
            if (isset($token_data['refresh_token'])) {
                $encrypted_refresh = $this->encrypt($token_data['refresh_token']);
                update_option($this->refresh_token_key, $encrypted_refresh);
            }
            
            // Save expiration and scope
            update_option($this->token_expires_key, $expires_at);
            update_option($this->token_scope_key, $token_data['scope'] ?? '');
            
            // Log successful token save
            log_activity('Desk-Moloni: OAuth tokens saved securely');
            
            return true;
            
        } catch (Exception $e) {
            log_activity('Desk-Moloni: Token save failed - ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Get decrypted access token
     * 
     * @return string|null Access token or null if not available
     */
    public function get_access_token()
    {
        try {
            $encrypted_token = get_option($this->access_token_key);
            
            if (empty($encrypted_token)) {
                return null;
            }
            
            return $this->decrypt($encrypted_token);
            
        } catch (Exception $e) {
            log_activity('Desk-Moloni: Access token decryption failed - ' . $e->getMessage());
            return null;
        }
    }
    
    /**
     * Get decrypted refresh token
     * 
     * @return string|null Refresh token or null if not available
     */
    public function get_refresh_token()
    {
        try {
            $encrypted_token = get_option($this->refresh_token_key);
            
            if (empty($encrypted_token)) {
                return null;
            }
            
            return $this->decrypt($encrypted_token);
            
        } catch (Exception $e) {
            log_activity('Desk-Moloni: Refresh token decryption failed - ' . $e->getMessage());
            return null;
        }
    }
    
    /**
     * Check if tokens are valid and not expired
     * 
     * @return bool Token validity status
     */
    public function are_tokens_valid()
    {
        // Check if access token exists
        if (empty($this->get_access_token())) {
            return false;
        }
        
        // Check expiration
        $expires_at = get_option($this->token_expires_key);
        if ($expires_at && time() >= $expires_at) {
            return false;
        }
        
        return true;
    }
    
    /**
     * Check if tokens are close to expiring (within 5 minutes)
     * 
     * @return bool True if tokens expire soon
     */
    public function tokens_expire_soon()
    {
        $expires_at = get_option($this->token_expires_key);
        
        if (!$expires_at) {
            return false;
        }
        
        return (time() + 300) >= $expires_at; // 5 minutes
    }
    
    /**
     * Get token expiration timestamp
     * 
     * @return int|null Expiration timestamp or null
     */
    public function get_token_expiration()
    {
        return get_option($this->token_expires_key) ?: null;
    }
    
    /**
     * Get token scope
     * 
     * @return string Token scope
     */
    public function get_token_scope()
    {
        return get_option($this->token_scope_key) ?: '';
    }
    
    /**
     * Clear all stored tokens
     * 
     * @return bool Success status
     */
    public function clear_tokens()
    {
        try {
            update_option($this->access_token_key, '');
            update_option($this->refresh_token_key, '');
            update_option($this->token_expires_key, '');
            update_option($this->token_scope_key, '');
            
            log_activity('Desk-Moloni: OAuth tokens cleared');
            
            return true;
            
        } catch (Exception $e) {
            log_activity('Desk-Moloni: Token clear failed - ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Get comprehensive token status
     * 
     * @return array Token status information
     */
    public function get_token_status()
    {
        $expires_at = $this->get_token_expiration();
        
        return [
            'has_access_token' => !empty($this->get_access_token()),
            'has_refresh_token' => !empty($this->get_refresh_token()),
            'is_valid' => $this->are_tokens_valid(),
            'expires_soon' => $this->tokens_expire_soon(),
            'expires_at' => $expires_at,
            'expires_in' => $expires_at ? max(0, $expires_at - time()) : 0,
            'scope' => $this->get_token_scope(),
            'formatted_expiry' => $expires_at ? date('Y-m-d H:i:s', $expires_at) : null
        ];
    }
    
    /**
     * Encrypt data using AES-256-CBC
     * 
     * @param string $data Data to encrypt
     * @return string Base64 encoded encrypted data with IV
     */
    private function encrypt($data)
    {
        if (empty($data)) {
            return '';
        }
        
        $key = $this->get_encryption_key();
        $iv = random_bytes($this->iv_size);
        
        $encrypted = openssl_encrypt($data, $this->cipher, $key, OPENSSL_RAW_DATA, $iv);
        
        if ($encrypted === false) {
            throw new Exception('Encryption failed');
        }
        
        // Prepend IV to encrypted data and encode
        return base64_encode($iv . $encrypted);
    }
    
    /**
     * Decrypt data using AES-256-CBC
     * 
     * @param string $encrypted_data Base64 encoded encrypted data with IV
     * @return string Decrypted data
     */
    private function decrypt($encrypted_data)
    {
        if (empty($encrypted_data)) {
            return '';
        }
        
        $data = base64_decode($encrypted_data);
        
        if ($data === false || strlen($data) < $this->iv_size) {
            throw new Exception('Invalid encrypted data');
        }
        
        $key = $this->get_encryption_key();
        $iv = substr($data, 0, $this->iv_size);
        $encrypted = substr($data, $this->iv_size);
        
        $decrypted = openssl_decrypt($encrypted, $this->cipher, $key, OPENSSL_RAW_DATA, $iv);
        
        if ($decrypted === false) {
            throw new Exception('Decryption failed');
        }
        
        return $decrypted;
    }
    
    /**
     * Get or generate encryption key
     * 
     * @return string Encryption key
     */
    private function get_encryption_key()
    {
        $key = get_option($this->encryption_key_option);
        
        if (empty($key)) {
            throw new Exception('Encryption key not found');
        }
        
        return base64_decode($key);
    }
    
    /**
     * Ensure encryption key exists
     */
    private function ensure_encryption_key()
    {
        $existing_key = get_option($this->encryption_key_option);
        
        if (empty($existing_key)) {
            // Generate new random key
            $key = random_bytes($this->key_size);
            $encoded_key = base64_encode($key);
            
            update_option($this->encryption_key_option, $encoded_key);
            
            log_activity('Desk-Moloni: New encryption key generated');
        }
    }
    
    /**
     * Rotate encryption key (for security maintenance)
     * WARNING: This will invalidate all existing tokens
     * 
     * @return bool Success status
     */
    public function rotate_encryption_key()
    {
        try {
            // Clear existing tokens first
            $this->clear_tokens();
            
            // Generate new key
            $new_key = random_bytes($this->key_size);
            $encoded_key = base64_encode($new_key);
            
            update_option($this->encryption_key_option, $encoded_key);
            
            log_activity('Desk-Moloni: Encryption key rotated - all tokens cleared');
            
            return true;
            
        } catch (Exception $e) {
            log_activity('Desk-Moloni: Key rotation failed - ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Validate encryption setup
     * 
     * @return array Validation results
     */
    public function validate_encryption()
    {
        $issues = [];
        
        // Check if OpenSSL is available
        if (!extension_loaded('openssl')) {
            $issues[] = 'OpenSSL extension not loaded';
        }
        
        // Check if cipher is supported
        if (!in_array($this->cipher, openssl_get_cipher_methods())) {
            $issues[] = 'AES-256-CBC cipher not supported';
        }
        
        // Check if encryption key exists
        try {
            $this->get_encryption_key();
        } catch (Exception $e) {
            $issues[] = 'Encryption key not available: ' . $e->getMessage();
        }
        
        // Test encryption/decryption
        try {
            $test_data = 'test_token_' . time();
            $encrypted = $this->encrypt($test_data);
            $decrypted = $this->decrypt($encrypted);
            
            if ($decrypted !== $test_data) {
                $issues[] = 'Encryption/decryption test failed';
            }
        } catch (Exception $e) {
            $issues[] = 'Encryption test failed: ' . $e->getMessage();
        }
        
        return [
            'is_valid' => empty($issues),
            'issues' => $issues,
            'cipher' => $this->cipher,
            'openssl_loaded' => extension_loaded('openssl'),
            'supported_ciphers' => openssl_get_cipher_methods()
        ];
    }
}