<?php
/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 */

declare(strict_types=1);

namespace DeskMoloni\Tests\E2E;

use PHPUnit\Framework\TestCase;
use DeskMoloni\Tests\TestHelpers;

/**
 * End-to-End Test: Complete User Workflows
 * 
 * This test MUST FAIL initially as part of TDD methodology.
 * Tests complete user journeys from configuration to document access.
 * 
 * @group e2e
 * @group workflow
 */
class CompleteWorkflowTest extends TestCase
{
    private array $testConfig;
    private \PDO $pdo;

    protected function setUp(): void
    {
        global $testConfig;
        $this->testConfig = $testConfig;
        
        $this->pdo = new \PDO(
            "mysql:host={$testConfig['database']['hostname']};dbname={$testConfig['database']['database']}",
            $testConfig['database']['username'],
            $testConfig['database']['password'],
            [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]
        );
        
        // Clean test data
        TestHelpers::clearTestData();
    }

    /**
     * Test complete OAuth setup and client synchronization workflow
     * This test will initially fail until all components are implemented
     */
    public function testCompleteOAuthAndSyncWorkflow(): void
    {
        // Step 1: Admin configures OAuth credentials
        $adminController = new \DeskMoloni\Controllers\AdminController();
        
        $oauthConfig = [
            'client_id' => $this->testConfig['moloni']['client_id'],
            'client_secret' => $this->testConfig['moloni']['client_secret'],
            'sandbox_mode' => true
        ];
        
        $configResult = $adminController->saveOAuthConfiguration($oauthConfig);
        
        $this->assertIsArray($configResult);
        $this->assertTrue($configResult['success'] ?? false, 'OAuth configuration should be saved successfully');
        
        // Verify configuration is encrypted and stored
        $stmt = $this->pdo->prepare("SELECT setting_value, encrypted FROM tbl_desk_moloni_config WHERE setting_key = 'moloni_client_secret'");
        $stmt->execute();
        $config = $stmt->fetch();
        
        $this->assertNotFalse($config, 'Client secret should be stored');
        $this->assertEquals(1, $config['encrypted'], 'Client secret should be encrypted');
        $this->assertNotEquals($oauthConfig['client_secret'], $config['setting_value'], 'Secret should not be stored in plaintext');
        
        // Step 2: Initiate OAuth flow
        $oauthController = new \DeskMoloni\Controllers\OAuthController();
        $authUrl = $oauthController->initiateOAuthFlow();
        
        $this->assertIsString($authUrl);
        $this->assertStringContains('api.moloni.pt', $authUrl);
        $this->assertStringContains('client_id=', $authUrl);
        $this->assertStringContains('response_type=code', $authUrl);
        
        // Step 3: Simulate OAuth callback with authorization code
        $authCode = 'test_authorization_code_123';
        $callbackResult = $oauthController->handleOAuthCallback($authCode);
        
        $this->assertIsArray($callbackResult);
        $this->assertTrue($callbackResult['success'] ?? false, 'OAuth callback should be successful');
        $this->assertArrayHasKey('access_token', $callbackResult);
        $this->assertArrayHasKey('refresh_token', $callbackResult);
        
        // Verify tokens are encrypted and stored
        $stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM tbl_desk_moloni_config WHERE setting_key IN ('oauth_access_token', 'oauth_refresh_token') AND encrypted = 1");
        $stmt->execute();
        $tokenCount = $stmt->fetch();
        
        $this->assertEquals(2, $tokenCount['count'], 'Access and refresh tokens should be encrypted and stored');
        
        // Step 4: Create and sync a client
        $testClient = TestHelpers::createTestClient([
            'userid' => 888888,
            'company' => 'E2E Test Company',
            'vat' => '888888888',
            'phonenumber' => '+351888888888',
            'email' => 'e2e-test@example.com'
        ]);
        
        $clientSyncService = new \DeskMoloni\ClientSyncService();
        $syncResult = $clientSyncService->syncPerfexToMoloni($testClient);
        
        $this->assertIsArray($syncResult);
        $this->assertTrue($syncResult['success'] ?? false, 'Client sync should be successful');
        $this->assertArrayHasKey('moloni_id', $syncResult);
        $this->assertIsInt($syncResult['moloni_id']);
        
        // Verify mapping was created
        $stmt = $this->pdo->prepare("SELECT * FROM tbl_desk_moloni_mapping WHERE entity_type = 'client' AND perfex_id = ?");
        $stmt->execute([$testClient['userid']]);
        $mapping = $stmt->fetch();
        
        $this->assertNotFalse($mapping, 'Client mapping should be created');
        $this->assertEquals($syncResult['moloni_id'], $mapping['moloni_id']);
        
        // Step 5: Create and sync an invoice
        $testInvoice = TestHelpers::createTestInvoice([
            'id' => 777777,
            'clientid' => $testClient['userid'],
            'number' => 'E2E-TEST-001',
            'total' => 123.00
        ]);
        
        $invoiceSyncService = new \DeskMoloni\InvoiceSyncService();
        $invoiceSyncResult = $invoiceSyncService->syncPerfexToMoloni($testInvoice);
        
        $this->assertIsArray($invoiceSyncResult);
        $this->assertTrue($invoiceSyncResult['success'] ?? false, 'Invoice sync should be successful');
        
        // Step 6: Verify complete audit trail
        $stmt = $this->pdo->prepare("SELECT COUNT(*) as count FROM tbl_desk_moloni_sync_log WHERE perfex_id IN (?, ?) AND status = 'success'");
        $stmt->execute([$testClient['userid'], $testInvoice['id']]);
        $logCount = $stmt->fetch();
        
        $this->assertGreaterThanOrEqual(2, $logCount['count'], 'Successful sync operations should be logged');
    }

    /**
     * Test complete client portal document access workflow
     */
    public function testCompleteClientPortalWorkflow(): void
    {
        // Step 1: Set up test client with documents
        $testClient = TestHelpers::createTestClient([
            'userid' => 777777,
            'company' => 'Portal Test Company',
            'email' => 'portal-test@example.com'
        ]);
        
        // Create some test invoices for the client
        $testInvoices = [];
        for ($i = 1; $i <= 3; $i++) {
            $invoice = TestHelpers::createTestInvoice([
                'id' => 666660 + $i,
                'clientid' => $testClient['userid'],
                'number' => "PORTAL-{$i}",
                'total' => 100.00 * $i
            ]);
            $testInvoices[] = $invoice;
        }
        
        // Step 2: Client attempts to access portal
        $clientPortalController = new \DeskMoloni\Controllers\ClientPortalController();
        
        // Test authentication
        $authResult = $clientPortalController->authenticate($testClient['email'], 'test_password');
        
        $this->assertIsArray($authResult);
        $this->assertTrue($authResult['success'] ?? false, 'Client authentication should succeed');
        $this->assertArrayHasKey('session_token', $authResult);
        $this->assertArrayHasKey('permissions', $authResult);
        
        $sessionToken = $authResult['session_token'];
        
        // Verify session is stored
        $stmt = $this->pdo->prepare("SELECT * FROM tbl_desk_moloni_client_sessions WHERE session_token = ?");
        $stmt->execute([$sessionToken]);
        $session = $stmt->fetch();
        
        $this->assertNotFalse($session, 'Client session should be created');
        $this->assertEquals($testClient['userid'], $session['client_id']);
        
        // Step 3: Fetch available documents
        $documentsResult = $clientPortalController->getClientDocuments($sessionToken);
        
        $this->assertIsArray($documentsResult);
        $this->assertTrue($documentsResult['success'] ?? false, 'Document list should be fetched');
        $this->assertArrayHasKey('documents', $documentsResult);
        $this->assertIsArray($documentsResult['documents']);
        
        // Should include the test invoices
        $this->assertGreaterThanOrEqual(count($testInvoices), count($documentsResult['documents']));
        
        // Step 4: Download a document
        if (!empty($documentsResult['documents'])) {
            $firstDocument = $documentsResult['documents'][0];
            
            $downloadResult = $clientPortalController->downloadDocument(
                $sessionToken,
                $firstDocument['id'],
                $firstDocument['type']
            );
            
            $this->assertIsArray($downloadResult);
            $this->assertTrue($downloadResult['success'] ?? false, 'Document download should succeed');
            $this->assertArrayHasKey('download_url', $downloadResult);
            $this->assertArrayHasKey('expires_at', $downloadResult);
            
            // Verify download URL is secure
            $this->assertStringContains('token=', $downloadResult['download_url']);
            $this->assertStringContains('expires=', $downloadResult['download_url']);
        }
        
        // Step 5: Test document filtering
        $filterResult = $clientPortalController->getClientDocuments($sessionToken, [
            'type' => 'invoice',
            'date_from' => date('Y-m-01'),
            'date_to' => date('Y-m-t')
        ]);
        
        $this->assertIsArray($filterResult);
        $this->assertTrue($filterResult['success'] ?? false, 'Filtered document list should be fetched');
        
        // Step 6: Test session expiration
        // Simulate session expiration
        $this->pdo->exec("UPDATE tbl_desk_moloni_client_sessions SET expires_at = DATE_SUB(NOW(), INTERVAL 1 HOUR) WHERE session_token = '{$sessionToken}'");
        
        $expiredResult = $clientPortalController->getClientDocuments($sessionToken);
        
        $this->assertIsArray($expiredResult);
        $this->assertFalse($expiredResult['success'] ?? true, 'Expired session should be rejected');
        $this->assertArrayHasKey('error', $expiredResult);
        $this->assertStringContains('expired', strtolower($expiredResult['error']));
    }

    /**
     * Test complete webhook processing workflow
     */
    public function testCompleteWebhookWorkflow(): void
    {
        // Step 1: Set up existing mapping
        $stmt = $this->pdo->prepare("INSERT INTO tbl_desk_moloni_mapping (entity_type, perfex_id, moloni_id, sync_direction) VALUES (?, ?, ?, ?)");
        $stmt->execute(['invoice', 555555, 444444, 'bidirectional']);
        
        // Step 2: Simulate Moloni webhook
        $webhookPayload = [
            'webhook_id' => 'moloni_webhook_' . time(),
            'event_type' => 'invoice.status_changed',
            'entity_type' => 'invoice',
            'entity_id' => 444444,
            'event_data' => [
                'invoice_id' => 444444,
                'status' => 'paid',
                'payment_date' => date('Y-m-d'),
                'payment_method' => 'bank_transfer'
            ],
            'signature' => 'webhook_signature_hash'
        ];
        
        $webhookController = new \DeskMoloni\Controllers\WebhookController();
        $webhookResult = $webhookController->processWebhook($webhookPayload);
        
        $this->assertIsArray($webhookResult);
        $this->assertTrue($webhookResult['success'] ?? false, 'Webhook should be processed successfully');
        
        // Step 3: Verify webhook was recorded
        $stmt = $this->pdo->prepare("SELECT * FROM tbl_desk_moloni_webhooks WHERE webhook_id = ?");
        $stmt->execute([$webhookPayload['webhook_id']]);
        $webhook = $stmt->fetch();
        
        $this->assertNotFalse($webhook, 'Webhook should be recorded');
        $this->assertEquals(1, $webhook['processed'], 'Webhook should be marked as processed');
        $this->assertEquals(1, $webhook['signature_valid'], 'Webhook signature should be validated');
        
        // Step 4: Verify queue task was created
        $stmt = $this->pdo->prepare("SELECT * FROM tbl_desk_moloni_sync_queue WHERE task_type = 'status_update' AND entity_id = ?");
        $stmt->execute([$webhookPayload['entity_id']]);
        $queueTask = $stmt->fetch();
        
        $this->assertNotFalse($queueTask, 'Queue task should be created from webhook');
        $this->assertEquals('pending', $queueTask['status']);
        
        // Step 5: Process the queue task
        $queueProcessor = new \DeskMoloni\QueueProcessor($this->testConfig);
        $processResult = $queueProcessor->processTask($queueTask['id']);
        
        $this->assertIsArray($processResult);
        $this->assertTrue($processResult['success'] ?? false, 'Queue task should be processed successfully');
        
        // Step 6: Verify Perfex invoice was updated
        $stmt = $this->pdo->prepare("SELECT * FROM tbl_desk_moloni_sync_log WHERE moloni_id = ? AND operation_type = 'status_change'");
        $stmt->execute([$webhookPayload['entity_id']]);
        $syncLog = $stmt->fetch();
        
        $this->assertNotFalse($syncLog, 'Status change should be logged');
        $this->assertEquals('success', $syncLog['status']);
    }

    /**
     * Test complete error handling and recovery workflow
     */
    public function testCompleteErrorHandlingWorkflow(): void
    {
        // Step 1: Create scenario that will cause API error
        $invalidClient = [
            'userid' => 111111,
            'company' => '', // Empty required field
            'vat' => 'INVALID',
            'email' => 'not-an-email'
        ];
        
        $clientSyncService = new \DeskMoloni\ClientSyncService();
        $syncResult = $clientSyncService->syncPerfexToMoloni($invalidClient);
        
        $this->assertIsArray($syncResult);
        $this->assertFalse($syncResult['success'] ?? true, 'Invalid client sync should fail');
        
        // Step 2: Verify error is properly logged
        $stmt = $this->pdo->prepare("SELECT * FROM tbl_desk_moloni_sync_log WHERE perfex_id = ? AND status = 'error'");
        $stmt->execute([$invalidClient['userid']]);
        $errorLog = $stmt->fetch();
        
        $this->assertNotFalse($errorLog, 'Error should be logged');
        $this->assertNotNull($errorLog['error_code']);
        $this->assertNotNull($errorLog['error_message']);
        
        // Step 3: Test retry mechanism
        $retryService = new \DeskMoloni\RetryHandler();
        $retryResult = $retryService->scheduleRetry($errorLog['id'], 'exponential_backoff');
        
        $this->assertIsArray($retryResult);
        $this->assertTrue($retryResult['scheduled'] ?? false, 'Retry should be scheduled');
        
        // Step 4: Verify retry task was created
        $stmt = $this->pdo->prepare("SELECT * FROM tbl_desk_moloni_sync_queue WHERE status = 'retry' AND entity_id = ?");
        $stmt->execute([$invalidClient['userid']]);
        $retryTask = $stmt->fetch();
        
        $this->assertNotFalse($retryTask, 'Retry task should be created');
        $this->assertGreaterThan(1, $retryTask['attempts']);
        
        // Step 5: Test admin notification for persistent failures
        $monitoringService = new \DeskMoloni\MonitoringService();
        $failureCount = 5; // Simulate multiple failures
        
        for ($i = 0; $i < $failureCount; $i++) {
            $monitoringService->recordFailure('client_sync', $invalidClient['userid'], 'validation_error');
        }
        
        $alertResult = $monitoringService->checkAlertThresholds();
        
        $this->assertIsArray($alertResult);
        $this->assertArrayHasKey('alerts_triggered', $alertResult);
        $this->assertGreaterThan(0, count($alertResult['alerts_triggered']), 'Alerts should be triggered for persistent failures');
    }

    /**
     * Test complete performance monitoring workflow
     */
    public function testCompletePerformanceMonitoringWorkflow(): void
    {
        // Step 1: Generate performance data
        $testOperations = [
            ['type' => 'client_sync', 'time_ms' => 1500],
            ['type' => 'client_sync', 'time_ms' => 1200],
            ['type' => 'invoice_sync', 'time_ms' => 2000],
            ['type' => 'invoice_sync', 'time_ms' => 1800],
            ['type' => 'queue_processing', 'time_ms' => 500]
        ];
        
        $performanceMonitor = new \DeskMoloni\PerformanceMonitor();
        
        foreach ($testOperations as $operation) {
            $performanceMonitor->recordOperation($operation['type'], $operation['time_ms']);
        }
        
        // Step 2: Generate performance report
        $reportResult = $performanceMonitor->generateDailyReport();
        
        $this->assertIsArray($reportResult);
        $this->assertArrayHasKey('average_times', $reportResult);
        $this->assertArrayHasKey('operation_counts', $reportResult);
        $this->assertArrayHasKey('performance_alerts', $reportResult);
        
        // Step 3: Test performance threshold alerts
        $slowOperations = [
            ['type' => 'client_sync', 'time_ms' => 8000], // Slow operation
            ['type' => 'client_sync', 'time_ms' => 9000]
        ];
        
        foreach ($slowOperations as $operation) {
            $performanceMonitor->recordOperation($operation['type'], $operation['time_ms']);
        }
        
        $alertCheck = $performanceMonitor->checkPerformanceThresholds();
        
        $this->assertIsArray($alertCheck);
        $this->assertArrayHasKey('threshold_violations', $alertCheck);
        $this->assertGreaterThan(0, count($alertCheck['threshold_violations']), 'Slow operations should trigger threshold violations');
        
        // Step 4: Verify performance data storage
        $stmt = $this->pdo->query("SELECT AVG(execution_time_ms) as avg_time FROM tbl_desk_moloni_sync_log WHERE operation_type IN ('create', 'update') AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)");
        $avgTime = $stmt->fetch();
        
        $this->assertNotNull($avgTime['avg_time'], 'Performance data should be stored in logs');
    }

    protected function tearDown(): void
    {
        // Clean up test data
        $testIds = [888888, 777777, 666661, 666662, 666663, 555555, 444444, 111111];
        
        foreach ($testIds as $id) {
            $this->pdo->exec("DELETE FROM tbl_desk_moloni_mapping WHERE perfex_id = {$id} OR moloni_id = {$id}");
            $this->pdo->exec("DELETE FROM tbl_desk_moloni_sync_log WHERE perfex_id = {$id} OR moloni_id = {$id}");
            $this->pdo->exec("DELETE FROM tbl_desk_moloni_sync_queue WHERE entity_id = {$id}");
        }
        
        $this->pdo->exec("DELETE FROM tbl_desk_moloni_webhooks WHERE webhook_id LIKE '%webhook_%'");
        $this->pdo->exec("DELETE FROM tbl_desk_moloni_client_sessions WHERE client_id IN (777777)");
        $this->pdo->exec("DELETE FROM tbl_desk_moloni_config WHERE setting_key IN ('moloni_client_secret', 'oauth_access_token', 'oauth_refresh_token')");
    }
}