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

<?php

/**
 * QueueTableTest.php
 * 
 * PHPUnit tests for desk_moloni_sync_queue table structure and validation rules
 * Tests asynchronous task queue for synchronization operations
 * 
 * @package DeskMoloni\Tests\Database
 * @author Database Design Specialist
 * @version 3.0
 */

require_once(__DIR__ . '/../../../../tests/TestCase.php');

class QueueTableTest extends TestCase
{
    private $tableName = 'desk_moloni_sync_queue';
    private $testQueueModel;
    
    public function setUp(): void
    {
        parent::setUp();
        $this->clearTestData();
        
        // Initialize test model (will be implemented after tests)
        // $this->testQueueModel = new DeskMoloniSyncQueue();
    }
    
    public function tearDown(): void
    {
        $this->clearTestData();
        parent::tearDown();
    }
    
    /**
     * Test table structure exists with correct columns
     */
    public function testTableStructureExists()
    {
        $db = $this->ci->db;
        
        // Verify table exists
        $this->assertTrue($db->table_exists($this->tableName), "Table {$this->tableName} should exist");
        
        // Verify required columns exist
        $expectedColumns = [
            'id', 'task_type', 'entity_type', 'entity_id', 'priority', 'payload',
            'status', 'attempts', 'max_attempts', 'scheduled_at', 'started_at', 
            'completed_at', 'error_message', 'created_at', 'updated_at'
        ];
        
        foreach ($expectedColumns as $column) {
            $this->assertTrue($db->field_exists($column, $this->tableName), 
                "Column '{$column}' should exist in {$this->tableName}");
        }
    }
    
    /**
     * Test task_type ENUM values
     */
    public function testTaskTypeEnumValues()
    {
        $db = $this->ci->db;
        
        $validTaskTypes = [
            'sync_client', 'sync_product', 'sync_invoice', 
            'sync_estimate', 'sync_credit_note', 'status_update'
        ];
        
        foreach ($validTaskTypes as $taskType) {
            $data = [
                'task_type' => $taskType,
                'entity_type' => 'client',
                'entity_id' => rand(1, 1000),
                'priority' => 5
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid task type '{$taskType}' should insert successfully");
            
            $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row();
            $this->assertEquals($taskType, $record->task_type, "Task type should match inserted value");
            
            // Clean up
            $db->where('entity_id', $data['entity_id'])->delete($this->tableName);
        }
    }
    
    /**
     * Test entity_type ENUM values
     */
    public function testEntityTypeEnumValues()
    {
        $db = $this->ci->db;
        
        $validEntityTypes = ['client', 'product', 'invoice', 'estimate', 'credit_note'];
        
        foreach ($validEntityTypes as $entityType) {
            $data = [
                'task_type' => 'sync_client',
                'entity_type' => $entityType,
                'entity_id' => rand(1, 1000),
                'priority' => 5
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid entity type '{$entityType}' should insert successfully");
            
            // Clean up
            $db->where('entity_id', $data['entity_id'])->delete($this->tableName);
        }
    }
    
    /**
     * Test status ENUM values and state transitions
     */
    public function testStatusEnumValues()
    {
        $db = $this->ci->db;
        
        $validStatuses = ['pending', 'processing', 'completed', 'failed', 'retry'];
        
        foreach ($validStatuses as $status) {
            $data = [
                'task_type' => 'sync_product',
                'entity_type' => 'product',
                'entity_id' => rand(1, 1000),
                'priority' => 5,
                'status' => $status
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid status '{$status}' should insert successfully");
            
            $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row();
            $this->assertEquals($status, $record->status, "Status should match inserted value");
            
            // Clean up
            $db->where('entity_id', $data['entity_id'])->delete($this->tableName);
        }
    }
    
    /**
     * Test default values
     */
    public function testDefaultValues()
    {
        $db = $this->ci->db;
        
        $data = [
            'task_type' => 'sync_invoice',
            'entity_type' => 'invoice',
            'entity_id' => rand(1, 1000)
            // Omit priority, status, attempts, max_attempts to test defaults
        ];
        
        $this->assertTrue($db->insert($this->tableName, $data), 'Insert with default values should succeed');
        
        $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row();
        
        $this->assertEquals(5, $record->priority, 'Default priority should be 5');
        $this->assertEquals('pending', $record->status, 'Default status should be pending');
        $this->assertEquals(0, $record->attempts, 'Default attempts should be 0');
        $this->assertEquals(3, $record->max_attempts, 'Default max_attempts should be 3');
        $this->assertNotNull($record->scheduled_at, 'scheduled_at should be auto-populated');
    }
    
    /**
     * Test priority validation constraints
     */
    public function testPriorityConstraints()
    {
        $db = $this->ci->db;
        
        // Test valid priority range (1-9)
        $validPriorities = [1, 2, 3, 4, 5, 6, 7, 8, 9];
        
        foreach ($validPriorities as $priority) {
            $data = [
                'task_type' => 'sync_client',
                'entity_type' => 'client',
                'entity_id' => rand(1, 1000),
                'priority' => $priority
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid priority '{$priority}' should insert successfully");
            
            // Clean up
            $db->where('entity_id', $data['entity_id'])->delete($this->tableName);
        }
        
        // Test invalid priority values should fail (if constraints are enforced)
        $invalidPriorities = [0, 10, -1, 15];
        
        foreach ($invalidPriorities as $priority) {
            $data = [
                'task_type' => 'sync_client',
                'entity_type' => 'client',
                'entity_id' => rand(1, 1000),
                'priority' => $priority
            ];
            
            // Note: This test depends on database constraint enforcement
            // Some databases may not enforce CHECK constraints
            $result = $db->insert($this->tableName, $data);
            if ($result === false) {
                $this->assertStringContainsString('constraint', strtolower($db->error()['message']));
            }
            
            // Clean up any successful inserts
            $db->where('entity_id', $data['entity_id'])->delete($this->tableName);
        }
    }
    
    /**
     * Test attempts validation constraints
     */
    public function testAttemptsConstraints()
    {
        $db = $this->ci->db;
        
        // Test valid attempts configuration
        $data = [
            'task_type' => 'sync_product',
            'entity_type' => 'product',
            'entity_id' => rand(1, 1000),
            'priority' => 5,
            'attempts' => 2,
            'max_attempts' => 3
        ];
        
        $this->assertTrue($db->insert($this->tableName, $data), 'Valid attempts configuration should succeed');
        
        $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row();
        $this->assertEquals(2, $record->attempts, 'Attempts should match inserted value');
        $this->assertEquals(3, $record->max_attempts, 'Max attempts should match inserted value');
    }
    
    /**
     * Test JSON payload validation
     */
    public function testJSONPayloadValidation()
    {
        $db = $this->ci->db;
        
        // Test valid JSON payload
        $validPayloads = [
            '{"action": "create", "data": {"name": "Test Client"}}',
            '{"sync_fields": ["name", "email", "phone"]}',
            '[]',
            '{}',
            null // NULL should be allowed
        ];
        
        foreach ($validPayloads as $index => $payload) {
            $data = [
                'task_type' => 'sync_client',
                'entity_type' => 'client',
                'entity_id' => rand(10000, 19999) + $index,
                'priority' => 5,
                'payload' => $payload
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid JSON payload should insert successfully");
            
            $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row();
            $this->assertEquals($payload, $record->payload, "Payload should match inserted value");
        }
    }
    
    /**
     * Test task state transitions
     */
    public function testTaskStateTransitions()
    {
        $db = $this->ci->db;
        
        $entityId = rand(20000, 29999);
        
        // Insert pending task
        $data = [
            'task_type' => 'sync_invoice',
            'entity_type' => 'invoice',
            'entity_id' => $entityId,
            'priority' => 3,
            'status' => 'pending'
        ];
        
        $this->assertTrue($db->insert($this->tableName, $data), 'Pending task should insert');
        
        // Transition to processing
        $startTime = date('Y-m-d H:i:s');
        $updateData = [
            'status' => 'processing',
            'started_at' => $startTime,
            'attempts' => 1
        ];
        
        $db->where('entity_id', $entityId)->update($this->tableName, $updateData);
        $record = $db->where('entity_id', $entityId)->get($this->tableName)->row();
        
        $this->assertEquals('processing', $record->status, 'Status should be updated to processing');
        $this->assertEquals($startTime, $record->started_at, 'started_at should be updated');
        $this->assertEquals(1, $record->attempts, 'Attempts should be incremented');
        
        // Transition to completed
        $completedTime = date('Y-m-d H:i:s');
        $completedData = [
            'status' => 'completed',
            'completed_at' => $completedTime
        ];
        
        $db->where('entity_id', $entityId)->update($this->tableName, $completedData);
        $finalRecord = $db->where('entity_id', $entityId)->get($this->tableName)->row();
        
        $this->assertEquals('completed', $finalRecord->status, 'Status should be updated to completed');
        $this->assertEquals($completedTime, $finalRecord->completed_at, 'completed_at should be updated');
    }
    
    /**
     * Test failed task with error message
     */
    public function testFailedTaskHandling()
    {
        $db = $this->ci->db;
        
        $entityId = rand(30000, 39999);
        $errorMessage = 'API connection timeout after 30 seconds';
        
        $data = [
            'task_type' => 'sync_client',
            'entity_type' => 'client',
            'entity_id' => $entityId,
            'priority' => 5,
            'status' => 'failed',
            'attempts' => 3,
            'max_attempts' => 3,
            'error_message' => $errorMessage,
            'completed_at' => date('Y-m-d H:i:s')
        ];
        
        $this->assertTrue($db->insert($this->tableName, $data), 'Failed task should insert');
        
        $record = $db->where('entity_id', $entityId)->get($this->tableName)->row();
        $this->assertEquals('failed', $record->status, 'Status should be failed');
        $this->assertEquals($errorMessage, $record->error_message, 'Error message should be stored');
        $this->assertEquals(3, $record->attempts, 'Should have maximum attempts');
    }
    
    /**
     * Test retry logic
     */
    public function testRetryLogic()
    {
        $db = $this->ci->db;
        
        $entityId = rand(40000, 49999);
        
        // Insert failed task that can be retried
        $data = [
            'task_type' => 'sync_product',
            'entity_type' => 'product',
            'entity_id' => $entityId,
            'priority' => 5,
            'status' => 'retry',
            'attempts' => 1,
            'max_attempts' => 3,
            'error_message' => 'Temporary API error',
            'scheduled_at' => date('Y-m-d H:i:s', strtotime('+5 minutes'))
        ];
        
        $this->assertTrue($db->insert($this->tableName, $data), 'Retry task should insert');
        
        $record = $db->where('entity_id', $entityId)->get($this->tableName)->row();
        $this->assertEquals('retry', $record->status, 'Status should be retry');
        $this->assertEquals(1, $record->attempts, 'Should have 1 attempt');
        $this->assertLessThan(3, $record->attempts, 'Should be below max attempts');
    }
    
    /**
     * Test performance indexes exist
     */
    public function testPerformanceIndexes()
    {
        $db = $this->ci->db;
        
        $query = "SHOW INDEX FROM {$this->tableName}";
        $indexes = $db->query($query)->result_array();
        
        $indexNames = array_column($indexes, 'Key_name');
        
        // Expected indexes for queue processing performance
        $expectedIndexes = [
            'PRIMARY',
            'idx_status_priority',
            'idx_entity',
            'idx_scheduled',
            'idx_task_status',
            'idx_attempts',
            'idx_created_at'
        ];
        
        foreach ($expectedIndexes as $expectedIndex) {
            $this->assertContains($expectedIndex, $indexNames, 
                "Index '{$expectedIndex}' should exist for queue processing performance");
        }
    }
    
    /**
     * Test queue processing query performance
     */
    public function testQueueProcessingQueries()
    {
        $db = $this->ci->db;
        
        // Insert test queue items
        $testTasks = [
            ['task_type' => 'sync_client', 'entity_type' => 'client', 'entity_id' => 50001, 'priority' => 1, 'status' => 'pending'],
            ['task_type' => 'sync_product', 'entity_type' => 'product', 'entity_id' => 50002, 'priority' => 3, 'status' => 'pending'],
            ['task_type' => 'sync_invoice', 'entity_type' => 'invoice', 'entity_id' => 50003, 'priority' => 2, 'status' => 'processing'],
            ['task_type' => 'status_update', 'entity_type' => 'invoice', 'entity_id' => 50004, 'priority' => 5, 'status' => 'completed']
        ];
        
        foreach ($testTasks as $task) {
            $db->insert($this->tableName, $task);
        }
        
        // Test typical queue processing query
        $pendingTasks = $db->where('status', 'pending')
                           ->where('scheduled_at <=', date('Y-m-d H:i:s'))
                           ->order_by('priority', 'ASC')
                           ->order_by('scheduled_at', 'ASC')
                           ->limit(10)
                           ->get($this->tableName)
                           ->result_array();
        
        $this->assertGreaterThan(0, count($pendingTasks), 'Should find pending tasks');
        
        // Verify priority ordering
        if (count($pendingTasks) > 1) {
            $this->assertLessThanOrEqual($pendingTasks[1]['priority'], $pendingTasks[0]['priority'], 
                'Tasks should be ordered by priority (ascending)');
        }
        
        // Test entity-specific queries
        $clientTasks = $db->where('entity_type', 'client')
                          ->where('entity_id', 50001)
                          ->get($this->tableName)
                          ->result_array();
        
        $this->assertEquals(1, count($clientTasks), 'Should find entity-specific tasks');
    }
    
    /**
     * Test timestamp fields auto-population
     */
    public function testTimestampFields()
    {
        $db = $this->ci->db;
        
        $beforeInsert = time();
        
        $data = [
            'task_type' => 'sync_estimate',
            'entity_type' => 'estimate',
            'entity_id' => rand(60000, 69999),
            'priority' => 5
        ];
        
        $this->assertTrue($db->insert($this->tableName, $data), 'Insert should succeed');
        
        $afterInsert = time();
        
        $record = $db->where('entity_id', $data['entity_id'])->get($this->tableName)->row();
        
        // Verify created_at is populated
        $this->assertNotNull($record->created_at, 'created_at should be auto-populated');
        $createdTimestamp = strtotime($record->created_at);
        $this->assertGreaterThanOrEqual($beforeInsert, $createdTimestamp, 'created_at should be recent');
        $this->assertLessThanOrEqual($afterInsert, $createdTimestamp, 'created_at should not be in future');
        
        // Verify scheduled_at is populated
        $this->assertNotNull($record->scheduled_at, 'scheduled_at should be auto-populated');
        
        // Verify optional timestamps are NULL
        $this->assertNull($record->started_at, 'started_at should be NULL initially');
        $this->assertNull($record->completed_at, 'completed_at should be NULL initially');
    }
    
    /**
     * Helper method to clear test data
     */
    private function clearTestData()
    {
        $db = $this->ci->db;
        
        // Clear test data using wide entity_id ranges
        $db->where('entity_id >=', 1)
           ->where('entity_id <=', 69999)
           ->delete($this->tableName);
    }
    
    /**
     * Test character set and collation
     */
    public function testCharacterSetAndCollation()
    {
        $db = $this->ci->db;
        
        $query = "SELECT TABLE_COLLATION 
                  FROM information_schema.TABLES 
                  WHERE TABLE_SCHEMA = DATABASE() 
                    AND TABLE_NAME = '{$this->tableName}'";
        
        $result = $db->query($query)->row();
        
        $this->assertEquals('utf8mb4_unicode_ci', $result->TABLE_COLLATION, 
            'Table should use utf8mb4_unicode_ci collation for proper Unicode support');
    }
    
    /**
     * Test storage engine
     */
    public function testStorageEngine()
    {
        $db = $this->ci->db;
        
        $query = "SELECT ENGINE 
                  FROM information_schema.TABLES 
                  WHERE TABLE_SCHEMA = DATABASE() 
                    AND TABLE_NAME = '{$this->tableName}'";
        
        $result = $db->query($query)->row();
        
        $this->assertEquals('InnoDB', $result->ENGINE, 
            'Table should use InnoDB engine for ACID compliance and transaction support');
    }
}