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

<?php

/**
 * MappingTableTest.php
 * 
 * PHPUnit tests for desk_moloni_mapping table structure and validation rules
 * Tests bidirectional entity mapping between Perfex and Moloni
 * 
 * @package DeskMoloni\Tests\Database
 * @author Database Design Specialist
 * @version 3.0
 */

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

class MappingTableTest extends TestCase
{
    private $tableName = 'desk_moloni_mapping';
    private $testMappingModel;
    
    public function setUp(): void
    {
        parent::setUp();
        $this->clearTestData();
        
        // Initialize test model (will be implemented after tests)
        // $this->testMappingModel = new DeskMoloniMapping();
    }
    
    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', 'entity_type', 'perfex_id', 'moloni_id', 'sync_direction', 
            'last_sync_at', 'created_at', 'updated_at'
        ];
        
        foreach ($expectedColumns as $column) {
            $this->assertTrue($db->field_exists($column, $this->tableName), 
                "Column '{$column}' should exist in {$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 = [
                'entity_type' => $entityType,
                'perfex_id' => rand(1, 1000),
                'moloni_id' => rand(1, 1000),
                'sync_direction' => 'bidirectional'
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid entity type '{$entityType}' should insert successfully");
            
            // Clean up immediately to avoid constraint conflicts
            $db->where('entity_type', $entityType)
               ->where('perfex_id', $data['perfex_id'])
               ->delete($this->tableName);
        }
    }
    
    /**
     * Test sync_direction ENUM values
     */
    public function testSyncDirectionEnumValues()
    {
        $db = $this->ci->db;
        
        $validDirections = ['perfex_to_moloni', 'moloni_to_perfex', 'bidirectional'];
        
        foreach ($validDirections as $direction) {
            $data = [
                'entity_type' => 'client',
                'perfex_id' => rand(1, 1000),
                'moloni_id' => rand(1, 1000),
                'sync_direction' => $direction
            ];
            
            $this->assertTrue($db->insert($this->tableName, $data), 
                "Valid sync direction '{$direction}' should insert successfully");
            
            // Verify stored value
            $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
            $this->assertEquals($direction, $record->sync_direction, "Sync direction should match inserted value");
            
            // Clean up
            $db->where('perfex_id', $data['perfex_id'])->delete($this->tableName);
        }
    }
    
    /**
     * Test default sync_direction value
     */
    public function testDefaultSyncDirection()
    {
        $db = $this->ci->db;
        
        $data = [
            'entity_type' => 'product',
            'perfex_id' => rand(1, 1000),
            'moloni_id' => rand(1, 1000)
            // sync_direction omitted to test default
        ];
        
        $this->assertTrue($db->insert($this->tableName, $data), 'Insert without sync_direction should succeed');
        
        $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
        $this->assertEquals('bidirectional', $record->sync_direction, 'Default sync_direction should be bidirectional');
    }
    
    /**
     * Test unique constraint on entity_type + perfex_id
     */
    public function testUniquePerfexMapping()
    {
        $db = $this->ci->db;
        
        // Insert first record
        $data1 = [
            'entity_type' => 'invoice',
            'perfex_id' => 12345,
            'moloni_id' => 54321,
            'sync_direction' => 'bidirectional'
        ];
        
        $this->assertTrue($db->insert($this->tableName, $data1), 'First mapping insert should succeed');
        
        // Try to insert duplicate perfex mapping - should fail
        $data2 = [
            'entity_type' => 'invoice', // Same entity type
            'perfex_id' => 12345,       // Same perfex ID
            'moloni_id' => 98765,       // Different moloni ID
            'sync_direction' => 'perfex_to_moloni'
        ];
        
        $this->assertFalse($db->insert($this->tableName, $data2), 'Duplicate perfex mapping should fail');
        $this->assertStringContainsString('Duplicate', $db->error()['message']);
    }
    
    /**
     * Test unique constraint on entity_type + moloni_id
     */
    public function testUniqueMoloniMapping()
    {
        $db = $this->ci->db;
        
        // Insert first record
        $data1 = [
            'entity_type' => 'client',
            'perfex_id' => 11111,
            'moloni_id' => 22222,
            'sync_direction' => 'bidirectional'
        ];
        
        $this->assertTrue($db->insert($this->tableName, $data1), 'First mapping insert should succeed');
        
        // Try to insert duplicate moloni mapping - should fail  
        $data2 = [
            'entity_type' => 'client',  // Same entity type
            'perfex_id' => 33333,       // Different perfex ID
            'moloni_id' => 22222,       // Same moloni ID
            'sync_direction' => 'moloni_to_perfex'
        ];
        
        $this->assertFalse($db->insert($this->tableName, $data2), 'Duplicate moloni mapping should fail');
        $this->assertStringContainsString('Duplicate', $db->error()['message']);
    }
    
    /**
     * Test that same IDs can exist for different entity types
     */
    public function testDifferentEntityTypesAllowSameIds()
    {
        $db = $this->ci->db;
        
        $sameId = 99999;
        
        // Insert mappings with same IDs but different entity types
        $mappings = [
            ['entity_type' => 'client', 'perfex_id' => $sameId, 'moloni_id' => $sameId],
            ['entity_type' => 'product', 'perfex_id' => $sameId, 'moloni_id' => $sameId],
            ['entity_type' => 'invoice', 'perfex_id' => $sameId, 'moloni_id' => $sameId]
        ];
        
        foreach ($mappings as $mapping) {
            $mapping['sync_direction'] = 'bidirectional';
            $this->assertTrue($db->insert($this->tableName, $mapping), 
                "Same IDs should be allowed for different entity types: {$mapping['entity_type']}");
        }
        
        // Verify all records exist
        $count = $db->where('perfex_id', $sameId)->count_all_results($this->tableName);
        $this->assertEquals(3, $count, 'Should have 3 mappings with same IDs but different entity types');
    }
    
    /**
     * Test last_sync_at timestamp handling
     */
    public function testLastSyncAtTimestamp()
    {
        $db = $this->ci->db;
        
        // Insert record without last_sync_at
        $data = [
            'entity_type' => 'estimate',
            'perfex_id' => rand(1, 1000),
            'moloni_id' => rand(1, 1000),
            'sync_direction' => 'bidirectional'
        ];
        
        $this->assertTrue($db->insert($this->tableName, $data), 'Insert should succeed');
        
        $record = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
        $this->assertNull($record->last_sync_at, 'last_sync_at should be NULL initially');
        
        // Update with sync timestamp
        $syncTime = date('Y-m-d H:i:s');
        $db->where('perfex_id', $data['perfex_id'])->update($this->tableName, ['last_sync_at' => $syncTime]);
        
        $updatedRecord = $db->where('perfex_id', $data['perfex_id'])->get($this->tableName)->row();
        $this->assertEquals($syncTime, $updatedRecord->last_sync_at, 'last_sync_at should be updated');
    }
    
    /**
     * 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
        $expectedIndexes = [
            'PRIMARY',
            'unique_perfex_mapping',
            'unique_moloni_mapping', 
            'idx_entity_perfex',
            'idx_entity_moloni',
            'idx_sync_direction',
            'idx_last_sync',
            'idx_created_at'
        ];
        
        foreach ($expectedIndexes as $expectedIndex) {
            $this->assertContains($expectedIndex, $indexNames, 
                "Index '{$expectedIndex}' should exist for performance optimization");
        }
    }
    
    /**
     * Test bidirectional mapping functionality
     */
    public function testBidirectionalMappingScenarios()
    {
        $db = $this->ci->db;
        
        // Test Perfex to Moloni sync
        $perfexToMoloni = [
            'entity_type' => 'client',
            'perfex_id' => 100,
            'moloni_id' => 200,
            'sync_direction' => 'perfex_to_moloni'
        ];
        
        $this->assertTrue($db->insert($this->tableName, $perfexToMoloni), 
            'Perfex to Moloni mapping should insert successfully');
        
        // Test Moloni to Perfex sync
        $moloniToPerfex = [
            'entity_type' => 'product',
            'perfex_id' => 300,
            'moloni_id' => 400,
            'sync_direction' => 'moloni_to_perfex'
        ];
        
        $this->assertTrue($db->insert($this->tableName, $moloniToPerfex), 
            'Moloni to Perfex mapping should insert successfully');
        
        // Test bidirectional sync
        $bidirectional = [
            'entity_type' => 'invoice',
            'perfex_id' => 500,
            'moloni_id' => 600,
            'sync_direction' => 'bidirectional'
        ];
        
        $this->assertTrue($db->insert($this->tableName, $bidirectional), 
            'Bidirectional mapping should insert successfully');
        
        // Verify mappings can be retrieved by direction
        $perfexDirection = $db->where('sync_direction', 'perfex_to_moloni')->count_all_results($this->tableName);
        $this->assertGreaterThanOrEqual(1, $perfexDirection, 'Should find perfex_to_moloni mappings');
        
        $moloniDirection = $db->where('sync_direction', 'moloni_to_perfex')->count_all_results($this->tableName);
        $this->assertGreaterThanOrEqual(1, $moloniDirection, 'Should find moloni_to_perfex mappings');
        
        $bidirectionalCount = $db->where('sync_direction', 'bidirectional')->count_all_results($this->tableName);
        $this->assertGreaterThanOrEqual(1, $bidirectionalCount, 'Should find bidirectional mappings');
    }
    
    /**
     * Test entity lookup by Perfex ID
     */
    public function testPerfexEntityLookup()
    {
        $db = $this->ci->db;
        
        $testMappings = [
            ['entity_type' => 'client', 'perfex_id' => 1001, 'moloni_id' => 2001],
            ['entity_type' => 'product', 'perfex_id' => 1002, 'moloni_id' => 2002],
            ['entity_type' => 'invoice', 'perfex_id' => 1003, 'moloni_id' => 2003]
        ];
        
        foreach ($testMappings as $mapping) {
            $mapping['sync_direction'] = 'bidirectional';
            $db->insert($this->tableName, $mapping);
        }
        
        // Test lookup by entity type and perfex ID
        foreach ($testMappings as $expected) {
            $result = $db->where('entity_type', $expected['entity_type'])
                         ->where('perfex_id', $expected['perfex_id'])
                         ->get($this->tableName)
                         ->row();
            
            $this->assertNotNull($result, "Should find mapping for {$expected['entity_type']} with perfex_id {$expected['perfex_id']}");
            $this->assertEquals($expected['moloni_id'], $result->moloni_id, 'Moloni ID should match');
        }
    }
    
    /**
     * Test entity lookup by Moloni ID  
     */
    public function testMoloniEntityLookup()
    {
        $db = $this->ci->db;
        
        $testMappings = [
            ['entity_type' => 'estimate', 'perfex_id' => 3001, 'moloni_id' => 4001],
            ['entity_type' => 'credit_note', 'perfex_id' => 3002, 'moloni_id' => 4002]
        ];
        
        foreach ($testMappings as $mapping) {
            $mapping['sync_direction'] = 'bidirectional';
            $db->insert($this->tableName, $mapping);
        }
        
        // Test lookup by entity type and moloni ID
        foreach ($testMappings as $expected) {
            $result = $db->where('entity_type', $expected['entity_type'])
                         ->where('moloni_id', $expected['moloni_id'])
                         ->get($this->tableName)
                         ->row();
            
            $this->assertNotNull($result, "Should find mapping for {$expected['entity_type']} with moloni_id {$expected['moloni_id']}");
            $this->assertEquals($expected['perfex_id'], $result->perfex_id, 'Perfex ID should match');
        }
    }
    
    /**
     * Test timestamp fields auto-population
     */
    public function testTimestampFields()
    {
        $db = $this->ci->db;
        
        $beforeInsert = time();
        
        $data = [
            'entity_type' => 'client',
            'perfex_id' => rand(5000, 9999),
            'moloni_id' => rand(5000, 9999),
            'sync_direction' => 'bidirectional'
        ];
        
        $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');
        
        // Verify updated_at is populated
        $this->assertNotNull($record->updated_at, 'updated_at should be auto-populated');
        $this->assertEquals($record->created_at, $record->updated_at, 'Initially created_at should equal updated_at');
    }
    
    /**
     * Helper method to clear test data
     */
    private function clearTestData()
    {
        $db = $this->ci->db;
        
        // Clear all test data - using wide range to catch test IDs
        $db->where('perfex_id >=', 1)
           ->where('perfex_id <=', 9999)
           ->delete($this->tableName);
           
        $db->where('moloni_id >=', 1)
           ->where('moloni_id <=', 9999)
           ->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 foreign key support');
    }
}