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

<?php

/**
 * Desk_moloni_sync_queue_model.php
 * 
 * Model for desk_moloni_sync_queue table
 * Handles asynchronous task queue for synchronization operations
 * 
 * @package DeskMoloni\Models
 * @author Database Design Specialist
 * @version 3.0
 */

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

require_once(dirname(__FILE__) . '/Desk_moloni_model.php');

class Desk_moloni_sync_queue_model extends Desk_moloni_model
{
    /**
     * Table name - must match Perfex CRM naming convention
     */
    private $table = 'tbldeskmoloni_sync_queue';
    
    /**
     * Valid task types (mapped from actions)
     */
    private $validTaskTypes = [
        'sync_client', 'sync_product', 'sync_invoice', 
        'sync_estimate', 'sync_credit_note', 'status_update'
    ];
    
    /**
     * Valid actions (database schema)
     */
    // Kept for backward compatibility with older schemas (not used in current schema)
    private $validActions = [
        'create', 'update', 'delete', 'sync'
    ];
    
    /**
     * Valid entity types
     */
    private $validEntityTypes = [
        'client', 'product', 'invoice', 'estimate', 'credit_note'
    ];
    
    /**
     * Valid task status values
     */
    private $validStatuses = [
        'pending', 'processing', 'completed', 'failed', 'retry'
    ];
    
    /**
     * Maximum priority value
     */
    private $maxPriority = 9;
    
    /**
     * Minimum priority value
     */
    private $minPriority = 1;
    
    public function __construct()
    {
        parent::__construct();
        // Use Perfex CRM table naming convention: tbl + module_prefix + table_name
        $this->table = 'tbldeskmoloni_sync_queue';
    }
    
    /**
     * Add task to sync queue
     * 
     * @param string $taskType Task type
     * @param string $entityType Entity type
     * @param int $entityId Entity ID
     * @param array $payload Task payload data
     * @param int $priority Task priority (1=highest, 9=lowest)
     * @param string $scheduledAt When to schedule the task (defaults to now)
     * @return int|false Task ID or false on failure
     */
    public function addTask($taskType, $entityType, $entityId, $payload = [], $priority = 5, $scheduledAt = null)
    {
        try {
            if ($scheduledAt === null) {
                $scheduledAt = date('Y-m-d H:i:s');
            }
            
            $data = [
                'task_type'   => $taskType,
                'entity_type' => $entityType,
                'entity_id'   => (int)$entityId,
                'priority'    => $this->clampPriority((int)$priority),
                'payload'     => !empty($payload) ? json_encode($payload) : null,
                'status'      => 'pending',
                'attempts' => 0,
                'max_attempts' => 3,
                'scheduled_at' => $scheduledAt,
                'created_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s')
            ];
            
            // Validate data
            $validationErrors = $this->validateTaskData($data);
            if (!empty($validationErrors)) {
                throw new Exception('Validation failed: ' . implode(', ', $validationErrors));
            }
            
            // Check for duplicate pending tasks
            if ($this->hasPendingTask($entityType, $entityId, $taskType)) {
                log_message('info', "Duplicate task ignored: {$taskType} for {$entityType} #{$entityId}");
                return false;
            }
            
            $result = $this->db->insert($this->table, $data);
            
            if ($result) {
                $taskId = $this->db->insert_id();
                $this->logDatabaseOperation('create', $this->table, $data, $taskId);
                return $taskId;
            }
            
            return false;
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue add task error: ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Compatibility method for CodeIgniter snake_case convention
     * 
     * @param mixed $taskData Task data (array or individual parameters)
     * @return int|false Task ID or false on failure
     */
    public function add_task($taskData)
    {
        // Handle both array and individual parameter formats
        if (is_array($taskData)) {
            return $this->addTask(
                $taskData['task_type'] ?? $taskData['type'],
                $taskData['entity_type'],
                $taskData['entity_id'],
                $taskData['payload'] ?? [],
                $taskData['priority'] ?? 5,
                $taskData['scheduled_at'] ?? null
            );
        } else {
            // Legacy signature with individual parameters
            $args = func_get_args();
            return $this->addTask(
                $args[0] ?? '', // task_type
                $args[1] ?? '', // entity_type
                $args[2] ?? 0,  // entity_id
                $args[3] ?? [], // payload
                $args[4] ?? 5,  // priority
                $args[5] ?? null // scheduled_at
            );
        }
    }
    
    /**
     * Get next pending tasks for processing
     * 
     * @param int $limit Maximum number of tasks to retrieve
     * @param array $taskTypes Optional filter by task types
     * @return array Array of task objects
     */
    public function getNextTasks($limit = 10, $taskTypes = null)
    {
        try {
            $this->db->where('status', 'pending')
                    ->where('scheduled_at <=', date('Y-m-d H:i:s'));
            
            if ($taskTypes !== null && is_array($taskTypes)) {
                $this->db->where_in('task_type', $taskTypes);
            }
            
            $query = $this->db->order_by('priority', 'ASC')
                             ->order_by('scheduled_at', 'ASC')
                             ->limit($limit)
                             ->get($this->table);
            
            return $query->result();
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue get next tasks error: ' . $e->getMessage());
            return [];
        }
    }
    
    /**
     * Start processing a task
     * 
     * @param int $taskId Task ID
     * @return bool Success status
     */
    public function startTask($taskId)
    {
        try {
            $data = [
                'status' => 'processing',
                'started_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s')
            ];
            
            $result = $this->db->where('id', (int)$taskId)
                              ->where('status', 'pending') // Only start if still pending
                              ->update($this->table, $data);
            
            if ($result && $this->db->affected_rows() > 0) {
                $this->logDatabaseOperation('update', $this->table, $data, $taskId);
                return true;
            }
            
            return false;
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue start task error: ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Complete a task successfully
     * 
     * @param int $taskId Task ID
     * @param array $result Optional result data
     * @return bool Success status
     */
    public function completeTask($taskId, $result = null)
    {
        try {
            $data = [
                'status' => 'completed',
                'completed_at' => date('Y-m-d H:i:s'),
                'updated_at' => date('Y-m-d H:i:s')
            ];
            
            // Add result to payload if provided
            if ($result !== null) {
                $task = $this->getTask($taskId);
                if ($task) {
                    $payloadData = json_decode($task->payload, true) ?: [];
                    $payloadData['result'] = $result;
                    $data['payload'] = json_encode($payloadData);
                }
            }
            
            $updateResult = $this->db->where('id', (int)$taskId)
                                   ->where('status', 'processing') // Only complete if processing
                                   ->update($this->table, $data);
            
            if ($updateResult && $this->db->affected_rows() > 0) {
                $this->logDatabaseOperation('update', $this->table, $data, $taskId);
                return true;
            }
            
            return false;
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue complete task error: ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Mark task as failed
     * 
     * @param int $taskId Task ID
     * @param string $errorMessage Error message
     * @param bool $retry Whether to schedule for retry
     * @return bool Success status
     */
    public function failTask($taskId, $errorMessage, $retry = true)
    {
        try {
            $task = $this->getTask($taskId);
            if (!$task) {
                return false;
            }
            
            $newAttempts = $task->attempts + 1;
            $shouldRetry = $retry && $newAttempts < $task->max_attempts;
            
            $data = [
                'attempts' => $newAttempts,
                'error_message' => $errorMessage,
                'updated_at' => date('Y-m-d H:i:s')
            ];
            
            if ($shouldRetry) {
                // Schedule for retry with exponential backoff
                $retryDelay = min(pow(2, $newAttempts) * 60, 3600); // Max 1 hour delay
                $data['status'] = 'retry';
                $data['scheduled_at'] = date('Y-m-d H:i:s', time() + $retryDelay);
            } else {
                // Mark as failed
                $data['status'] = 'failed';
                $data['completed_at'] = date('Y-m-d H:i:s');
            }
            
            $result = $this->db->where('id', (int)$taskId)->update($this->table, $data);
            
            if ($result) {
                $this->logDatabaseOperation('update', $this->table, $data, $taskId);
            }
            
            return $result;
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue fail task error: ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Reset retry tasks to pending status
     * 
     * @return int Number of tasks reset
     */
    public function resetRetryTasks()
    {
        try {
            $data = [
                'status' => 'pending',
                'updated_at' => date('Y-m-d H:i:s')
            ];
            
            $result = $this->db->where('status', 'retry')
                              ->where('scheduled_at <=', date('Y-m-d H:i:s'))
                              ->update($this->table, $data);
            
            return $this->db->affected_rows();
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue reset retry tasks error: ' . $e->getMessage());
            return 0;
        }
    }
    
    /**
     * Get task by ID
     * 
     * @param int $taskId Task ID
     * @return object|null Task object or null if not found
     */
    public function getTask($taskId)
    {
        try {
            $query = $this->db->where('id', (int)$taskId)->get($this->table);
            return $query->num_rows() > 0 ? $query->row() : null;
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue get task error: ' . $e->getMessage());
            return null;
        }
    }
    
    /**
     * Get tasks by entity
     * 
     * @param string $entityType Entity type
     * @param int $entityId Entity ID
     * @param string $status Optional status filter
     * @return array Array of task objects
     */
    public function getTasksByEntity($entityType, $entityId, $status = null)
    {
        try {
            $this->db->where('entity_type', $entityType)
                    ->where('entity_id', (int)$entityId);
            
            if ($status !== null) {
                $this->db->where('status', $status);
            }
            
            $query = $this->db->order_by('created_at', 'DESC')->get($this->table);
            
            return $query->result();
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue get tasks by entity error: ' . $e->getMessage());
            return [];
        }
    }
    
    /**
     * Cancel pending task
     * 
     * @param int $taskId Task ID
     * @return bool Success status
     */
    public function cancelTask($taskId)
    {
        try {
            $result = $this->db->where('id', (int)$taskId)
                              ->where('status', 'pending')
                              ->delete($this->table);
            
            if ($result && $this->db->affected_rows() > 0) {
                $this->logDatabaseOperation('delete', $this->table, ['id' => $taskId], $taskId);
                return true;
            }
            
            return false;
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue cancel task error: ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Clean up old completed/failed tasks
     * 
     * @param int $olderThanDays Delete tasks older than X days
     * @return int Number of tasks deleted
     */
    public function cleanupOldTasks($olderThanDays = 30)
    {
        try {
            $cutoffDate = date('Y-m-d H:i:s', strtotime("-{$olderThanDays} days"));
            
            $result = $this->db->where_in('status', ['completed', 'failed'])
                              ->where('completed_at <', $cutoffDate)
                              ->delete($this->table);
            
            return $this->db->affected_rows();
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue cleanup error: ' . $e->getMessage());
            return 0;
        }
    }
    
    /**
     * Get queue statistics
     * 
     * @return array Statistics array
     */
    public function getStatistics()
    {
        try {
            $stats = [];
            
            // By status
            foreach ($this->validStatuses as $status) {
                $stats['by_status'][$status] = $this->db->where('status', $status)
                                                       ->count_all_results($this->table);
            }
            
            // By task type
            foreach ($this->validTaskTypes as $taskType) {
                $stats['by_task_type'][$taskType] = $this->db->where('task_type', $taskType)
                                                            ->count_all_results($this->table);
            }
            
            // By entity type
            foreach ($this->validEntityTypes as $entityType) {
                $stats['by_entity_type'][$entityType] = $this->db->where('entity_type', $entityType)
                                                                ->count_all_results($this->table);
            }
            
            // Processing times (average for completed tasks in last 24 hours)
            $yesterday = date('Y-m-d H:i:s', strtotime('-24 hours'));
            $query = $this->db->select('AVG(TIMESTAMPDIFF(SECOND, started_at, completed_at)) as avg_processing_time')
                             ->where('status', 'completed')
                             ->where('completed_at >', $yesterday)
                             ->where('started_at IS NOT NULL')
                             ->get($this->table);
            
            $stats['avg_processing_time_seconds'] = $query->row()->avg_processing_time ?: 0;
            
            // Failed tasks in last 24 hours
            $stats['failed_24h'] = $this->db->where('status', 'failed')
                                           ->where('completed_at >', $yesterday)
                                           ->count_all_results($this->table);
            
            return $stats;
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue statistics error: ' . $e->getMessage());
            return [];
        }
    }
    
    /**
     * Check if entity has pending task of specific type
     * 
     * @param string $entityType Entity type
     * @param int $entityId Entity ID
     * @param string $taskType Task type
     * @return bool True if pending task exists
     */
    public function hasPendingTask($entityType, $entityId, $taskType)
    {
        try {
            $count = $this->db->where('entity_type', $entityType)
                             ->where('entity_id', (int)$entityId)
                             ->where('task_type', $taskType)
                             ->where('status', 'pending')
                             ->count_all_results($this->table);
            
            return $count > 0;
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue has pending task error: ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Update task priority
     * 
     * @param int $taskId Task ID
     * @param int $priority New priority (1-9)
     * @return bool Success status
     */
    public function updatePriority($taskId, $priority)
    {
        try {
            $priority = max($this->minPriority, min($this->maxPriority, (int)$priority));
            
            $data = [
                'priority' => $priority,
                'updated_at' => date('Y-m-d H:i:s')
            ];
            
            $result = $this->db->where('id', (int)$taskId)
                              ->where('status', 'pending') // Only update pending tasks
                              ->update($this->table, $data);
            
            if ($result && $this->db->affected_rows() > 0) {
                $this->logDatabaseOperation('update', $this->table, $data, $taskId);
                return true;
            }
            
            return false;
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue update priority error: ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Validate task data
     * 
     * @param array $data Task data to validate
     * @return array Validation errors
     */
    private function validateTaskData($data)
    {
        $errors = [];
        
        // Required fields
        $requiredFields = ['entity_type', 'entity_id', 'action'];
        $errors = array_merge($errors, $this->validateRequiredFields($data, $requiredFields));
        
        // Task type validation (database schema)
        if (isset($data['task_type']) && !$this->validateEnum($data['task_type'], $this->validTaskTypes)) {
            $errors[] = 'Invalid task type. Must be one of: ' . implode(', ', $this->validTaskTypes);
        }
        
        // Entity type validation
        if (isset($data['entity_type']) && !$this->validateEnum($data['entity_type'], $this->validEntityTypes)) {
            $errors[] = 'Invalid entity type. Must be one of: ' . implode(', ', $this->validEntityTypes);
        }
        
        // Status validation
        if (isset($data['status']) && !$this->validateEnum($data['status'], $this->validStatuses)) {
            $errors[] = 'Invalid status. Must be one of: ' . implode(', ', $this->validStatuses);
        }
        
        // Priority validation
        if (isset($data['priority'])) {
            $priority = (int)$data['priority'];
            if ($priority < $this->minPriority || $priority > $this->maxPriority) {
                $errors[] = "Priority must be between {$this->minPriority} and {$this->maxPriority}";
            }
        }
        
        // Entity ID validation
        if (isset($data['entity_id']) && (!is_numeric($data['entity_id']) || (int)$data['entity_id'] <= 0)) {
            $errors[] = 'Entity ID must be a positive integer';
        }
        
        // JSON payload validation
        if (isset($data['payload']) && !$this->validateJSON($data['payload'])) {
            $errors[] = 'Payload must be valid JSON';
        }
        
        return $errors;
    }
    
    /**
     * Get valid task types
     * 
     * @return array Valid task types
     */
    public function getValidTaskTypes()
    {
        return $this->validTaskTypes;
    }
    
    /**
     * Get valid entity types
     * 
     * @return array Valid entity types
     */
    public function getValidEntityTypes()
    {
        return $this->validEntityTypes;
    }
    
    /**
     * Get valid status values
     * 
     * @return array Valid status values
     */
    public function getValidStatuses()
    {
        return $this->validStatuses;
    }
    
    /**
     * Map task type to database action
     * 
     * @param string $taskType Task type from API/tests
     * @return string Database action
     */
    /**
     * Clamp numeric priority to valid range (1..9)
     */
    private function clampPriority($priority)
    {
        return max($this->minPriority, min($this->maxPriority, (int)$priority));
    }
    
    /**
     * Get count of items by status
     * 
     * @param array $filters Filter criteria
     * @return int Count of items
     */
    public function get_count($filters = [])
    {
        try {
            // Check if table exists first
            if (!$this->db->table_exists($this->table)) {
                log_message('info', 'Desk-Moloni sync queue table does not exist yet');
                return 0;
            }
            
            // Reset any previous query builder state
            $this->db->reset_query();
            
            $this->db->from($this->table);
            
            if (isset($filters['status'])) {
                $this->db->where('status', $filters['status']);
            }
            
            if (isset($filters['entity_type'])) {
                $this->db->where('entity_type', $filters['entity_type']);
            }
            
            if (isset($filters['priority'])) {
                $this->db->where('priority', $filters['priority']);
            }
            
            return $this->db->count_all_results();
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue get_count error: ' . $e->getMessage());
            return 0;
        }
    }
    
    /**
     * Get queue summary for dashboard
     * 
     * @return array Queue summary statistics
     */
    public function get_queue_summary()
    {
        try {
            $summary = [];
            
            // Get counts by status
            foreach (['pending', 'processing', 'completed', 'failed'] as $status) {
                $summary[$status] = $this->get_count(['status' => $status]);
            }
            
            // Get total items
            $summary['total'] = array_sum($summary);
            
            // Get recent activity (last 24 hours)
            $this->db->reset_query();
            $this->db->from($this->table);
            $this->db->where('created_at >=', date('Y-m-d H:i:s', strtotime('-24 hours')));
            $summary['recent_24h'] = $this->db->count_all_results();
            
            return $summary;
            
        } catch (Exception $e) {
            log_message('error', 'Desk-Moloni queue summary error: ' . $e->getMessage());
            return [
                'pending' => 0,
                'processing' => 0,
                'completed' => 0,
                'failed' => 0,
                'total' => 0,
                'recent_24h' => 0
            ];
        }
    }
}