<?php

declare(strict_types=1);

namespace DeskMoloni\Tests\Integration;

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

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\DataProvider;
use DeskMoloni\Tests\TestCase as DeskMoloniTestCase;

/**
 * FullSyncIntegrationTest
 * 
 * Integration tests for complete synchronization workflows
 * Tests end-to-end scenarios with multiple components
 * 
 * @package DeskMoloni\Tests\Integration
 * @author Development Helper
 * @version 1.0.0
 */
#[CoversNothing]
class FullSyncIntegrationTest extends DeskMoloniTestCase
{
    private $client_sync_service;
    private $invoice_sync_service;
    private $queue_processor;
    private $api_client;
    private $mapping_service;
    
    protected function setUp(): void
    {
        parent::setUp();
        
        // Load integration services
        require_once 'modules/desk_moloni/libraries/ClientSyncService.php';
        require_once 'modules/desk_moloni/libraries/InvoiceSyncService.php';
        require_once 'modules/desk_moloni/libraries/QueueProcessor.php';
        require_once 'modules/desk_moloni/libraries/MoloniApiClient.php';
        require_once 'modules/desk_moloni/libraries/EntityMappingService.php';
        
        $this->client_sync_service = new ClientSyncService();
        $this->invoice_sync_service = new InvoiceSyncService();
        $this->queue_processor = new QueueProcessor();
        $this->api_client = new MoloniApiClient();
        $this->mapping_service = new EntityMappingService();
        
        // Initialize test environment
        $this->setupTestEnvironment();
    }
    
    #[Test]
    #[Group('integration')]
    public function testCompleteCustomerToInvoiceWorkflow(): void
    {
        // Step 1: Create customer in Perfex
        $customer_data = $this->createTestCustomer([
            'company' => 'Integration Test Customer Ltd',
            'email' => 'integration@testcustomer.com',
            'vat' => 'PT123456789'
        ]);
        
        $this->assertNotNull($customer_data['perfex_id']);
        
        // Step 2: Sync customer to Moloni
        $customer_sync_result = $this->client_sync_service->sync_client_to_moloni($customer_data['perfex_id']);
        
        $this->assertTrue($customer_sync_result['success']);
        $this->assertNotNull($customer_sync_result['moloni_id']);
        
        // Step 3: Create invoice in Perfex for this customer
        $invoice_data = $this->createTestInvoice([
            'clientid' => $customer_data['perfex_id'],
            'number' => 'INT-TEST-' . date('Ymd-His'),
            'subtotal' => 100.00,
            'total_tax' => 23.00,
            'total' => 123.00
        ]);
        
        $this->assertNotNull($invoice_data['perfex_id']);
        
        // Step 4: Sync invoice to Moloni
        $invoice_sync_result = $this->invoice_sync_service->sync_invoice_to_moloni($invoice_data['perfex_id']);
        
        $this->assertTrue($invoice_sync_result['success']);
        $this->assertNotNull($invoice_sync_result['moloni_id']);
        
        // Step 5: Verify mappings were created
        $customer_mapping = $this->mapping_service->get_mapping_by_perfex_id('customer', $customer_data['perfex_id']);
        $invoice_mapping = $this->mapping_service->get_mapping_by_perfex_id('invoice', $invoice_data['perfex_id']);
        
        $this->assertNotNull($customer_mapping);
        $this->assertNotNull($invoice_mapping);
        $this->assertEquals('synced', $customer_mapping->sync_status);
        $this->assertEquals('synced', $invoice_mapping->sync_status);
        
        // Step 6: Verify data consistency
        $this->verifyDataConsistency($customer_data['perfex_id'], $customer_sync_result['moloni_id'], 'customer');
        $this->verifyDataConsistency($invoice_data['perfex_id'], $invoice_sync_result['moloni_id'], 'invoice');
    }
    
    #[Test]
    #[Group('integration')]
    public function testQueueBasedSynchronization(): void
    {
        // Create multiple entities for queue processing
        $customers = [];
        for ($i = 1; $i <= 5; $i++) {
            $customers[] = $this->createTestCustomer([
                'company' => "Queue Test Customer {$i}",
                'email' => "queue{$i}@test.com",
                'vat' => "PT12345678{$i}"
            ]);
        }
        
        // Add all customers to sync queue
        $job_ids = [];
        foreach ($customers as $customer) {
            $job_id = $this->queue_processor->add_to_queue(
                'customer',
                $customer['perfex_id'],
                'create',
                'perfex_to_moloni',
                QueueProcessor::PRIORITY_NORMAL
            );
            
            $this->assertNotFalse($job_id);
            $job_ids[] = $job_id;
        }
        
        // Process queue
        $process_result = $this->queue_processor->process_queue(count($customers), 300);
        
        $this->assertEquals(count($customers), $process_result['processed']);
        $this->assertEquals(count($customers), $process_result['success']);
        $this->assertEquals(0, $process_result['errors']);
        
        // Verify all customers were synced
        foreach ($customers as $customer) {
            $mapping = $this->mapping_service->get_mapping_by_perfex_id('customer', $customer['perfex_id']);
            $this->assertNotNull($mapping);
            $this->assertEquals('synced', $mapping->sync_status);
        }
    }
    
    #[Test]
    #[Group('integration')]
    public function testBidirectionalSyncWithConflicts(): void
    {
        // Create customer and sync initially
        $customer_data = $this->createTestCustomer([
            'company' => 'Bidirectional Test Company',
            'email' => 'bidirectional@test.com'
        ]);
        
        $initial_sync = $this->client_sync_service->sync_client_to_moloni($customer_data['perfex_id']);
        $this->assertTrue($initial_sync['success']);
        
        // Simulate concurrent updates
        // Update in Perfex
        $this->updatePerfexCustomer($customer_data['perfex_id'], [
            'company' => 'Updated by Perfex System',
            'phonenumber' => '+351999111222'
        ]);
        
        // Simulate update in Moloni (mock the API response)
        $this->simulateMoloniCustomerUpdate($initial_sync['moloni_id'], [
            'name' => 'Updated by Moloni System',
            'phone' => '+351888333444'
        ]);
        
        // Trigger bidirectional sync
        $bidirectional_result = $this->client_sync_service->bidirectional_sync(
            $customer_data['perfex_id'],
            $initial_sync['moloni_id']
        );
        
        // Should detect conflicts
        $this->assertArrayHasKey('conflicts_detected', $bidirectional_result);
        
        if ($bidirectional_result['conflicts_detected']) {
            $this->assertArrayHasKey('conflicted_fields', $bidirectional_result);
            $this->assertContains('company', $bidirectional_result['conflicted_fields']);
            $this->assertContains('phone', $bidirectional_result['conflicted_fields']);
        }
    }
    
    #[Test]
    #[Group('integration')]
    public function testWebhookTriggeredSync(): void
    {
        // Create customer and sync to establish mapping
        $customer_data = $this->createTestCustomer([
            'company' => 'Webhook Test Company',
            'email' => 'webhook@test.com'
        ]);
        
        $sync_result = $this->client_sync_service->sync_client_to_moloni($customer_data['perfex_id']);
        $this->assertTrue($sync_result['success']);
        
        // Simulate webhook from Moloni
        $webhook_payload = [
            'entity_type' => 'customer',
            'entity_id' => $sync_result['moloni_id'],
            'action' => 'update',
            'event_type' => 'customer.updated',
            'timestamp' => time(),
            'data' => [
                'customer_id' => $sync_result['moloni_id'],
                'name' => 'Updated via Webhook',
                'email' => 'updated.webhook@test.com'
            ]
        ];
        
        // Process webhook (would be handled by WebhookController in real scenario)
        $webhook_result = $this->processWebhookPayload($webhook_payload);
        
        $this->assertTrue($webhook_result['success']);
        $this->assertArrayHasKey('job_id', $webhook_result);
        
        // Process the queued job
        $process_result = $this->queue_processor->process_queue(1, 60);
        
        $this->assertEquals(1, $process_result['processed']);
        $this->assertEquals(1, $process_result['success']);
        
        // Verify customer was updated in Perfex
        $updated_customer = $this->getPerfexCustomer($customer_data['perfex_id']);
        $this->assertEquals('Updated via Webhook', $updated_customer['company']);
        $this->assertEquals('updated.webhook@test.com', $updated_customer['email']);
    }
    
    #[Test]
    #[Group('integration')]
    public function testErrorHandlingAndRecovery(): void
    {
        // Create customer with invalid data to trigger errors
        $customer_data = $this->createTestCustomer([
            'company' => 'Error Test Company',
            'email' => 'invalid-email-format',  // Invalid email
            'vat' => 'INVALID_VAT'              // Invalid VAT
        ]);
        
        // First sync attempt should fail with validation errors
        $sync_result = $this->client_sync_service->sync_client_to_moloni($customer_data['perfex_id']);
        
        $this->assertFalse($sync_result['success']);
        $this->assertArrayHasKey('errors', $sync_result);
        
        // Fix the customer data
        $this->updatePerfexCustomer($customer_data['perfex_id'], [
            'email' => 'corrected@email.com',
            'vat' => 'PT123456789'
        ]);
        
        // Retry sync should now succeed
        $retry_result = $this->client_sync_service->sync_client_to_moloni($customer_data['perfex_id']);
        
        $this->assertTrue($retry_result['success']);
        $this->assertNotNull($retry_result['moloni_id']);
        
        // Verify mapping was created
        $mapping = $this->mapping_service->get_mapping_by_perfex_id('customer', $customer_data['perfex_id']);
        $this->assertNotNull($mapping);
        $this->assertEquals('synced', $mapping->sync_status);
    }
    
    #[Test]
    #[Group('integration')]
    #[DataProvider('massDataProvider')]
    public function testMassDataSynchronization(int $customer_count, int $invoice_count): void
    {
        $start_time = microtime(true);
        
        // Create customers
        $customers = [];
        for ($i = 1; $i <= $customer_count; $i++) {
            $customers[] = $this->createTestCustomer([
                'company' => "Mass Test Customer {$i}",
                'email' => "mass{$i}@test.com"
            ]);
        }
        
        // Sync all customers using batch processing
        $customer_ids = array_column($customers, 'perfex_id');
        $batch_result = $this->client_sync_service->batch_sync_clients_to_moloni($customer_ids);
        
        $this->assertEquals($customer_count, $batch_result['total']);
        $this->assertEquals($customer_count, $batch_result['success_count']);
        
        // Create invoices for each customer
        $invoices = [];
        foreach ($customers as $index => $customer) {
            for ($j = 1; $j <= $invoice_count; $j++) {
                $invoices[] = $this->createTestInvoice([
                    'clientid' => $customer['perfex_id'],
                    'number' => "MASS-{$index}-{$j}-" . date('His'),
                    'subtotal' => 50.00 * $j,
                    'total' => 61.50 * $j  // With 23% tax
                ]);
            }
        }
        
        // Sync all invoices
        $invoice_ids = array_column($invoices, 'perfex_id');
        $invoice_batch_result = $this->invoice_sync_service->batch_sync_invoices_to_moloni($invoice_ids);
        
        $total_invoices = $customer_count * $invoice_count;
        $this->assertEquals($total_invoices, $invoice_batch_result['total']);
        
        $execution_time = microtime(true) - $start_time;
        
        // Performance assertions
        $this->assertLessThan(300, $execution_time, 'Mass sync should complete within 5 minutes');
        
        // Memory usage should be reasonable
        $memory_mb = memory_get_peak_usage(true) / (1024 * 1024);
        $this->assertLessThan(256, $memory_mb, 'Memory usage should be under 256MB');
        
        echo "\nMass sync performance: {$customer_count} customers + " . 
             "{$total_invoices} invoices in " . round($execution_time, 2) . "s using " . 
             round($memory_mb, 2) . "MB\n";
    }
    
    public static function massDataProvider(): array
    {
        return [
            'Small batch' => [5, 2],   // 5 customers, 2 invoices each = 10 invoices
            'Medium batch' => [10, 3], // 10 customers, 3 invoices each = 30 invoices
            'Large batch' => [20, 5]   // 20 customers, 5 invoices each = 100 invoices
        ];
    }
    
    #[Test]
    #[Group('integration')]
    public function testConcurrentSyncOperations(): void
    {
        if (!extension_loaded('pcntl')) {
            $this->markTestSkipped('pcntl extension not available for concurrent testing');
        }
        
        $customers = [];
        for ($i = 1; $i <= 6; $i++) {
            $customers[] = $this->createTestCustomer([
                'company' => "Concurrent Test Customer {$i}",
                'email' => "concurrent{$i}@test.com"
            ]);
        }
        
        // Split customers into groups for concurrent processing
        $group1 = array_slice($customers, 0, 3);
        $group2 = array_slice($customers, 3, 3);
        
        $pids = [];
        
        // Fork for group 1
        $pid1 = pcntl_fork();
        if ($pid1 == 0) {
            // Child process 1
            foreach ($group1 as $customer) {
                $result = $this->client_sync_service->sync_client_to_moloni($customer['perfex_id']);
                if (!$result['success']) {
                    exit(1);
                }
            }
            exit(0);
        } else {
            $pids[] = $pid1;
        }
        
        // Fork for group 2
        $pid2 = pcntl_fork();
        if ($pid2 == 0) {
            // Child process 2
            foreach ($group2 as $customer) {
                $result = $this->client_sync_service->sync_client_to_moloni($customer['perfex_id']);
                if (!$result['success']) {
                    exit(1);
                }
            }
            exit(0);
        } else {
            $pids[] = $pid2;
        }
        
        // Wait for all processes to complete
        $all_success = true;
        foreach ($pids as $pid) {
            $status = 0;
            pcntl_waitpid($pid, $status);
            if (pcntl_wexitstatus($status) !== 0) {
                $all_success = false;
            }
        }
        
        $this->assertTrue($all_success, 'All concurrent sync operations should succeed');
        
        // Verify all customers were synced
        foreach ($customers as $customer) {
            $mapping = $this->mapping_service->get_mapping_by_perfex_id('customer', $customer['perfex_id']);
            $this->assertNotNull($mapping);
            $this->assertEquals('synced', $mapping->sync_status);
        }
    }
    
    private function setupTestEnvironment(): void
    {
        // Clean up any previous test data
        $this->cleanupTestData();
        
        // Initialize test configuration
        $test_config = [
            'sync_enabled' => true,
            'batch_size' => 10,
            'api_timeout' => 30,
            'max_retries' => 3
        ];
        
        foreach ($test_config as $key => $value) {
            // Set test configuration (would use config model in real implementation)
        }
    }
    
    private function createTestCustomer(array $data): array
    {
        // Mock customer creation in Perfex
        $perfex_id = rand(10000, 99999);
        
        // Store test customer data
        $this->test_customers[] = array_merge($data, ['perfex_id' => $perfex_id]);
        
        return ['perfex_id' => $perfex_id];
    }
    
    private function createTestInvoice(array $data): array
    {
        // Mock invoice creation in Perfex
        $perfex_id = rand(10000, 99999);
        
        // Store test invoice data
        $this->test_invoices[] = array_merge($data, ['perfex_id' => $perfex_id]);
        
        return ['perfex_id' => $perfex_id];
    }
    
    private function verifyDataConsistency(int $perfex_id, string $moloni_id, string $entity_type): void
    {
        // This would compare data between Perfex and Moloni to ensure consistency
        // For now, we'll just verify that both IDs exist and mapping is correct
        
        $mapping = $this->mapping_service->get_mapping_by_perfex_id($entity_type, $perfex_id);
        $this->assertNotNull($mapping);
        $this->assertEquals($moloni_id, $mapping->moloni_id);
        $this->assertEquals('synced', $mapping->sync_status);
    }
    
    private $test_customers = [];
    private $test_invoices = [];
    
    protected function tearDown(): void
    {
        // Clean up test data
        $this->cleanupTestData();
        
        $this->client_sync_service = null;
        $this->invoice_sync_service = null;
        $this->queue_processor = null;
        $this->api_client = null;
        $this->mapping_service = null;
        
        parent::tearDown();
    }
    
    private function cleanupTestData(): void
    {
        // Clean up test customers and invoices
        // In real implementation, this would clean up database records
        $this->test_customers = [];
        $this->test_invoices = [];
    }
}