<?php

declare(strict_types=1);

namespace DeskMoloni\Tests;

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

use PHPUnit\Framework\TestCase;
use DeskMoloni\Libraries\ClientSyncService;
use DeskMoloni\Libraries\EntityMappingService;
use DeskMoloni\Libraries\ErrorHandler;
use DeskMoloni\Libraries\MoloniApiClient;
use ReflectionClass;
use stdClass;

/**
 * ClientSyncServiceTest
 * 
 * Comprehensive test suite for Client/Customer synchronization service
 * Tests bidirectional sync, conflict resolution, data validation, and error handling
 * 
 * @package DeskMoloni\Tests
 * @author Descomplicar® - PHP Fullstack Engineer
 * @version 1.0.0
 */
class ClientSyncServiceTest extends TestCase
{
    private $client_sync_service;
    private $entity_mapping_mock;
    private $api_client_mock;
    private $error_handler_mock;
    private $CI_mock;
    
    protected function setUp(): void
    {
        // Create mocks for dependencies
        $this->entity_mapping_mock = $this->createMock(EntityMappingService::class);
        $this->api_client_mock = $this->createMock(MoloniApiClient::class);
        $this->error_handler_mock = $this->createMock(ErrorHandler::class);
        
        // Mock CodeIgniter instance
        $this->CI_mock = $this->createMock(stdClass::class);
        $this->CI_mock->clients_model = $this->createMock(stdClass::class);
        $this->CI_mock->desk_moloni_model = $this->createMock(stdClass::class);
        
        // Initialize service with mocked dependencies
        $this->client_sync_service = new ClientSyncService();
        
        // Use reflection to inject mocks
        $reflection = new ReflectionClass($this->client_sync_service);
        
        $entity_mapping_property = $reflection->getProperty('entity_mapping');
        $entity_mapping_property->setAccessible(true);
        $entity_mapping_property->setValue($this->client_sync_service, $this->entity_mapping_mock);
        
        $api_client_property = $reflection->getProperty('api_client');
        $api_client_property->setAccessible(true);
        $api_client_property->setValue($this->client_sync_service, $this->api_client_mock);
        
        $error_handler_property = $reflection->getProperty('error_handler');
        $error_handler_property->setAccessible(true);
        $error_handler_property->setValue($this->client_sync_service, $this->error_handler_mock);
        
        $ci_property = $reflection->getProperty('CI');
        $ci_property->setAccessible(true);
        $ci_property->setValue($this->client_sync_service, $this->CI_mock);
    }
    
    public function testSyncPerfexToMoloniSuccess()
    {
        // Test data
        $perfex_client_id = 123;
        $perfex_client = [
            'userid' => 123,
            'company' => 'Test Company Ltd',
            'vat' => 'PT123456789',
            'email' => 'test@example.com',
            'phonenumber' => '+351912345678',
            'billing_street' => 'Test Street 123',
            'billing_city' => 'Porto',
            'billing_zip' => '4000-001',
            'billing_country' => 'PT'
        ];
        
        $moloni_customer_data = [
            'name' => 'Test Company Ltd',
            'vat' => 'PT123456789',
            'email' => 'test@example.com',
            'phone' => '+351912345678',
            'address' => 'Test Street 123',
            'city' => 'Porto',
            'zip_code' => '4000-001',
            'country_id' => 1
        ];
        
        // Mock no existing mapping
        $this->entity_mapping_mock
            ->expects($this->once())
            ->method('get_mapping_by_perfex_id')
            ->with(EntityMappingService::ENTITY_CUSTOMER, $perfex_client_id)
            ->willReturn(null);
        
        // Mock successful Perfex client retrieval
        $this->CI_mock->clients_model
            ->expects($this->once())
            ->method('get')
            ->with($perfex_client_id)
            ->willReturn((object)$perfex_client);
        
        // Mock successful Moloni API call
        $this->api_client_mock
            ->expects($this->once())
            ->method('create_customer')
            ->with($this->callback(function($data) use ($moloni_customer_data) {
                return $data['name'] === $moloni_customer_data['name'] &&
                       $data['vat'] === $moloni_customer_data['vat'] &&
                       $data['email'] === $moloni_customer_data['email'];
            }))
            ->willReturn([
                'success' => true,
                'data' => ['customer_id' => 456]
            ]);
        
        // Mock mapping creation
        $this->entity_mapping_mock
            ->expects($this->once())
            ->method('create_mapping')
            ->with(
                EntityMappingService::ENTITY_CUSTOMER,
                $perfex_client_id,
                456,
                EntityMappingService::DIRECTION_PERFEX_TO_MOLONI
            )
            ->willReturn(1);
        
        // Mock activity logging
        $this->CI_mock->desk_moloni_model
            ->expects($this->once())
            ->method('log_sync_activity')
            ->with($this->callback(function($data) {
                return $data['entity_type'] === 'customer' &&
                       $data['action'] === 'create' &&
                       $data['direction'] === 'perfex_to_moloni' &&
                       $data['status'] === 'success';
            }));
        
        // Execute test
        $result = $this->client_sync_service->sync_perfex_to_moloni($perfex_client_id);
        
        // Assertions
        $this->assertTrue($result['success']);
        $this->assertEquals('Customer created successfully in Moloni', $result['message']);
        $this->assertEquals(1, $result['mapping_id']);
        $this->assertEquals(456, $result['moloni_customer_id']);
        $this->assertEquals('create', $result['action']);
        $this->assertArrayHasKey('execution_time', $result);
    }
    
    public function testSyncMoloniToPerfexSuccess()
    {
        // Test data
        $moloni_customer_id = 456;
        $moloni_customer = [
            'customer_id' => 456,
            'name' => 'Test Company Ltd',
            'vat' => 'PT123456789',
            'email' => 'test@example.com',
            'phone' => '+351912345678',
            'address' => 'Test Street 123',
            'city' => 'Porto',
            'zip_code' => '4000-001',
            'country_id' => 1
        ];
        
        $perfex_client_data = [
            'company' => 'Test Company Ltd',
            'vat' => 'PT123456789',
            'email' => 'test@example.com',
            'phonenumber' => '+351912345678',
            'billing_street' => 'Test Street 123',
            'billing_city' => 'Porto',
            'billing_zip' => '4000-001',
            'billing_country' => 'PT'
        ];
        
        // Mock no existing mapping
        $this->entity_mapping_mock
            ->expects($this->once())
            ->method('get_mapping_by_moloni_id')
            ->with(EntityMappingService::ENTITY_CUSTOMER, $moloni_customer_id)
            ->willReturn(null);
        
        // Mock successful Moloni customer retrieval
        $this->api_client_mock
            ->expects($this->once())
            ->method('get_customer')
            ->with($moloni_customer_id)
            ->willReturn([
                'success' => true,
                'data' => $moloni_customer
            ]);
        
        // Mock successful Perfex client creation
        $this->CI_mock->clients_model
            ->expects($this->once())
            ->method('add')
            ->with($this->callback(function($data) use ($perfex_client_data) {
                return $data['company'] === $perfex_client_data['company'] &&
                       $data['vat'] === $perfex_client_data['vat'] &&
                       $data['email'] === $perfex_client_data['email'];
            }))
            ->willReturn(123);
        
        // Mock mapping creation
        $this->entity_mapping_mock
            ->expects($this->once())
            ->method('create_mapping')
            ->with(
                EntityMappingService::ENTITY_CUSTOMER,
                123,
                $moloni_customer_id,
                EntityMappingService::DIRECTION_MOLONI_TO_PERFEX
            )
            ->willReturn(1);
        
        // Execute test
        $result = $this->client_sync_service->sync_moloni_to_perfex($moloni_customer_id);
        
        // Assertions
        $this->assertTrue($result['success']);
        $this->assertEquals('Customer created successfully in Perfex', $result['message']);
        $this->assertEquals(1, $result['mapping_id']);
        $this->assertEquals(123, $result['perfex_client_id']);
        $this->assertEquals('create', $result['action']);
    }
    
    public function testSyncPerfexToMoloniWithConflict()
    {
        // Test data
        $perfex_client_id = 123;
        $mapping = (object)[
            'id' => 1,
            'perfex_id' => 123,
            'moloni_id' => 456,
            'last_sync_perfex' => '2024-01-01 10:00:00',
            'last_sync_moloni' => '2024-01-01 09:00:00'
        ];
        
        $perfex_client = [
            'userid' => 123,
            'company' => 'Test Company Ltd - Updated',
            'vat' => 'PT123456789',
            'email' => 'test@example.com'
        ];
        
        $moloni_customer = [
            'customer_id' => 456,
            'name' => 'Test Company Ltd - Different Update',
            'vat' => 'PT123456789',
            'email' => 'test@example.com'
        ];
        
        // Mock existing mapping
        $this->entity_mapping_mock
            ->expects($this->once())
            ->method('get_mapping_by_perfex_id')
            ->with(EntityMappingService::ENTITY_CUSTOMER, $perfex_client_id)
            ->willReturn($mapping);
        
        // Mock Perfex client retrieval
        $this->CI_mock->clients_model
            ->expects($this->once())
            ->method('get')
            ->with($perfex_client_id)
            ->willReturn((object)$perfex_client);
        
        // Mock Moloni customer retrieval for conflict check
        $this->api_client_mock
            ->expects($this->once())
            ->method('get_customer')
            ->with(456)
            ->willReturn([
                'success' => true,
                'data' => $moloni_customer
            ]);
        
        // Mock mapping status update to conflict
        $this->entity_mapping_mock
            ->expects($this->once())
            ->method('update_mapping_status')
            ->with(
                1,
                EntityMappingService::STATUS_CONFLICT,
                $this->isType('string')
            );
        
        // Execute test
        $result = $this->client_sync_service->sync_perfex_to_moloni($perfex_client_id);
        
        // Assertions
        $this->assertFalse($result['success']);
        $this->assertStringContains('conflict', strtolower($result['message']));
        $this->assertArrayHasKey('conflict_details', $result);
        $this->assertTrue($result['requires_manual_resolution']);
    }
    
    public function testFindMoloniCustomerMatches()
    {
        // Test data
        $perfex_client = [
            'company' => 'Test Company Ltd',
            'vat' => 'PT123456789',
            'email' => 'test@example.com',
            'phonenumber' => '+351912345678'
        ];
        
        $moloni_matches = [
            [
                'customer_id' => 456,
                'name' => 'Test Company Ltd',
                'vat' => 'PT123456789',
                'email' => 'test@example.com'
            ]
        ];
        
        // Mock VAT search returning exact match
        $this->api_client_mock
            ->expects($this->once())
            ->method('search_customers')
            ->with(['vat' => 'PT123456789'])
            ->willReturn([
                'success' => true,
                'data' => $moloni_matches
            ]);
        
        // Execute test
        $matches = $this->client_sync_service->find_moloni_customer_matches($perfex_client);
        
        // Assertions
        $this->assertCount(1, $matches);
        $this->assertEquals(100, $matches[0]['match_score']); // Exact match
        $this->assertEquals('vat', $matches[0]['match_type']);
        $this->assertEquals(['vat' => 'PT123456789'], $matches[0]['match_criteria']);
    }
    
    public function testSyncPerfexToMoloniWithMissingClient()
    {
        // Test data
        $perfex_client_id = 999; // Non-existent client
        
        // Mock no existing mapping
        $this->entity_mapping_mock
            ->expects($this->once())
            ->method('get_mapping_by_perfex_id')
            ->with(EntityMappingService::ENTITY_CUSTOMER, $perfex_client_id)
            ->willReturn(null);
        
        // Mock client not found
        $this->CI_mock->clients_model
            ->expects($this->once())
            ->method('get')
            ->with($perfex_client_id)
            ->willReturn(null);
        
        // Mock error logging
        $this->error_handler_mock
            ->expects($this->once())
            ->method('log_error')
            ->with('sync', 'CLIENT_SYNC_FAILED', $this->stringContains('not found'));
        
        // Execute test
        $result = $this->client_sync_service->sync_perfex_to_moloni($perfex_client_id);
        
        // Assertions
        $this->assertFalse($result['success']);
        $this->assertStringContains('not found', $result['message']);
        $this->assertArrayHasKey('execution_time', $result);
    }
    
    public function testSyncPerfexToMoloniWithApiError()
    {
        // Test data
        $perfex_client_id = 123;
        $perfex_client = [
            'userid' => 123,
            'company' => 'Test Company Ltd',
            'vat' => 'PT123456789',
            'email' => 'test@example.com'
        ];
        
        // Mock no existing mapping
        $this->entity_mapping_mock
            ->expects($this->once())
            ->method('get_mapping_by_perfex_id')
            ->willReturn(null);
        
        // Mock successful Perfex client retrieval
        $this->CI_mock->clients_model
            ->expects($this->once())
            ->method('get')
            ->willReturn((object)$perfex_client);
        
        // Mock Moloni API error
        $this->api_client_mock
            ->expects($this->once())
            ->method('create_customer')
            ->willReturn([
                'success' => false,
                'message' => 'Moloni API connection failed'
            ]);
        
        // Mock error logging
        $this->error_handler_mock
            ->expects($this->once())
            ->method('log_error')
            ->with('sync', 'CLIENT_SYNC_FAILED', $this->stringContains('Moloni API'));
        
        // Execute test
        $result = $this->client_sync_service->sync_perfex_to_moloni($perfex_client_id);
        
        // Assertions
        $this->assertFalse($result['success']);
        $this->assertStringContains('Moloni API', $result['message']);
    }
    
    public function testClientUpdateWithSignificantChanges()
    {
        // Test data reflecting significant field changes
        $perfex_client_id = 123;
        $original_client = [
            'userid' => 123,
            'company' => 'Test Company Ltd',
            'vat' => 'PT123456789',
            'email' => 'old@example.com'
        ];
        
        $updated_client = [
            'userid' => 123,
            'company' => 'Test Company Ltd',
            'vat' => 'PT123456789',
            'email' => 'new@example.com' // Significant change
        ];
        
        $mapping = (object)[
            'id' => 1,
            'perfex_id' => 123,
            'moloni_id' => 456,
            'sync_status' => EntityMappingService::STATUS_SYNCED
        ];
        
        // Mock existing mapping
        $this->entity_mapping_mock
            ->expects($this->once())
            ->method('get_mapping_by_perfex_id')
            ->willReturn($mapping);
        
        // Mock updated client data
        $this->CI_mock->clients_model
            ->expects($this->once())
            ->method('get')
            ->willReturn((object)$updated_client);
        
        // Mock successful update
        $this->api_client_mock
            ->expects($this->once())
            ->method('update_customer')
            ->willReturn([
                'success' => true,
                'data' => ['customer_id' => 456]
            ]);
        
        // Mock mapping update
        $this->entity_mapping_mock
            ->expects($this->once())
            ->method('update_mapping');
        
        // Execute test
        $result = $this->client_sync_service->sync_perfex_to_moloni($perfex_client_id, true);
        
        // Assertions
        $this->assertTrue($result['success']);
        $this->assertEquals('update', $result['action']);
        $this->assertArrayHasKey('data_changes', $result);
    }
    
    protected function tearDown(): void
    {
        // Clean up any test artifacts
        $this->client_sync_service = null;
        $this->entity_mapping_mock = null;
        $this->api_client_mock = null;
        $this->error_handler_mock = null;
        $this->CI_mock = null;
    }
}