<?php

declare(strict_types=1);

namespace DeskMoloni\Tests\Unit;

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

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use ReflectionClass;
use stdClass;

/**
 * ClientSyncServiceTest
 * 
 * Unit tests for ClientSyncService class
 * Tests bidirectional client data synchronization between Perfex CRM and Moloni
 * 
 * @package DeskMoloni\Tests\Unit
 * @author Development Helper
 * @version 1.0.0
 */
#[CoversClass('ClientSyncService')]
class ClientSyncServiceTest extends TestCase
{
    private $sync_service;
    private $ci_mock;
    private $api_client_mock;
    private $mapping_model_mock;
    private $sync_log_model_mock;
    private $clients_model_mock;
    
    protected function setUp(): void
    {
        parent::setUp();
        
        // Create mocks
        $this->ci_mock = $this->createMock(stdClass::class);
        $this->api_client_mock = $this->createMock(stdClass::class);
        $this->mapping_model_mock = $this->createMock(stdClass::class);
        $this->sync_log_model_mock = $this->createMock(stdClass::class);
        $this->clients_model_mock = $this->createMock(stdClass::class);
        
        // Setup CI mock
        $this->ci_mock->load = $this->createMock(stdClass::class);
        $this->ci_mock->moloni_api_client = $this->api_client_mock;
        $this->ci_mock->mapping_model = $this->mapping_model_mock;
        $this->ci_mock->sync_log_model = $this->sync_log_model_mock;
        $this->ci_mock->clients_model = $this->clients_model_mock;
        
        // Mock get_instance function
        if (!function_exists('get_instance')) {
            function get_instance() {
                return $GLOBALS['CI_INSTANCE'];
            }
        }
        $GLOBALS['CI_INSTANCE'] = $this->ci_mock;
        
        // Create ClientSyncService instance
        require_once 'modules/desk_moloni/libraries/ClientSyncService.php';
        $this->sync_service = new ClientSyncService();
    }
    
    #[Test]
    #[Group('unit')]
    public function testServiceInitialization(): void
    {
        $this->assertInstanceOf(ClientSyncService::class, $this->sync_service);
        
        // Test default configuration
        $reflection = new ReflectionClass($this->sync_service);
        
        $batch_size_property = $reflection->getProperty('batch_size');
        $batch_size_property->setAccessible(true);
        $this->assertEquals(50, $batch_size_property->getValue($this->sync_service));
        
        $sync_direction_property = $reflection->getProperty('sync_direction');
        $sync_direction_property->setAccessible(true);
        $this->assertEquals('bidirectional', $sync_direction_property->getValue($this->sync_service));
    }
    
    #[Test]
    #[Group('unit')]
    public function testSyncPerfexClientToMoloni(): void
    {
        // Mock client data
        $perfex_client = [
            'userid' => '123',
            'company' => 'Test Sync Company',
            'email' => 'sync@test.com',
            'vat' => 'PT123456789'
        ];
        
        // Mock API response
        $moloni_response = [
            'valid' => 1,
            'data' => [
                'customer_id' => '999'
            ]
        ];
        
        // Setup expectations
        $this->clients_model_mock
            ->expects($this->once())
            ->method('get')
            ->with(123)
            ->willReturn((object)$perfex_client);
            
        $this->api_client_mock
            ->expects($this->once())
            ->method('create_customer')
            ->willReturn($moloni_response);
            
        $this->mapping_model_mock
            ->expects($this->once())
            ->method('create_mapping')
            ->with(
                'customer',
                123,
                '999',
                'perfex_to_moloni'
            );
        
        // Execute sync
        $result = $this->sync_service->sync_client_to_moloni(123);
        
        $this->assertTrue($result['success']);
        $this->assertEquals('999', $result['moloni_id']);
    }
    
    #[Test]
    #[Group('unit')]
    public function testSyncMoloniCustomerToPerfex(): void
    {
        // Mock Moloni customer data
        $moloni_customer = [
            'customer_id' => '888',
            'name' => 'Moloni Test Customer',
            'email' => 'molonitest@example.com',
            'vat' => 'PT987654321'
        ];
        
        // Mock Perfex creation response
        $perfex_client_id = 456;
        
        // Setup expectations
        $this->api_client_mock
            ->expects($this->once())
            ->method('get_customer')
            ->with('888')
            ->willReturn([
                'valid' => 1,
                'data' => $moloni_customer
            ]);
            
        $this->clients_model_mock
            ->expects($this->once())
            ->method('add')
            ->willReturn($perfex_client_id);
            
        $this->mapping_model_mock
            ->expects($this->once())
            ->method('create_mapping')
            ->with(
                'customer',
                $perfex_client_id,
                '888',
                'moloni_to_perfex'
            );
        
        // Execute sync
        $result = $this->sync_service->sync_moloni_customer_to_perfex('888');
        
        $this->assertTrue($result['success']);
        $this->assertEquals($perfex_client_id, $result['perfex_id']);
    }
    
    #[Test]
    #[Group('unit')]
    public function testBidirectionalSync(): void
    {
        $perfex_client_id = 789;
        $moloni_customer_id = '777';
        
        // Mock existing mapping
        $existing_mapping = (object)[
            'id' => 1,
            'perfex_id' => $perfex_client_id,
            'moloni_id' => $moloni_customer_id,
            'sync_direction' => 'bidirectional',
            'last_sync_at' => date('Y-m-d H:i:s', strtotime('-1 hour'))
        ];
        
        // Mock updated data on both sides
        $perfex_client = [
            'userid' => $perfex_client_id,
            'company' => 'Updated Company Name',
            'updated_at' => date('Y-m-d H:i:s', strtotime('-30 minutes'))
        ];
        
        $moloni_customer = [
            'customer_id' => $moloni_customer_id,
            'name' => 'Different Updated Name',
            'updated_at' => date('Y-m-d H:i:s', strtotime('-15 minutes'))
        ];
        
        // Setup expectations
        $this->mapping_model_mock
            ->expects($this->once())
            ->method('get_mapping')
            ->willReturn($existing_mapping);
            
        $this->clients_model_mock
            ->expects($this->once())
            ->method('get')
            ->willReturn((object)$perfex_client);
            
        $this->api_client_mock
            ->expects($this->once())
            ->method('get_customer')
            ->willReturn([
                'valid' => 1,
                'data' => $moloni_customer
            ]);
        
        // Execute bidirectional sync
        $result = $this->sync_service->bidirectional_sync($perfex_client_id, $moloni_customer_id);
        
        $this->assertIsArray($result);
        $this->assertArrayHasKey('success', $result);
    }
    
    #[Test]
    #[Group('unit')]
    public function testConflictDetection(): void
    {
        $perfex_data = [
            'company' => 'Perfex Company Name',
            'email' => 'perfex@company.com',
            'updated_at' => date('Y-m-d H:i:s', strtotime('-10 minutes'))
        ];
        
        $moloni_data = [
            'name' => 'Moloni Company Name',
            'email' => 'moloni@company.com',
            'updated_at' => date('Y-m-d H:i:s', strtotime('-5 minutes'))
        ];
        
        $result = $this->sync_service->detect_conflicts($perfex_data, $moloni_data);
        
        $this->assertTrue($result['has_conflicts']);
        $this->assertContains('company', $result['conflicted_fields']);
        $this->assertContains('email', $result['conflicted_fields']);
    }
    
    #[Test]
    #[Group('unit')]
    #[DataProvider('conflictResolutionProvider')]
    public function testConflictResolution(string $strategy, array $perfex_data, array $moloni_data, string $expected_winner): void
    {
        // Set conflict resolution strategy
        $reflection = new ReflectionClass($this->sync_service);
        $conflict_property = $reflection->getProperty('conflict_resolution');
        $conflict_property->setAccessible(true);
        $conflict_property->setValue($this->sync_service, $strategy);
        
        $result = $this->sync_service->resolve_conflict($perfex_data, $moloni_data, ['company']);
        
        $this->assertEquals($expected_winner, $result['winner']);
    }
    
    public static function conflictResolutionProvider(): array
    {
        return [
            'Last modified wins - Perfex newer' => [
                'last_modified_wins',
                ['company' => 'Perfex Name', 'updated_at' => date('Y-m-d H:i:s')],
                ['name' => 'Moloni Name', 'updated_at' => date('Y-m-d H:i:s', strtotime('-1 hour'))],
                'perfex'
            ],
            'Last modified wins - Moloni newer' => [
                'last_modified_wins',
                ['company' => 'Perfex Name', 'updated_at' => date('Y-m-d H:i:s', strtotime('-1 hour'))],
                ['name' => 'Moloni Name', 'updated_at' => date('Y-m-d H:i:s')],
                'moloni'
            ],
            'Perfex wins strategy' => [
                'perfex_wins',
                ['company' => 'Perfex Name'],
                ['name' => 'Moloni Name'],
                'perfex'
            ],
            'Moloni wins strategy' => [
                'moloni_wins',
                ['company' => 'Perfex Name'],
                ['name' => 'Moloni Name'],
                'moloni'
            ]
        ];
    }
    
    #[Test]
    #[Group('unit')]
    public function testBatchSynchronization(): void
    {
        $client_ids = [100, 101, 102, 103, 104];
        
        // Mock batch processing
        $this->clients_model_mock
            ->expects($this->exactly(count($client_ids)))
            ->method('get')
            ->willReturnOnConsecutiveCalls(
                (object)['userid' => 100, 'company' => 'Company 1'],
                (object)['userid' => 101, 'company' => 'Company 2'],
                (object)['userid' => 102, 'company' => 'Company 3'],
                (object)['userid' => 103, 'company' => 'Company 4'],
                (object)['userid' => 104, 'company' => 'Company 5']
            );
        
        $this->api_client_mock
            ->expects($this->exactly(count($client_ids)))
            ->method('create_customer')
            ->willReturn(['valid' => 1, 'data' => ['customer_id' => '999']]);
        
        $result = $this->sync_service->batch_sync_clients_to_moloni($client_ids);
        
        $this->assertEquals(count($client_ids), $result['total']);
        $this->assertEquals(count($client_ids), $result['success_count']);
        $this->assertEquals(0, $result['error_count']);
    }
    
    #[Test]
    #[Group('unit')]
    public function testSyncWithApiFailure(): void
    {
        $perfex_client = [
            'userid' => '999',
            'company' => 'Test Company',
            'email' => 'test@company.com'
        ];
        
        // Mock API failure
        $this->clients_model_mock
            ->expects($this->once())
            ->method('get')
            ->willReturn((object)$perfex_client);
            
        $this->api_client_mock
            ->expects($this->once())
            ->method('create_customer')
            ->willReturn(['valid' => 0, 'errors' => ['API Error']]);
        
        // Should log error but not throw exception
        $this->sync_log_model_mock
            ->expects($this->once())
            ->method('log_sync_attempt');
        
        $result = $this->sync_service->sync_client_to_moloni(999);
        
        $this->assertFalse($result['success']);
        $this->assertArrayHasKey('error', $result);
    }
    
    #[Test]
    #[Group('unit')]
    public function testSyncProgressTracking(): void
    {
        $batch_size = 3;
        $total_clients = 10;
        
        // Set smaller batch size for testing
        $reflection = new ReflectionClass($this->sync_service);
        $batch_property = $reflection->getProperty('batch_size');
        $batch_property->setAccessible(true);
        $batch_property->setValue($this->sync_service, $batch_size);
        
        $progress_callback = function($current, $total, $status) {
            $this->assertIsInt($current);
            $this->assertEquals(10, $total);
            $this->assertIsString($status);
        };
        
        // Mock clients
        $client_ids = range(1, $total_clients);
        
        // This would test actual progress tracking implementation
        $this->assertTrue(true); // Placeholder
    }
    
    #[Test]
    #[Group('unit')]
    public function testValidateClientData(): void
    {
        $valid_client = [
            'company' => 'Valid Company',
            'email' => 'valid@email.com',
            'vat' => 'PT123456789'
        ];
        
        $invalid_client = [
            'company' => '',
            'email' => 'invalid-email',
            'vat' => ''
        ];
        
        $valid_result = $this->sync_service->validate_client_data($valid_client);
        $this->assertTrue($valid_result['is_valid']);
        
        $invalid_result = $this->sync_service->validate_client_data($invalid_client);
        $this->assertFalse($invalid_result['is_valid']);
        $this->assertNotEmpty($invalid_result['errors']);
    }
    
    #[Test]
    #[Group('unit')]
    public function testSyncStatusTracking(): void
    {
        $mapping_data = [
            'entity_type' => 'customer',
            'perfex_id' => 123,
            'moloni_id' => '456',
            'sync_direction' => 'bidirectional',
            'status' => 'synced'
        ];
        
        $this->mapping_model_mock
            ->expects($this->once())
            ->method('update_mapping_status')
            ->with($mapping_data['perfex_id'], $mapping_data['moloni_id'], 'synced');
        
        $this->sync_service->update_sync_status($mapping_data);
        $this->assertTrue(true); // Assertion happens in mock expectation
    }
    
    protected function tearDown(): void
    {
        $this->sync_service = null;
        $this->ci_mock = null;
        $this->api_client_mock = null;
        $this->mapping_model_mock = null;
        $this->sync_log_model_mock = null;
        $this->clients_model_mock = null;
        
        parent::tearDown();
    }
}