<?php

declare(strict_types=1);

namespace DeskMoloni\Tests\Feature;

/**
 * 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;

/**
 * SyncWorkflowFeatureTest
 * 
 * Feature tests for complete synchronization workflows
 * Tests real-world scenarios from user perspective
 * 
 * @package DeskMoloni\Tests\Feature
 * @author Development Helper
 * @version 1.0.0
 */
#[CoversNothing]
class SyncWorkflowFeatureTest extends DeskMoloniTestCase
{
    private $test_scenarios = [];
    
    protected function setUp(): void
    {
        parent::setUp();
        
        // Initialize test scenarios
        $this->setupTestScenarios();
    }
    
    #[Test]
    #[Group('feature')]
    public function testNewCustomerRegistrationAndSync(): void
    {
        // SCENARIO: New customer registers in CRM, gets synced to Moloni
        
        // Step 1: Customer registers or is created in Perfex CRM
        $customer_data = [
            'company' => 'Feature Test Company Lda',
            'firstname' => 'João',
            'lastname' => 'Silva',
            'email' => 'joao.silva@featuretest.pt',
            'phonenumber' => '+351912345678',
            'vat' => 'PT123456789',
            'address' => 'Rua da Feature 123',
            'city' => 'Porto',
            'zip' => '4000-001',
            'country' => 'PT'
        ];
        
        $perfex_customer_id = $this->simulateCustomerRegistration($customer_data);
        $this->assertIsInt($perfex_customer_id);
        $this->assertGreaterThan(0, $perfex_customer_id);
        
        // Step 2: System detects new customer and triggers sync
        $sync_triggered = $this->simulateNewCustomerHook($perfex_customer_id);
        $this->assertTrue($sync_triggered);
        
        // Step 3: Background job processes the sync
        $job_processed = $this->waitForJobCompletion('customer', $perfex_customer_id, 30);
        $this->assertTrue($job_processed);
        
        // Step 4: Verify customer exists in Moloni
        $moloni_customer = $this->getMoloniCustomerByPerfexId($perfex_customer_id);
        $this->assertNotNull($moloni_customer);
        $this->assertEquals($customer_data['company'], $moloni_customer['name']);
        $this->assertEquals($customer_data['email'], $moloni_customer['email']);
        $this->assertEquals($customer_data['vat'], $moloni_customer['vat']);
        
        // Step 5: Verify mapping was created
        $mapping = $this->getCustomerMapping($perfex_customer_id);
        $this->assertNotNull($mapping);
        $this->assertEquals('synced', $mapping['status']);
        $this->assertEquals('perfex_to_moloni', $mapping['direction']);
        
        $this->addTestScenario('customer_registration', [
            'perfex_id' => $perfex_customer_id,
            'moloni_id' => $moloni_customer['customer_id'],
            'status' => 'completed'
        ]);
    }
    
    #[Test]
    #[Group('feature')]
    public function testInvoiceCreationWithCustomerSync(): void
    {
        // SCENARIO: Create invoice for existing customer, ensure both are synced
        
        // Step 1: Get or create synced customer
        $customer_scenario = $this->getTestScenario('customer_registration');
        if (!$customer_scenario) {
            $customer_scenario = $this->createTestCustomerScenario();
        }
        
        // Step 2: Create invoice in Perfex for this customer
        $invoice_data = [
            'clientid' => $customer_scenario['perfex_id'],
            'number' => 'FT-' . date('Y') . '-' . sprintf('%04d', rand(1, 9999)),
            'date' => date('Y-m-d'),
            'duedate' => date('Y-m-d', strtotime('+30 days')),
            'currency' => 1,
            'subtotal' => 250.00,
            'total_tax' => 57.50,
            'total' => 307.50,
            'status' => 1,
            'items' => [
                [
                    'description' => 'Serviços de Consultoria',
                    'qty' => 5,
                    'rate' => 50.00,
                    'unit' => 'hora'
                ]
            ]
        ];
        
        $perfex_invoice_id = $this->simulateInvoiceCreation($invoice_data);
        $this->assertIsInt($perfex_invoice_id);
        
        // Step 3: System triggers invoice sync
        $sync_triggered = $this->simulateInvoiceCreatedHook($perfex_invoice_id);
        $this->assertTrue($sync_triggered);
        
        // Step 4: Wait for sync completion
        $job_processed = $this->waitForJobCompletion('invoice', $perfex_invoice_id, 45);
        $this->assertTrue($job_processed);
        
        // Step 5: Verify invoice exists in Moloni
        $moloni_invoice = $this->getMoloniInvoiceByPerfexId($perfex_invoice_id);
        $this->assertNotNull($moloni_invoice);
        $this->assertEquals($invoice_data['number'], $moloni_invoice['number']);
        $this->assertEquals($invoice_data['total'], $moloni_invoice['net_value']);
        $this->assertEquals($customer_scenario['moloni_id'], $moloni_invoice['customer_id']);
        
        // Step 6: Verify invoice mapping
        $mapping = $this->getInvoiceMapping($perfex_invoice_id);
        $this->assertNotNull($mapping);
        $this->assertEquals('synced', $mapping['status']);
    }
    
    #[Test]
    #[Group('feature')]
    public function testCustomerUpdatesFromMoloni(): void
    {
        // SCENARIO: Customer details updated in Moloni, changes sync back to Perfex
        
        // Step 1: Get existing synced customer
        $customer_scenario = $this->getTestScenario('customer_registration') ?: $this->createTestCustomerScenario();
        
        // Step 2: Simulate customer update in Moloni (via webhook)
        $moloni_updates = [
            'name' => 'Feature Test Company Lda - UPDATED',
            'email' => 'updated.joao.silva@featuretest.pt',
            'phone' => '+351987654321',
            'address' => 'Nova Rua da Feature 456'
        ];
        
        $webhook_triggered = $this->simulateMoloniWebhook([
            'entity_type' => 'customer',
            'entity_id' => $customer_scenario['moloni_id'],
            'action' => 'update',
            'timestamp' => time(),
            'data' => $moloni_updates
        ]);
        
        $this->assertTrue($webhook_triggered);
        
        // Step 3: Wait for webhook processing
        $webhook_processed = $this->waitForWebhookProcessing($customer_scenario['moloni_id'], 30);
        $this->assertTrue($webhook_processed);
        
        // Step 4: Verify changes were applied to Perfex customer
        $updated_perfex_customer = $this->getPerfexCustomer($customer_scenario['perfex_id']);
        $this->assertNotNull($updated_perfex_customer);
        $this->assertEquals($moloni_updates['name'], $updated_perfex_customer['company']);
        $this->assertEquals($moloni_updates['email'], $updated_perfex_customer['email']);
        $this->assertEquals($moloni_updates['phone'], $updated_perfex_customer['phonenumber']);
        
        // Step 5: Verify sync log shows bidirectional update
        $sync_log = $this->getLatestSyncLog('customer', $customer_scenario['perfex_id']);
        $this->assertEquals('moloni_to_perfex', $sync_log['direction']);
        $this->assertEquals('success', $sync_log['status']);
    }
    
    #[Test]
    #[Group('feature')]
    public function testConflictResolutionWorkflow(): void
    {
        // SCENARIO: Customer updated in both systems simultaneously, conflict resolution
        
        // Step 1: Get synced customer
        $customer_scenario = $this->getTestScenario('customer_registration') ?: $this->createTestCustomerScenario();
        
        // Step 2: Update customer in Perfex
        $perfex_updates = [
            'company' => 'Perfex Updated Company Name',
            'phonenumber' => '+351111222333',
            'admin_notes' => 'Updated from Perfex at ' . date('H:i:s')
        ];
        
        $this->updatePerfexCustomer($customer_scenario['perfex_id'], $perfex_updates);
        
        // Step 3: Simulate concurrent update in Moloni (slightly later)
        usleep(100000); // 100ms delay
        $moloni_updates = [
            'name' => 'Moloni Updated Company Name',
            'phone' => '+351444555666',
            'notes' => 'Updated from Moloni at ' . date('H:i:s')
        ];
        
        $this->simulateMoloniWebhook([
            'entity_type' => 'customer',
            'entity_id' => $customer_scenario['moloni_id'],
            'action' => 'update',
            'timestamp' => time(),
            'data' => $moloni_updates
        ]);
        
        // Step 4: Trigger bidirectional sync
        $this->triggerBidirectionalSync('customer', $customer_scenario['perfex_id'], $customer_scenario['moloni_id']);
        
        // Step 5: Wait for conflict detection and resolution
        $conflict_resolved = $this->waitForConflictResolution($customer_scenario['perfex_id'], 60);
        $this->assertTrue($conflict_resolved);
        
        // Step 6: Verify conflict was handled according to configured strategy
        $conflict_log = $this->getConflictLog($customer_scenario['perfex_id']);
        $this->assertNotNull($conflict_log);
        $this->assertArrayHasKey('conflicted_fields', $conflict_log);
        $this->assertArrayHasKey('resolution_strategy', $conflict_log);
        $this->assertArrayHasKey('winning_source', $conflict_log);
        
        // Step 7: Verify final state is consistent
        $final_perfex = $this->getPerfexCustomer($customer_scenario['perfex_id']);
        $final_moloni = $this->getMoloniCustomer($customer_scenario['moloni_id']);
        
        // Both should have the same final values (according to resolution strategy)
        $this->assertEquals($final_perfex['company'], $final_moloni['name']);
        $this->assertEquals($final_perfex['phonenumber'], $final_moloni['phone']);
    }
    
    #[Test]
    #[Group('feature')]
    #[DataProvider('businessScenarioProvider')]
    public function testBusinessScenarios(string $scenario_name, array $scenario_data): void
    {
        switch ($scenario_name) {
            case 'new_client_full_cycle':
                $this->executeNewClientFullCycle($scenario_data);
                break;
                
            case 'seasonal_bulk_sync',
                $this->executeSeasonalBulkSync($scenario_data);
                break;
                
            case 'api_outage_recovery':
                $this->executeApiOutageRecovery($scenario_data);
                break;
                
            case 'data_migration':
                $this->executeDataMigration($scenario_data);
                break;
                
            default:
                $this->fail("Unknown scenario: {$scenario_name}");
        }
    }
    
    public static function businessScenarioProvider(): array
    {
        return [
            'New client full cycle' => [
                'new_client_full_cycle',
                [
                    'customer_count' => 3,
                    'invoices_per_customer' => 2,
                    'include_payments' => true
                ]
            ],
            'Seasonal bulk sync' => [
                'seasonal_bulk_sync',
                [
                    'customer_count' => 50,
                    'batch_size' => 10,
                    'include_estimates' => true
                ]
            ],
            'API outage recovery' => [
                'api_outage_recovery',
                [
                    'simulate_outage_duration' => 30, // seconds
                    'pending_jobs' => 25,
                    'test_retry_logic' => true
                ]
            ],
            'Data migration' => [
                'data_migration',
                [
                    'legacy_customer_count' => 20,
                    'validate_data_integrity' => true,
                    'rollback_on_failure' => true
                ]
            ]
        ];
    }
    
    #[Test]
    #[Group('feature')]
    #[Group('slow')]
    public function testLongRunningSync(): void
    {
        // SCENARIO: Long-running synchronization process with monitoring
        
        $start_time = microtime(true);
        $total_customers = 100;
        $total_invoices = 300;
        
        // Step 1: Create large dataset
        $customers = [];
        for ($i = 1; $i <= $total_customers; $i++) {
            $customers[] = $this->createTestCustomer([
                'company' => "Long Running Test Company {$i}",
                'email' => "longrun{$i}@test.com"
            ]);
        }
        
        $invoices = [];
        foreach ($customers as $index => $customer) {
            for ($j = 1; $j <= 3; $j++) {
                $invoices[] = $this->createTestInvoice([
                    'clientid' => $customer['perfex_id'],
                    'number' => "LR-{$index}-{$j}",
                    'total' => 100 + ($j * 50)
                ]);
            }
        }
        
        // Step 2: Queue all sync jobs
        $customer_jobs = $this->queueBulkSync('customer', array_column($customers, 'perfex_id'));
        $invoice_jobs = $this->queueBulkSync('invoice', array_column($invoices, 'perfex_id'));
        
        $this->assertEquals($total_customers, count($customer_jobs));
        $this->assertEquals($total_invoices, count($invoice_jobs));
        
        // Step 3: Monitor progress
        $progress_history = [];
        $timeout = 600; // 10 minutes max
        
        while (!$this->allJobsCompleted($customer_jobs + $invoice_jobs) && (microtime(true) - $start_time) < $timeout) {
            $current_progress = $this->getQueueProgress();
            $progress_history[] = array_merge($current_progress, [
                'timestamp' => microtime(true),
                'elapsed' => microtime(true) - $start_time
            ]);
            
            sleep(5); // Check every 5 seconds
        }
        
        $total_time = microtime(true) - $start_time;
        
        // Step 4: Verify completion
        $this->assertTrue($this->allJobsCompleted($customer_jobs + $invoice_jobs));
        $this->assertLessThan($timeout, $total_time);
        
        // Step 5: Verify data integrity
        $successful_customers = $this->countSuccessfulSyncs('customer');
        $successful_invoices = $this->countSuccessfulSyncs('invoice');
        
        $this->assertEquals($total_customers, $successful_customers);
        $this->assertEquals($total_invoices, $successful_invoices);
        
        // Step 6: Performance metrics
        $avg_customer_sync_time = $total_time / $total_customers;
        $avg_invoice_sync_time = $total_time / $total_invoices;
        
        echo "\nLong-running sync performance:\n";
        echo "Total time: " . round($total_time, 2) . "s\n";
        echo "Customers: {$total_customers} in " . round($avg_customer_sync_time, 3) . "s avg\n";
        echo "Invoices: {$total_invoices} in " . round($avg_invoice_sync_time, 3) . "s avg\n";
        echo "Memory peak: " . round(memory_get_peak_usage(true) / 1024 / 1024, 2) . "MB\n";
        
        // Performance assertions
        $this->assertLessThan(5.0, $avg_customer_sync_time, 'Customer sync should average under 5s');
        $this->assertLessThan(3.0, $avg_invoice_sync_time, 'Invoice sync should average under 3s');
    }
    
    private function setupTestScenarios(): void
    {
        $this->test_scenarios = [];
    }
    
    private function addTestScenario(string $name, array $data): void
    {
        $this->test_scenarios[$name] = $data;
    }
    
    private function getTestScenario(string $name): ?array
    {
        return $this->test_scenarios[$name] ?? null;
    }
    
    private function createTestCustomerScenario(): array
    {
        // Create a test customer scenario for tests that need one
        $customer_data = [
            'company' => 'Default Test Customer',
            'email' => 'default@test.com',
            'vat' => 'PT999888777'
        ];
        
        $perfex_id = $this->simulateCustomerRegistration($customer_data);
        $this->simulateNewCustomerHook($perfex_id);
        $this->waitForJobCompletion('customer', $perfex_id, 30);
        
        $moloni_customer = $this->getMoloniCustomerByPerfexId($perfex_id);
        
        $scenario = [
            'perfex_id' => $perfex_id,
            'moloni_id' => $moloni_customer['customer_id'],
            'status' => 'completed'
        ];
        
        $this->addTestScenario('default_customer', $scenario);
        return $scenario;
    }
    
    // Helper methods for simulation (would be implemented based on actual system)
    private function simulateCustomerRegistration(array $data): int { return rand(1000, 9999); }
    private function simulateInvoiceCreation(array $data): int { return rand(1000, 9999); }
    private function simulateNewCustomerHook(int $id): bool { return true; }
    private function simulateInvoiceCreatedHook(int $id): bool { return true; }
    private function simulateMoloniWebhook(array $data): bool { return true; }
    private function waitForJobCompletion(string $type, int $id, int $timeout): bool { return true; }
    private function waitForWebhookProcessing(string $moloni_id, int $timeout): bool { return true; }
    private function waitForConflictResolution(int $perfex_id, int $timeout): bool { return true; }
    private function getMoloniCustomerByPerfexId(int $perfex_id): ?array { 
        return ['customer_id' => 'MOL' . $perfex_id, 'name' => 'Test', 'email' => 'test@test.com', 'vat' => 'PT123456789']; 
    }
    private function getMoloniInvoiceByPerfexId(int $perfex_id): ?array { 
        return ['invoice_id' => 'INV' . $perfex_id, 'number' => 'TEST-001', 'net_value' => 307.50, 'customer_id' => 'MOL123']; 
    }
    private function getCustomerMapping(int $perfex_id): ?array { 
        return ['status' => 'synced', 'direction' => 'perfex_to_moloni']; 
    }
    private function getInvoiceMapping(int $perfex_id): ?array { 
        return ['status' => 'synced', 'direction' => 'perfex_to_moloni']; 
    }
    private function getPerfexCustomer(int $id): ?array { 
        return ['company' => 'Updated Company', 'email' => 'updated@test.com', 'phonenumber' => '+351123456789']; 
    }
    private function getMoloniCustomer(string $id): ?array { 
        return ['name' => 'Updated Company', 'email' => 'updated@test.com', 'phone' => '+351123456789']; 
    }
    private function updatePerfexCustomer(int $id, array $data): bool { return true; }
    private function triggerBidirectionalSync(string $type, int $perfex_id, string $moloni_id): bool { return true; }
    private function getLatestSyncLog(string $type, int $id): ?array { 
        return ['direction' => 'moloni_to_perfex', 'status' => 'success']; 
    }
    private function getConflictLog(int $perfex_id): ?array { 
        return [
            'conflicted_fields' => ['company', 'phone'], 
            'resolution_strategy' => 'last_modified_wins', 
            'winning_source' => 'moloni'
        ]; 
    }
    
    protected function tearDown(): void
    {
        // Clean up test scenarios
        $this->test_scenarios = [];
        
        parent::tearDown();
    }
}