<?php
/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 * 
 * Contract Test for desk_moloni_sync_queue table
 * 
 * This test MUST FAIL until the Sync_queue_model is properly implemented
 * Following TDD RED-GREEN-REFACTOR cycle
 * 
 * @package DeskMoloni\Tests\Contract
 */

namespace DeskMoloni\Tests\Contract;

use PHPUnit\Framework\TestCase;

class QueueTableTest extends TestCase
{
    private $CI;
    private $db;

    protected function setUp(): void
    {
        $this->CI = &get_instance();
        $this->CI->load->database();
        $this->db = $this->CI->db;
        
        if (ENVIRONMENT !== 'testing') {
            $this->markTestSkipped('Contract tests should only run in testing environment');
        }
    }

    /**
     * @test
     * Contract: desk_moloni_sync_queue table must exist with correct structure
     */
    public function queue_table_exists_with_required_structure()
    {
        // ACT: Check table existence
        $table_exists = $this->db->table_exists('desk_moloni_sync_queue');
        
        // ASSERT: Table must exist
        $this->assertTrue($table_exists, 'desk_moloni_sync_queue table must exist');
        
        // ASSERT: Required columns exist
        $fields = $this->db->list_fields('desk_moloni_sync_queue');
        $required_fields = [
            '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 ($required_fields as $field) {
            $this->assertContains($field, $fields, "Required field '{$field}' must exist in desk_moloni_sync_queue table");
        }
    }

    /**
     * @test
     * Contract: Queue table must enforce task_type ENUM values
     */
    public function queue_table_enforces_task_type_enum()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_queue');
        
        // ACT & ASSERT: Valid task types should work
        $valid_task_types = [
            'sync_client', 'sync_product', 'sync_invoice', 
            'sync_estimate', 'sync_credit_note', 'status_update'
        ];
        
        foreach ($valid_task_types as $task_type) {
            $test_data = [
                'task_type' => $task_type,
                'entity_type' => 'client',
                'entity_id' => 1,
                'priority' => 5
            ];
            
            $insert_success = $this->db->insert('desk_moloni_sync_queue', $test_data);
            $this->assertTrue($insert_success, "Task type '{$task_type}' must be valid");
            
            // Clean up
            $this->db->delete('desk_moloni_sync_queue', ['task_type' => $task_type]);
        }
        
        // ACT & ASSERT: Invalid task type should fail
        $invalid_data = [
            'task_type' => 'invalid_task',
            'entity_type' => 'client',
            'entity_id' => 1,
            'priority' => 5
        ];
        
        $this->expectException(\Exception::class);
        $this->db->insert('desk_moloni_sync_queue', $invalid_data);
    }

    /**
     * @test
     * Contract: Queue table must enforce status ENUM values
     */
    public function queue_table_enforces_status_enum()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_queue');
        
        // ACT & ASSERT: Valid status values should work
        $valid_statuses = ['pending', 'processing', 'completed', 'failed', 'retry'];
        
        foreach ($valid_statuses as $status) {
            $test_data = [
                'task_type' => 'sync_client',
                'entity_type' => 'client',
                'entity_id' => 1,
                'priority' => 5,
                'status' => $status
            ];
            
            $insert_success = $this->db->insert('desk_moloni_sync_queue', $test_data);
            $this->assertTrue($insert_success, "Status '{$status}' must be valid");
            
            // Clean up
            $this->db->delete('desk_moloni_sync_queue', ['status' => $status]);
        }
        
        // ACT & ASSERT: Invalid status should fail
        $invalid_data = [
            'task_type' => 'sync_client',
            'entity_type' => 'client',
            'entity_id' => 1,
            'priority' => 5,
            'status' => 'invalid_status'
        ];
        
        $this->expectException(\Exception::class);
        $this->db->insert('desk_moloni_sync_queue', $invalid_data);
    }

    /**
     * @test
     * Contract: Queue table must support priority-based ordering
     */
    public function queue_table_supports_priority_ordering()
    {
        // ARRANGE: Clean table and insert tasks with different priorities
        $this->db->truncate('desk_moloni_sync_queue');
        
        $tasks = [
            ['priority' => 9, 'entity_id' => 1], // Lowest priority
            ['priority' => 1, 'entity_id' => 2], // Highest priority
            ['priority' => 5, 'entity_id' => 3], // Medium priority
        ];
        
        foreach ($tasks as $task) {
            $task_data = [
                'task_type' => 'sync_client',
                'entity_type' => 'client',
                'entity_id' => $task['entity_id'],
                'priority' => $task['priority'],
                'status' => 'pending'
            ];
            $this->db->insert('desk_moloni_sync_queue', $task_data);
        }
        
        // ACT: Query tasks ordered by priority (ascending = highest priority first)
        $this->db->select('entity_id, priority');
        $this->db->where('status', 'pending');
        $this->db->order_by('priority', 'ASC');
        $ordered_tasks = $this->db->get('desk_moloni_sync_queue')->result();
        
        // ASSERT: Tasks are ordered by priority (1 = highest, 9 = lowest)
        $this->assertEquals(2, $ordered_tasks[0]->entity_id, 'Highest priority task (1) should be first');
        $this->assertEquals(3, $ordered_tasks[1]->entity_id, 'Medium priority task (5) should be second');
        $this->assertEquals(1, $ordered_tasks[2]->entity_id, 'Lowest priority task (9) should be last');
    }

    /**
     * @test
     * Contract: Queue table must support JSON payload for task data
     */
    public function queue_table_supports_json_payload()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_queue');
        
        // ACT: Insert task with JSON payload
        $json_payload = [
            'sync_fields' => ['name', 'email', 'vat'],
            'force_update' => true,
            'retry_count' => 0,
            'metadata' => [
                'source' => 'perfex',
                'trigger' => 'after_client_updated'
            ]
        ];
        
        $task_data = [
            'task_type' => 'sync_client',
            'entity_type' => 'client',
            'entity_id' => 100,
            'priority' => 3,
            'payload' => json_encode($json_payload),
            'status' => 'pending'
        ];
        
        $insert_success = $this->db->insert('desk_moloni_sync_queue', $task_data);
        $this->assertTrue($insert_success, 'Task with JSON payload must be inserted successfully');
        
        // ASSERT: JSON payload is stored and retrieved correctly
        $row = $this->db->get_where('desk_moloni_sync_queue', ['entity_id' => 100])->row();
        $retrieved_payload = json_decode($row->payload, true);
        
        $this->assertEquals($json_payload, $retrieved_payload, 'JSON payload must be stored and retrieved correctly');
        $this->assertTrue(is_array($retrieved_payload), 'Payload must be retrievable as array');
        $this->assertEquals('perfex', $retrieved_payload['metadata']['source'], 'Nested JSON data must be accessible');
    }

    /**
     * @test
     * Contract: Queue table must support retry mechanism with attempts tracking
     */
    public function queue_table_supports_retry_mechanism()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_queue');
        
        // ACT: Insert task with retry configuration
        $retry_task = [
            'task_type' => 'sync_invoice',
            'entity_type' => 'invoice',
            'entity_id' => 200,
            'priority' => 1,
            'status' => 'failed',
            'attempts' => 2,
            'max_attempts' => 3,
            'error_message' => 'API rate limit exceeded'
        ];
        
        $insert_success = $this->db->insert('desk_moloni_sync_queue', $retry_task);
        $this->assertTrue($insert_success, 'Task with retry configuration must be inserted');
        
        // ASSERT: Retry data is stored correctly
        $row = $this->db->get_where('desk_moloni_sync_queue', ['entity_id' => 200])->row();
        
        $this->assertEquals(2, $row->attempts, 'Attempts counter must be stored');
        $this->assertEquals(3, $row->max_attempts, 'Max attempts limit must be stored');
        $this->assertEquals('failed', $row->status, 'Failed status must be stored');
        $this->assertEquals('API rate limit exceeded', $row->error_message, 'Error message must be stored');
        
        // ASSERT: Task can be updated for retry
        $this->db->set('status', 'retry');
        $this->db->set('attempts', $row->attempts + 1);
        $this->db->where('id', $row->id);
        $update_success = $this->db->update('desk_moloni_sync_queue');
        
        $this->assertTrue($update_success, 'Task must be updatable for retry');
    }

    /**
     * @test
     * Contract: Queue table must have required indexes for performance
     */
    public function queue_table_has_required_indexes()
    {
        // ACT: Get table indexes
        $indexes = $this->db->query("SHOW INDEX FROM desk_moloni_sync_queue")->result_array();
        
        // ASSERT: Required indexes exist
        $required_indexes = [
            'PRIMARY',
            'idx_status_priority',
            'idx_entity', 
            'idx_scheduled',
            'idx_status_attempts',
            'idx_queue_processing'
        ];
        
        $index_names = array_column($indexes, 'Key_name');
        
        foreach ($required_indexes as $required_index) {
            $this->assertContains($required_index, $index_names, "Required index '{$required_index}' must exist for queue performance");
        }
    }

    /**
     * @test
     * Contract: Queue table must support scheduled execution times
     */
    public function queue_table_supports_scheduled_execution()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_queue');
        
        // ACT: Insert tasks with different scheduled times
        $future_time = date('Y-m-d H:i:s', time() + 3600); // 1 hour from now
        $past_time = date('Y-m-d H:i:s', time() - 3600);   // 1 hour ago
        
        $scheduled_tasks = [
            [
                'entity_id' => 301,
                'scheduled_at' => $future_time,
                'status' => 'pending'
            ],
            [
                'entity_id' => 302, 
                'scheduled_at' => $past_time,
                'status' => 'pending'
            ]
        ];
        
        foreach ($scheduled_tasks as $task) {
            $task_data = array_merge([
                'task_type' => 'sync_product',
                'entity_type' => 'product',
                'priority' => 5
            ], $task);
            
            $this->db->insert('desk_moloni_sync_queue', $task_data);
        }
        
        // ASSERT: Tasks can be filtered by scheduled time
        $ready_tasks = $this->db->get_where('desk_moloni_sync_queue', [
            'scheduled_at <=' => date('Y-m-d H:i:s'),
            'status' => 'pending'
        ])->result();
        
        $this->assertCount(1, $ready_tasks, 'Only past/current scheduled tasks should be ready');
        $this->assertEquals(302, $ready_tasks[0]->entity_id, 'Past scheduled task should be ready for processing');
    }

    protected function tearDown(): void
    {
        // Clean up test data
        if ($this->db) {
            $this->db->where('entity_id >=', 1);
            $this->db->where('entity_id <=', 400);
            $this->db->delete('desk_moloni_sync_queue');
        }
    }
}