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

namespace DeskMoloni\Tests\Contract;

use PHPUnit\Framework\TestCase;

class MappingTableTest 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_mapping table must exist with correct structure
     */
    public function mapping_table_exists_with_required_structure()
    {
        // ACT: Check table existence
        $table_exists = $this->db->table_exists('desk_moloni_mapping');
        
        // ASSERT: Table must exist
        $this->assertTrue($table_exists, 'desk_moloni_mapping table must exist');
        
        // ASSERT: Required columns exist
        $fields = $this->db->list_fields('desk_moloni_mapping');
        $required_fields = ['id', 'entity_type', 'perfex_id', 'moloni_id', 'sync_direction', 'last_sync_at', 'created_at', 'updated_at'];
        
        foreach ($required_fields as $field) {
            $this->assertContains($field, $fields, "Required field '{$field}' must exist in desk_moloni_mapping table");
        }
    }

    /**
     * @test
     * Contract: Mapping table must enforce entity_type ENUM values
     */
    public function mapping_table_enforces_entity_type_enum()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_mapping');
        
        // ACT & ASSERT: Valid entity types should work
        $valid_types = ['client', 'product', 'invoice', 'estimate', 'credit_note'];
        
        foreach ($valid_types as $type) {
            $test_data = [
                'entity_type' => $type,
                'perfex_id' => 1,
                'moloni_id' => 1,
                'sync_direction' => 'bidirectional'
            ];
            
            $insert_success = $this->db->insert('desk_moloni_mapping', $test_data);
            $this->assertTrue($insert_success, "Entity type '{$type}' must be valid");
            
            // Clean up for next iteration
            $this->db->delete('desk_moloni_mapping', ['entity_type' => $type]);
        }
        
        // ACT & ASSERT: Invalid entity type should fail
        $invalid_data = [
            'entity_type' => 'invalid_type',
            'perfex_id' => 1,
            'moloni_id' => 1,
            'sync_direction' => 'bidirectional'
        ];
        
        $this->expectException(\Exception::class);
        $this->db->insert('desk_moloni_mapping', $invalid_data);
    }

    /**
     * @test 
     * Contract: Mapping table must enforce sync_direction ENUM values
     */
    public function mapping_table_enforces_sync_direction_enum()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_mapping');
        
        // ACT & ASSERT: Valid sync directions should work
        $valid_directions = ['perfex_to_moloni', 'moloni_to_perfex', 'bidirectional'];
        
        foreach ($valid_directions as $direction) {
            $test_data = [
                'entity_type' => 'client',
                'perfex_id' => 1,
                'moloni_id' => 1,
                'sync_direction' => $direction
            ];
            
            $insert_success = $this->db->insert('desk_moloni_mapping', $test_data);
            $this->assertTrue($insert_success, "Sync direction '{$direction}' must be valid");
            
            // Clean up for next iteration
            $this->db->delete('desk_moloni_mapping', ['sync_direction' => $direction]);
        }
        
        // ACT & ASSERT: Invalid sync direction should fail
        $invalid_data = [
            'entity_type' => 'client',
            'perfex_id' => 1, 
            'moloni_id' => 1,
            'sync_direction' => 'invalid_direction'
        ];
        
        $this->expectException(\Exception::class);
        $this->db->insert('desk_moloni_mapping', $invalid_data);
    }

    /**
     * @test
     * Contract: Mapping table must enforce unique constraints
     */
    public function mapping_table_enforces_unique_constraints()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_mapping');
        
        $test_data = [
            'entity_type' => 'client',
            'perfex_id' => 123,
            'moloni_id' => 456,
            'sync_direction' => 'bidirectional'
        ];
        
        // ACT & ASSERT: First insert should succeed
        $first_insert = $this->db->insert('desk_moloni_mapping', $test_data);
        $this->assertTrue($first_insert, 'First insert with unique mapping should succeed');
        
        // ACT & ASSERT: Duplicate perfex_id for same entity_type should fail
        $duplicate_perfex = [
            'entity_type' => 'client',
            'perfex_id' => 123,
            'moloni_id' => 789,
            'sync_direction' => 'bidirectional'
        ];
        
        $this->expectException(\Exception::class);
        $this->db->insert('desk_moloni_mapping', $duplicate_perfex);
    }

    /**
     * @test
     * Contract: Mapping table must have required indexes for performance
     */
    public function mapping_table_has_required_indexes()
    {
        // ACT: Get table indexes
        $indexes = $this->db->query("SHOW INDEX FROM desk_moloni_mapping")->result_array();
        
        // ASSERT: Required indexes exist
        $required_indexes = [
            'PRIMARY',
            'unique_perfex_mapping',
            'unique_moloni_mapping', 
            'idx_entity_perfex',
            'idx_entity_moloni',
            'idx_last_sync'
        ];
        
        $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");
        }
    }

    /**
     * @test
     * Contract: Mapping table must support bidirectional relationships
     */
    public function mapping_table_supports_bidirectional_relationships()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_mapping');
        
        // ACT: Insert bidirectional mapping
        $bidirectional_data = [
            'entity_type' => 'invoice',
            'perfex_id' => 100,
            'moloni_id' => 200,
            'sync_direction' => 'bidirectional',
            'last_sync_at' => date('Y-m-d H:i:s')
        ];
        
        $insert_success = $this->db->insert('desk_moloni_mapping', $bidirectional_data);
        $this->assertTrue($insert_success, 'Bidirectional mapping must be supported');
        
        // ASSERT: Data retrieved correctly
        $row = $this->db->get_where('desk_moloni_mapping', ['perfex_id' => 100, 'entity_type' => 'invoice'])->row();
        
        $this->assertEquals('bidirectional', $row->sync_direction, 'Bidirectional sync direction must be stored');
        $this->assertEquals(100, $row->perfex_id, 'Perfex ID must be stored correctly');
        $this->assertEquals(200, $row->moloni_id, 'Moloni ID must be stored correctly');
        $this->assertNotNull($row->last_sync_at, 'last_sync_at must support timestamp values');
    }

    /**
     * @test
     * Contract: Mapping table must allow NULL last_sync_at for new mappings
     */
    public function mapping_table_allows_null_last_sync_at()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_mapping');
        
        // ACT: Insert mapping without last_sync_at
        $new_mapping_data = [
            'entity_type' => 'product',
            'perfex_id' => 50,
            'moloni_id' => 75,
            'sync_direction' => 'perfex_to_moloni'
        ];
        
        $insert_success = $this->db->insert('desk_moloni_mapping', $new_mapping_data);
        $this->assertTrue($insert_success, 'New mapping without last_sync_at must be allowed');
        
        // ASSERT: last_sync_at is NULL for new mappings
        $row = $this->db->get_where('desk_moloni_mapping', ['perfex_id' => 50, 'entity_type' => 'product'])->row();
        $this->assertNull($row->last_sync_at, 'last_sync_at must be NULL for new mappings');
    }

    /**
     * @test
     * Contract: Mapping table must have automatic created_at and updated_at timestamps
     */
    public function mapping_table_has_automatic_timestamps()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_mapping');
        
        // ACT: Insert mapping
        $test_data = [
            'entity_type' => 'estimate',
            'perfex_id' => 25,
            'moloni_id' => 35,
            'sync_direction' => 'moloni_to_perfex'
        ];
        
        $this->db->insert('desk_moloni_mapping', $test_data);
        
        // ASSERT: Timestamps are automatically set
        $row = $this->db->get_where('desk_moloni_mapping', ['perfex_id' => 25, 'entity_type' => 'estimate'])->row();
        
        $this->assertNotNull($row->created_at, 'created_at must be automatically set');
        $this->assertNotNull($row->updated_at, 'updated_at must be automatically set');
        
        // ASSERT: Timestamps are recent
        $created_time = strtotime($row->created_at);
        $current_time = time();
        $this->assertLessThan(5, abs($current_time - $created_time), 'created_at must be recent');
    }

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