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

<?php

/**
 * Desk_moloni_model.php
 * 
 * Base model for Desk-Moloni v3.0 integration
 * Provides common functionality for all Desk-Moloni models
 * 
 * @package DeskMoloni\Models
 * @author Database Design Specialist
 * @version 3.0
 */

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

class Desk_moloni_model extends App_Model
{
    /**
     * AES-256-GCM encryption key (should be stored securely in config)
     */
    private $encryptionKey;
    
    /**
     * Table prefix for all Desk-Moloni tables (follows Perfex CRM convention)
     */
    protected $tablePrefix = 'tbldeskmoloni_';
    
    public function __construct()
    {
        parent::__construct();
        
        // Load encryption library
        $this->load->library('encryption');
        
        // Initialize encryption key (should be from secure config)
        $this->encryptionKey = $this->getEncryptionKey();
        
        // Load database
        $this->load->database();
    }
    
    /**
     * Get secure encryption key
     * 
     * @return string
     */
    private function getEncryptionKey(): string
    {
        // In production, this should come from secure configuration
        // For now, using app key with salt
        $appKey = get_option('encryption_key') ?: 'desk_moloni_default_key';
        return hash('sha256', $appKey . 'desk_moloni_salt_v3', true);
    }
    
    /**
     * Encrypt sensitive data using AES-256-GCM
     * 
     * @param string $data Data to encrypt
     * @return string Encrypted data with nonce
     */
    protected function encryptData(string $data): string
    {
        if (empty($data)) {
            return $data;
        }
        
        try {
            // Generate random nonce
            $nonce = random_bytes(12); // 96-bit nonce for GCM
            
            // Encrypt data
            $encrypted = openssl_encrypt(
                $data,
                'aes-256-gcm',
                $this->encryptionKey,
                OPENSSL_RAW_DATA,
                $nonce,
                $tag
            );
            
            if ($encrypted === false) {
                throw new Exception('Encryption failed');
            }
            
            // Combine nonce + tag + encrypted data and base64 encode
            return base64_encode($nonce . $tag . $encrypted);
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni encryption error: ' . $e->getMessage());
            throw new Exception('Failed to encrypt sensitive data');
        }
    }
    
    /**
     * Decrypt sensitive data using AES-256-GCM
     * 
     * @param string $encryptedData Encrypted data with nonce
     * @return string Decrypted data
     */
    protected function decryptData(string $encryptedData): string
    {
        if (empty($encryptedData)) {
            return $encryptedData;
        }
        
        try {
            // Decode base64
            $data = base64_decode($encryptedData);
            
            if ($data === false || strlen($data) < 28) { // 12 + 16 + at least some data
                throw new Exception('Invalid encrypted data format');
            }
            
            // Extract components
            $nonce = substr($data, 0, 12);
            $tag = substr($data, 12, 16);
            $encrypted = substr($data, 28);
            
            // Decrypt data
            $decrypted = openssl_decrypt(
                $encrypted,
                'aes-256-gcm',
                $this->encryptionKey,
                OPENSSL_RAW_DATA,
                $nonce,
                $tag
            );
            
            if ($decrypted === false) {
                throw new Exception('Decryption failed - data may be corrupted');
            }
            
            return $decrypted;
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni decryption error: ' . $e->getMessage());
            throw new Exception('Failed to decrypt sensitive data');
        }
    }
    
    /**
     * Validate JSON data
     * 
     * @param string $jsonString JSON string to validate
     * @return bool True if valid JSON
     */
    protected function validateJSON(string $jsonString): bool
    {
        if ($jsonString === null || $jsonString === '') {
            return true; // NULL and empty strings are valid
        }
        
        json_decode($jsonString);
        return json_last_error() === JSON_ERROR_NONE;
    }
    
    /**
     * Validate ENUM values
     * 
     * @param string $value Value to validate
     * @param array $allowedValues Array of allowed ENUM values
     * @return bool True if value is valid
     */
    protected function validateEnum(string $value, array $allowedValues): bool
    {
        return in_array($value, $allowedValues, true);
    }
    
    /**
     * Get table name with prefix
     * 
     * @param string $tableSuffix Table suffix (e.g., 'config', 'mapping')
     * @return string Full table name
     */
    protected function getTableName(string $tableSuffix): string
    {
        return $this->tablePrefix . $tableSuffix;
    }
    
    /**
     * Log database operations for audit trail
     * 
     * @param string $operation Operation type (create, update, delete)
     * @param string $table Table name
     * @param array $data Operation data
     * @param int|null $recordId Record ID if applicable
     */
    protected function logDatabaseOperation(string $operation, string $table, array $data, ?int $recordId = null): void
    {
        try {
            $logData = [
                'operation' => $operation,
                'table_name' => $table,
                'record_id' => $recordId,
                'data_snapshot' => json_encode($data),
                'user_id' => get_staff_user_id(),
                'ip_address' => $this->input->ip_address(),
                'user_agent' => $this->input->user_agent(),
                'created_at' => date('Y-m-d H:i:s')
            ];
            
            // Insert into audit log (if table exists)
            if ($this->db->table_exists($this->getTableName('audit_log'))) {
                $this->db->insert($this->getTableName('audit_log'), $logData);
            }
            
        } catch (Exception $e) {
            // Don't fail the main operation if logging fails
            log_message('error', 'Desk-Moloni audit log error: ' . $e->getMessage());
        }
    }
    
    /**
     * Validate required fields
     * 
     * @param array $data Data to validate
     * @param array $requiredFields Required field names
     * @return array Validation errors (empty if valid)
     */
    protected function validateRequiredFields(array $data, array $requiredFields): array
    {
        $errors = [];
        
        foreach ($requiredFields as $field) {
            if (!isset($data[$field]) || $data[$field] === '' || $data[$field] === null) {
                $errors[] = "Field '{$field}' is required";
            }
        }
        
        return $errors;
    }
    
    /**
     * Validate field lengths
     * 
     * @param array $data Data to validate
     * @param array $fieldLimits Field length limits ['field' => max_length]
     * @return array Validation errors
     */
    protected function validateFieldLengths(array $data, array $fieldLimits): array
    {
        $errors = [];
        
        foreach ($fieldLimits as $field => $maxLength) {
            if (isset($data[$field]) && strlen($data[$field]) > $maxLength) {
                $errors[] = "Field '{$field}' exceeds maximum length of {$maxLength} characters";
            }
        }
        
        return $errors;
    }
    
    /**
     * Sanitize data for database insertion
     * 
     * @param array $data Data to sanitize
     * @return array Sanitized data
     */
    protected function sanitizeData(array $data): array
    {
        $sanitized = [];
        
        foreach ($data as $key => $value) {
            if (is_string($value)) {
                // Trim whitespace and sanitize
                $sanitized[$key] = trim($value);
            } else {
                $sanitized[$key] = $value;
            }
        }
        
        return $sanitized;
    }
    
    /**
     * Check if table exists
     * 
     * @param string $tableName Table name to check
     * @return bool True if table exists
     */
    protected function tableExists(string $tableName): bool
    {
        return $this->db->table_exists($tableName);
    }
    
    /**
     * Execute transaction with rollback on failure
     * 
     * @param callable $callback Function to execute in transaction
     * @return mixed Result of callback or false on failure
     */
    protected function executeTransaction(callable $callback): mixed
    {
        $this->db->trans_begin();
        
        try {
            $result = $callback();
            
            if ($this->db->trans_status() === false) {
                throw new Exception('Transaction failed');
            }
            
            $this->db->trans_commit();
            return $result;
            
        } catch (Exception $e) {
            $this->db->trans_rollback();
            log_message('error', 'Desk-Moloni transaction error: ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Get human-readable timestamp
     * 
     * @param string $timestamp Database timestamp
     * @return string Formatted timestamp
     */
    protected function formatTimestamp($timestamp): ?string
    {
        if (empty($timestamp) || $timestamp === '0000-00-00 00:00:00') {
            return null;
        }
        
        return date('Y-m-d H:i:s', strtotime($timestamp));
    }
    
    /**
     * Check if current user has permission for operation
     * 
     * @param string $permission Permission to check
     * @return bool True if user has permission
     */
    protected function hasPermission(string $permission): bool
    {
        // Check if user is admin or has specific permission
        if (is_admin()) {
            return true;
        }
        
        // Check module-specific permissions
        return has_permission($permission, '', 'view') || has_permission($permission, '', 'create');
    }
    
    /**
     * Get current user ID
     * 
     * @return int|null User ID or null if not logged in
     */
    protected function getCurrentUserId()
    {
        return get_staff_user_id();
    }
}