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

namespace DeskMoloni\Tests\Contract;

use PHPUnit\Framework\TestCase;

class LogTableTest 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_log table must exist with correct structure
     */
    public function log_table_exists_with_required_structure()
    {
        // ACT: Check table existence
        $table_exists = $this->db->table_exists('desk_moloni_sync_log');
        
        // ASSERT: Table must exist
        $this->assertTrue($table_exists, 'desk_moloni_sync_log table must exist');
        
        // ASSERT: Required columns exist
        $fields = $this->db->list_fields('desk_moloni_sync_log');
        $required_fields = [
            'id', 'operation_type', 'entity_type', 'perfex_id', 'moloni_id',
            'direction', 'status', 'request_data', 'response_data', 
            'error_message', 'execution_time_ms', 'created_at'
        ];
        
        foreach ($required_fields as $field) {
            $this->assertContains($field, $fields, "Required field '{$field}' must exist in desk_moloni_sync_log table");
        }
    }

    /**
     * @test
     * Contract: Log table must enforce operation_type ENUM values
     */
    public function log_table_enforces_operation_type_enum()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_log');
        
        // ACT & ASSERT: Valid operation types should work
        $valid_operations = ['create', 'update', 'delete', 'status_change'];
        
        foreach ($valid_operations as $operation) {
            $test_data = [
                'operation_type' => $operation,
                'entity_type' => 'client',
                'perfex_id' => 1,
                'moloni_id' => 1,
                'direction' => 'perfex_to_moloni',
                'status' => 'success'
            ];
            
            $insert_success = $this->db->insert('desk_moloni_sync_log', $test_data);
            $this->assertTrue($insert_success, "Operation type '{$operation}' must be valid");
            
            // Clean up
            $this->db->delete('desk_moloni_sync_log', ['operation_type' => $operation]);
        }
        
        // ACT & ASSERT: Invalid operation type should fail
        $invalid_data = [
            'operation_type' => 'invalid_operation',
            'entity_type' => 'client',
            'perfex_id' => 1,
            'moloni_id' => 1,
            'direction' => 'perfex_to_moloni',
            'status' => 'success'
        ];
        
        $this->expectException(\Exception::class);
        $this->db->insert('desk_moloni_sync_log', $invalid_data);
    }

    /**
     * @test
     * Contract: Log table must enforce direction ENUM values
     */
    public function log_table_enforces_direction_enum()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_log');
        
        // ACT & ASSERT: Valid directions should work
        $valid_directions = ['perfex_to_moloni', 'moloni_to_perfex'];
        
        foreach ($valid_directions as $direction) {
            $test_data = [
                'operation_type' => 'create',
                'entity_type' => 'invoice',
                'perfex_id' => 10,
                'moloni_id' => 20,
                'direction' => $direction,
                'status' => 'success'
            ];
            
            $insert_success = $this->db->insert('desk_moloni_sync_log', $test_data);
            $this->assertTrue($insert_success, "Direction '{$direction}' must be valid");
            
            // Clean up
            $this->db->delete('desk_moloni_sync_log', ['direction' => $direction]);
        }
        
        // ACT & ASSERT: Invalid direction should fail
        $invalid_data = [
            'operation_type' => 'create',
            'entity_type' => 'invoice',
            'perfex_id' => 10,
            'moloni_id' => 20,
            'direction' => 'invalid_direction',
            'status' => 'success'
        ];
        
        $this->expectException(\Exception::class);
        $this->db->insert('desk_moloni_sync_log', $invalid_data);
    }

    /**
     * @test
     * Contract: Log table must enforce status ENUM values
     */
    public function log_table_enforces_status_enum()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_log');
        
        // ACT & ASSERT: Valid status values should work
        $valid_statuses = ['success', 'error', 'warning'];
        
        foreach ($valid_statuses as $status) {
            $test_data = [
                'operation_type' => 'update',
                'entity_type' => 'product',
                'perfex_id' => 30,
                'moloni_id' => 40,
                'direction' => 'moloni_to_perfex',
                'status' => $status
            ];
            
            $insert_success = $this->db->insert('desk_moloni_sync_log', $test_data);
            $this->assertTrue($insert_success, "Status '{$status}' must be valid");
            
            // Clean up
            $this->db->delete('desk_moloni_sync_log', ['status' => $status]);
        }
        
        // ACT & ASSERT: Invalid status should fail
        $invalid_data = [
            'operation_type' => 'update',
            'entity_type' => 'product',
            'perfex_id' => 30,
            'moloni_id' => 40,
            'direction' => 'moloni_to_perfex',
            'status' => 'invalid_status'
        ];
        
        $this->expectException(\Exception::class);
        $this->db->insert('desk_moloni_sync_log', $invalid_data);
    }

    /**
     * @test
     * Contract: Log table must support JSON storage for request and response data
     */
    public function log_table_supports_json_request_response_data()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_log');
        
        // ACT: Insert log entry with JSON request/response data
        $request_data = [
            'method' => 'POST',
            'endpoint' => '/customers',
            'headers' => ['Authorization' => 'Bearer token123'],
            'body' => [
                'name' => 'Test Company',
                'vat' => '123456789',
                'email' => 'test@company.com'
            ]
        ];
        
        $response_data = [
            'status_code' => 201,
            'headers' => ['Content-Type' => 'application/json'],
            'body' => [
                'customer_id' => 456,
                'message' => 'Customer created successfully'
            ]
        ];
        
        $log_data = [
            'operation_type' => 'create',
            'entity_type' => 'client',
            'perfex_id' => 100,
            'moloni_id' => 456,
            'direction' => 'perfex_to_moloni',
            'status' => 'success',
            'request_data' => json_encode($request_data),
            'response_data' => json_encode($response_data),
            'execution_time_ms' => 1500
        ];
        
        $insert_success = $this->db->insert('desk_moloni_sync_log', $log_data);
        $this->assertTrue($insert_success, 'Log entry with JSON data must be inserted successfully');
        
        // ASSERT: JSON data is stored and retrieved correctly
        $row = $this->db->get_where('desk_moloni_sync_log', ['perfex_id' => 100, 'moloni_id' => 456])->row();
        
        $retrieved_request = json_decode($row->request_data, true);
        $retrieved_response = json_decode($row->response_data, true);
        
        $this->assertEquals($request_data, $retrieved_request, 'Request data JSON must be stored and retrieved correctly');
        $this->assertEquals($response_data, $retrieved_response, 'Response data JSON must be stored and retrieved correctly');
        $this->assertEquals('Test Company', $retrieved_request['body']['name'], 'Nested request data must be accessible');
        $this->assertEquals(456, $retrieved_response['body']['customer_id'], 'Nested response data must be accessible');
    }

    /**
     * @test
     * Contract: Log table must support performance monitoring with execution time
     */
    public function log_table_supports_performance_monitoring()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_log');
        
        // ACT: Insert log entries with different execution times
        $performance_logs = [
            ['execution_time_ms' => 50, 'entity_id' => 501],   // Fast operation
            ['execution_time_ms' => 2500, 'entity_id' => 502], // Slow operation
            ['execution_time_ms' => 15000, 'entity_id' => 503], // Very slow operation
        ];
        
        foreach ($performance_logs as $log) {
            $log_data = [
                'operation_type' => 'create',
                'entity_type' => 'invoice',
                'perfex_id' => $log['entity_id'],
                'moloni_id' => $log['entity_id'] + 1000,
                'direction' => 'perfex_to_moloni',
                'status' => 'success',
                'execution_time_ms' => $log['execution_time_ms']
            ];
            
            $this->db->insert('desk_moloni_sync_log', $log_data);
        }
        
        // ASSERT: Performance data can be queried and analyzed
        $this->db->select('AVG(execution_time_ms) as avg_time, MAX(execution_time_ms) as max_time, MIN(execution_time_ms) as min_time');
        $this->db->where('entity_type', 'invoice');
        $performance_stats = $this->db->get('desk_moloni_sync_log')->row();
        
        $this->assertEquals(5850, $performance_stats->avg_time, 'Average execution time must be calculable');
        $this->assertEquals(15000, $performance_stats->max_time, 'Maximum execution time must be retrievable');
        $this->assertEquals(50, $performance_stats->min_time, 'Minimum execution time must be retrievable');
        
        // ASSERT: Slow operations can be identified
        $slow_operations = $this->db->get_where('desk_moloni_sync_log', 'execution_time_ms > 10000')->result();
        $this->assertCount(1, $slow_operations, 'Slow operations must be identifiable for optimization');
    }

    /**
     * @test
     * Contract: Log table must support NULL perfex_id or moloni_id for failed operations
     */
    public function log_table_supports_null_entity_ids()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_log');
        
        // ACT: Insert log entry with NULL perfex_id (creation failed before getting Perfex ID)
        $failed_creation = [
            'operation_type' => 'create',
            'entity_type' => 'client',
            'perfex_id' => null,
            'moloni_id' => 789,
            'direction' => 'moloni_to_perfex',
            'status' => 'error',
            'error_message' => 'Perfex client creation failed due to validation error'
        ];
        
        $insert_success = $this->db->insert('desk_moloni_sync_log', $failed_creation);
        $this->assertTrue($insert_success, 'Log entry with NULL perfex_id must be allowed');
        
        // ACT: Insert log entry with NULL moloni_id (Moloni creation failed)
        $failed_moloni_creation = [
            'operation_type' => 'create',
            'entity_type' => 'product',
            'perfex_id' => 123,
            'moloni_id' => null,
            'direction' => 'perfex_to_moloni',
            'status' => 'error',
            'error_message' => 'Moloni product creation failed due to API error'
        ];
        
        $insert_success2 = $this->db->insert('desk_moloni_sync_log', $failed_moloni_creation);
        $this->assertTrue($insert_success2, 'Log entry with NULL moloni_id must be allowed');
        
        // ASSERT: NULL values are handled correctly
        $null_perfex = $this->db->get_where('desk_moloni_sync_log', ['moloni_id' => 789])->row();
        $null_moloni = $this->db->get_where('desk_moloni_sync_log', ['perfex_id' => 123])->row();
        
        $this->assertNull($null_perfex->perfex_id, 'perfex_id must be NULL when creation fails');
        $this->assertNull($null_moloni->moloni_id, 'moloni_id must be NULL when Moloni creation fails');
    }

    /**
     * @test
     * Contract: Log table must have required indexes for analytics and performance
     */
    public function log_table_has_required_indexes()
    {
        // ACT: Get table indexes
        $indexes = $this->db->query("SHOW INDEX FROM desk_moloni_sync_log")->result_array();
        
        // ASSERT: Required indexes exist for analytics and performance
        $required_indexes = [
            'PRIMARY',
            'idx_entity_status',
            'idx_perfex_entity',
            'idx_moloni_entity',
            'idx_created_at',
            'idx_status_direction',
            'idx_log_analytics'
        ];
        
        $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 log analytics");
        }
    }

    /**
     * @test
     * Contract: Log table must support automatic created_at timestamp
     */
    public function log_table_has_automatic_created_at()
    {
        // ARRANGE: Clean table
        $this->db->truncate('desk_moloni_sync_log');
        
        // ACT: Insert log entry without specifying created_at
        $log_data = [
            'operation_type' => 'update',
            'entity_type' => 'estimate',
            'perfex_id' => 999,
            'moloni_id' => 888,
            'direction' => 'bidirectional',
            'status' => 'success',
            'execution_time_ms' => 750
        ];
        
        $this->db->insert('desk_moloni_sync_log', $log_data);
        
        // ASSERT: created_at is automatically set and is recent
        $row = $this->db->get_where('desk_moloni_sync_log', ['perfex_id' => 999])->row();
        
        $this->assertNotNull($row->created_at, 'created_at must be automatically set');
        
        $created_time = strtotime($row->created_at);
        $current_time = time();
        $this->assertLessThan(5, abs($current_time - $created_time), 'created_at must be recent timestamp');
    }

    protected function tearDown(): void
    {
        // Clean up test data
        if ($this->db) {
            $this->db->where('perfex_id IS NOT NULL OR moloni_id IS NOT NULL');
            $this->db->where('(perfex_id <= 1000 OR moloni_id <= 1000)');
            $this->db->delete('desk_moloni_sync_log');
        }
    }
}