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

<?php

/**
 * LogTableTest.php
 * 
 * PHPUnit tests for desk_moloni_sync_log table structure and validation rules
 * Tests comprehensive audit log of all synchronization operations
 * 
 * @package DeskMoloni\Tests\Database
 * @author Database Design Specialist
 * @version 3.0
 */

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

class LogTableTest extends TestCase
{
    private $tableName = 'desk_moloni_sync_log';
    private $testLogModel;
    
    public function setUp(): void
    {
        parent::setUp();
        $this->clearTestData();
        
        // Initialize test model (will be implemented after tests)
        // $this->testLogModel = new DeskMoloniSyncLog();
    }
    
    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', 'operation_type', 'entity_type', 'perfex_id', 'moloni_id', 
            'direction', 'status', 'request_data', 'response_data', 'error_message',
            'execution_time_ms', 'created_at'
        ];
        
        foreach ($expectedColumns as $column) {
            $this->assertTrue($db->field_exists($column, $this->tableName), 
                "Column '{$column}' should exist in {$this->tableName}");
        }
    }
    
    /**
     * Test operation_type ENUM values
     */
    public function testOperationTypeEnumValues()
    {
        $db = $this->ci->db;
        
        $validOperationTypes = ['create', 'update', 'delete', 'status_change'];
        
        foreach ($validOperationTypes as $operationType) {
            $data = [
                'operation_type' => $operationType,
                'entity_type' => 'client',
                'perfex_id' => rand(1, 1000),
                'direction' => 'perfex_to_moloni',
                'status' => 'success'
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid operation type '{$operationType}' should insert successfully");
            
            $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
            $this->assertEquals($operationType, $record->operation_type, "Operation type should match inserted value");
            
            // Clean up
            $db->where('perfex_id', $data['perfex_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 = [
                'operation_type' => 'create',
                'entity_type' => $entityType,
                'perfex_id' => rand(1, 1000),
                'direction' => 'perfex_to_moloni',
                'status' => 'success'
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid entity type '{$entityType}' should insert successfully");
            
            // Clean up
            $db->where('perfex_id', $data['perfex_id'])->delete($this->tableName);
        }
    }
    
    /**
     * Test direction ENUM values
     */
    public function testDirectionEnumValues()
    {
        $db = $this->ci->db;
        
        $validDirections = ['perfex_to_moloni', 'moloni_to_perfex'];
        
        foreach ($validDirections as $direction) {
            $data = [
                'operation_type' => 'update',
                'entity_type' => 'product',
                'perfex_id' => rand(1, 1000),
                'direction' => $direction,
                'status' => 'success'
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid direction '{$direction}' should insert successfully");
            
            $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
            $this->assertEquals($direction, $record->direction, "Direction should match inserted value");
            
            // Clean up
            $db->where('perfex_id', $data['perfex_id'])->delete($this->tableName);
        }
    }
    
    /**
     * Test status ENUM values
     */
    public function testStatusEnumValues()
    {
        $db = $this->ci->db;
        
        $validStatuses = ['success', 'error', 'warning'];
        
        foreach ($validStatuses as $status) {
            $data = [
                'operation_type' => 'create',
                'entity_type' => 'invoice',
                'perfex_id' => rand(1, 1000),
                'direction' => 'perfex_to_moloni',
                'status' => $status
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid status '{$status}' should insert successfully");
            
            $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
            $this->assertEquals($status, $record->status, "Status should match inserted value");
            
            // Clean up
            $db->where('perfex_id', $data['perfex_id'])->delete($this->tableName);
        }
    }
    
    /**
     * Test entity ID constraints - at least one must be present
     */
    public function testEntityIdConstraints()
    {
        $db = $this->ci->db;
        
        // Test with perfex_id only
        $dataWithPerfexId = [
            'operation_type' => 'create',
            'entity_type' => 'client',
            'perfex_id' => rand(10000, 19999),
            'moloni_id' => null,
            'direction' => 'perfex_to_moloni',
            'status' => 'success'
        ];
        
        $this->assertTrue($db->insert($this->tableName, $dataWithPerfexId), 
            'Insert with perfex_id only should succeed');
        
        // Test with moloni_id only
        $dataWithMoloniId = [
            'operation_type' => 'update',
            'entity_type' => 'product',
            'perfex_id' => null,
            'moloni_id' => rand(10000, 19999),
            'direction' => 'moloni_to_perfex',
            'status' => 'success'
        ];
        
        $this->assertTrue($db->insert($this->tableName, $dataWithMoloniId), 
            'Insert with moloni_id only should succeed');
        
        // Test with both IDs
        $dataWithBothIds = [
            'operation_type' => 'update',
            'entity_type' => 'invoice',
            'perfex_id' => rand(20000, 29999),
            'moloni_id' => rand(20000, 29999),
            'direction' => 'perfex_to_moloni',
            'status' => 'success'
        ];
        
        $this->assertTrue($db->insert($this->tableName, $dataWithBothIds), 
            'Insert with both IDs should succeed');
    }
    
    /**
     * Test JSON validation for request and response data
     */
    public function testJSONValidation()
    {
        $db = $this->ci->db;
        
        // Test valid JSON data
        $validJSONData = [
            '{"client_id": 123, "name": "Test Client", "email": "test@example.com"}',
            '{"products": [{"id": 1, "name": "Product 1"}, {"id": 2, "name": "Product 2"}]}',
            '[]',
            '{}',
            null
        ];
        
        foreach ($validJSONData as $index => $jsonData) {
            $data = [
                'operation_type' => 'create',
                'entity_type' => 'client',
                'perfex_id' => rand(30000, 39999) + $index,
                'direction' => 'perfex_to_moloni',
                'status' => 'success',
                'request_data' => $jsonData,
                'response_data' => $jsonData
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid JSON data should insert successfully");
            
            $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
            $this->assertEquals($jsonData, $record->request_data, "Request data should match");
            $this->assertEquals($jsonData, $record->response_data, "Response data should match");
        }
    }
    
    /**
     * Test execution time validation
     */
    public function testExecutionTimeValidation()
    {
        $db = $this->ci->db;
        
        $validExecutionTimes = [0, 50, 150, 1000, 5000];
        
        foreach ($validExecutionTimes as $index => $executionTime) {
            $data = [
                'operation_type' => 'update',
                'entity_type' => 'product',
                'perfex_id' => rand(40000, 49999) + $index,
                'direction' => 'perfex_to_moloni',
                'status' => 'success',
                'execution_time_ms' => $executionTime
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid execution time '{$executionTime}ms' should insert successfully");
            
            $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
            $this->assertEquals($executionTime, $record->execution_time_ms, "Execution time should match");
        }
    }
    
    /**
     * Test successful operation logging
     */
    public function testSuccessfulOperationLogging()
    {
        $db = $this->ci->db;
        
        $successfulOperation = [
            'operation_type' => 'create',
            'entity_type' => 'client',
            'perfex_id' => rand(50000, 59999),
            'moloni_id' => rand(50000, 59999),
            'direction' => 'perfex_to_moloni',
            'status' => 'success',
            'request_data' => '{"name": "New Client", "email": "client@example.com"}',
            'response_data' => '{"id": 12345, "status": "created", "moloni_id": 54321}',
            'execution_time_ms' => 250
        ];
        
        $this->assertTrue($db->insert($this->tableName, $successfulOperation), 
            'Successful operation should log correctly');
        
        $logEntry = $db->where('perfex_id', $successfulOperation['perfex_id'])->get($this->tableName)->row();
        
        $this->assertEquals('success', $logEntry->status, 'Status should be success');
        $this->assertNull($logEntry->error_message, 'Error message should be NULL for successful operations');
        $this->assertNotNull($logEntry->request_data, 'Request data should be logged');
        $this->assertNotNull($logEntry->response_data, 'Response data should be logged');
        $this->assertEquals(250, $logEntry->execution_time_ms, 'Execution time should be logged');
    }
    
    /**
     * Test error operation logging
     */
    public function testErrorOperationLogging()
    {
        $db = $this->ci->db;
        
        $errorMessage = 'API returned 400 Bad Request: Invalid client data - email field is required';
        
        $errorOperation = [
            'operation_type' => 'create',
            'entity_type' => 'client',
            'perfex_id' => rand(60000, 69999),
            'direction' => 'perfex_to_moloni',
            'status' => 'error',
            'request_data' => '{"name": "Incomplete Client"}',
            'response_data' => '{"error": "email field is required", "code": 400}',
            'error_message' => $errorMessage,
            'execution_time_ms' => 1200
        ];
        
        $this->assertTrue($db->insert($this->tableName, $errorOperation), 
            'Error operation should log correctly');
        
        $logEntry = $db->where('perfex_id', $errorOperation['perfex_id'])->get($this->tableName)->row();
        
        $this->assertEquals('error', $logEntry->status, 'Status should be error');
        $this->assertEquals($errorMessage, $logEntry->error_message, 'Error message should be logged');
        $this->assertNotNull($logEntry->request_data, 'Request data should be logged for debugging');
        $this->assertNotNull($logEntry->response_data, 'Response data should be logged for debugging');
    }
    
    /**
     * Test warning operation logging
     */
    public function testWarningOperationLogging()
    {
        $db = $this->ci->db;
        
        $warningMessage = 'Operation completed but some fields were ignored due to validation rules';
        
        $warningOperation = [
            'operation_type' => 'update',
            'entity_type' => 'product',
            'perfex_id' => rand(70000, 79999),
            'moloni_id' => rand(70000, 79999),
            'direction' => 'perfex_to_moloni',
            'status' => 'warning',
            'request_data' => '{"name": "Updated Product", "invalid_field": "ignored"}',
            'response_data' => '{"id": 12345, "status": "updated", "warnings": ["invalid_field ignored"]}',
            'error_message' => $warningMessage,
            'execution_time_ms' => 800
        ];
        
        $this->assertTrue($db->insert($this->tableName, $warningOperation), 
            'Warning operation should log correctly');
        
        $logEntry = $db->where('perfex_id', $warningOperation['perfex_id'])->get($this->tableName)->row();
        
        $this->assertEquals('warning', $logEntry->status, 'Status should be warning');
        $this->assertEquals($warningMessage, $logEntry->error_message, 'Warning message should be logged');
    }
    
    /**
     * 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 log analysis and performance
        $expectedIndexes = [
            'PRIMARY',
            'idx_entity_status',
            'idx_perfex_entity',
            'idx_moloni_entity',
            'idx_created_at',
            'idx_operation_direction',
            'idx_status',
            'idx_execution_time'
        ];
        
        foreach ($expectedIndexes as $expectedIndex) {
            $this->assertContains($expectedIndex, $indexNames, 
                "Index '{$expectedIndex}' should exist for log analysis performance");
        }
    }
    
    /**
     * Test log analysis queries
     */
    public function testLogAnalysisQueries()
    {
        $db = $this->ci->db;
        
        // Insert test log entries for analysis
        $testLogs = [
            ['operation_type' => 'create', 'entity_type' => 'client', 'perfex_id' => 80001, 'status' => 'success', 'execution_time_ms' => 200],
            ['operation_type' => 'update', 'entity_type' => 'client', 'perfex_id' => 80002, 'status' => 'error', 'execution_time_ms' => 1500],
            ['operation_type' => 'create', 'entity_type' => 'product', 'perfex_id' => 80003, 'status' => 'success', 'execution_time_ms' => 300],
            ['operation_type' => 'delete', 'entity_type' => 'invoice', 'perfex_id' => 80004, 'status' => 'success', 'execution_time_ms' => 100]
        ];
        
        foreach ($testLogs as $log) {
            $log['direction'] = 'perfex_to_moloni';
            $db->insert($this->tableName, $log);
        }
        
        // Test error analysis query
        $errorCount = $db->where('status', 'error')
                         ->where('created_at >=', date('Y-m-d', strtotime('-1 day')))
                         ->count_all_results($this->tableName);
        
        $this->assertGreaterThanOrEqual(1, $errorCount, 'Should find error logs');
        
        // Test performance analysis query
        $slowOperations = $db->where('execution_time_ms >', 1000)
                             ->order_by('execution_time_ms', 'DESC')
                             ->get($this->tableName)
                             ->result_array();
        
        $this->assertGreaterThanOrEqual(1, count($slowOperations), 'Should find slow operations');
        
        // Test entity-specific analysis
        $clientOperations = $db->where('entity_type', 'client')
                               ->where('created_at >=', date('Y-m-d'))
                               ->get($this->tableName)
                               ->result_array();
        
        $this->assertGreaterThanOrEqual(2, count($clientOperations), 'Should find client operations');
    }
    
    /**
     * Test timestamp auto-population
     */
    public function testTimestampAutoPopulation()
    {
        $db = $this->ci->db;
        
        $beforeInsert = time();
        
        $data = [
            'operation_type' => 'status_change',
            'entity_type' => 'invoice',
            'perfex_id' => rand(90000, 99999),
            'direction' => 'moloni_to_perfex',
            'status' => 'success'
        ];
        
        $this->assertTrue($db->insert($this->tableName, $data), 'Insert should succeed');
        
        $afterInsert = time();
        
        $record = $db->where('perfex_id', $data['perfex_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');
    }
    
    /**
     * Test audit trail completeness
     */
    public function testAuditTrailCompleteness()
    {
        $db = $this->ci->db;
        
        // Simulate complete operation audit trail
        $operationId = rand(100000, 199999);
        
        $auditTrail = [
            [
                'operation_type' => 'create',
                'entity_type' => 'client',
                'perfex_id' => $operationId,
                'direction' => 'perfex_to_moloni',
                'status' => 'success',
                'request_data' => '{"name": "Audit Test Client", "email": "audit@test.com"}',
                'response_data' => '{"id": ' . $operationId . ', "moloni_id": ' . ($operationId + 1000) . '}',
                'execution_time_ms' => 300
            ],
            [
                'operation_type' => 'update',
                'entity_type' => 'client',
                'perfex_id' => $operationId,
                'moloni_id' => $operationId + 1000,
                'direction' => 'perfex_to_moloni',
                'status' => 'success',
                'request_data' => '{"name": "Updated Audit Test Client"}',
                'response_data' => '{"id": ' . ($operationId + 1000) . ', "status": "updated"}',
                'execution_time_ms' => 200
            ]
        ];
        
        foreach ($auditTrail as $entry) {
            $this->assertTrue($db->insert($this->tableName, $entry), 'Audit entry should insert');
        }
        
        // Verify complete audit trail exists
        $auditEntries = $db->where('perfex_id', $operationId)
                           ->order_by('created_at', 'ASC')
                           ->get($this->tableName)
                           ->result_array();
        
        $this->assertEquals(2, count($auditEntries), 'Should have complete audit trail');
        $this->assertEquals('create', $auditEntries[0]['operation_type'], 'First entry should be create');
        $this->assertEquals('update', $auditEntries[1]['operation_type'], 'Second entry should be update');
    }
    
    /**
     * Helper method to clear test data
     */
    private function clearTestData()
    {
        $db = $this->ci->db;
        
        // Clear test data using wide ID ranges
        $idRanges = [
            ['min' => 1, 'max' => 199999]  // Covers all test ranges
        ];
        
        foreach ($idRanges as $range) {
            $db->where('perfex_id >=', $range['min'])
               ->where('perfex_id <=', $range['max'])
               ->delete($this->tableName);
               
            $db->where('moloni_id >=', $range['min'])
               ->where('moloni_id <=', $range['max'])
               ->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 audit integrity');
    }
}