<?php

namespace Tests\Integration;

use Tests\TestCase;
use App\Services\Core\ModuleManager;
use App\Services\Core\EventBus;
use App\Services\Core\ConfigurationManager;
use App\Modules\PersianModule;
use App\Modules\PaymentModule;
use App\Modules\ContentModule;
use App\Modules\AnalyticsModule;
use App\Modules\NotificationModule;
use App\DTOs\TelegramUpdateDTO;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;

class ModularSystemIntegrationTest extends TestCase
{
    use RefreshDatabase;

    private ModuleManager $moduleManager;
    private EventBus $eventBus;
    private ConfigurationManager $configManager;

    protected function setUp(): void
    {
        parent::setUp();
        
        $this->eventBus = new EventBus();
        $this->configManager = new ConfigurationManager();
        $this->moduleManager = new ModuleManager($this->eventBus, $this->configManager);
    }

    protected function tearDown(): void
    {
        Mockery::close();
        parent::tearDown();
    }

    public function test_module_system_initialization()
    {
        // Register all core modules
        $persianModule = new PersianModule($this->moduleManager, $this->eventBus, $this->configManager);
        $contentModule = new ContentModule($this->moduleManager, $this->eventBus, $this->configManager);
        $analyticsModule = new AnalyticsModule($this->moduleManager, $this->eventBus, $this->configManager);
        
        $this->assertTrue($this->moduleManager->registerModule($persianModule));
        $this->assertTrue($this->moduleManager->registerModule($contentModule));
        $this->assertTrue($this->moduleManager->registerModule($analyticsModule));

        $registeredModules = $this->moduleManager->getRegisteredModules();
        $this->assertCount(3, $registeredModules);
        
        $this->assertArrayHasKey('PersianModule', $registeredModules);
        $this->assertArrayHasKey('ContentModule', $registeredModules);
        $this->assertArrayHasKey('AnalyticsModule', $registeredModules);
    }

    public function test_inter_module_communication_via_eventbus()
    {
        $persianModule = new PersianModule($this->moduleManager, $this->eventBus, $this->configManager);
        $analyticsModule = new AnalyticsModule($this->moduleManager, $this->eventBus, $this->configManager);
        
        $this->moduleManager->registerModule($persianModule);
        $this->moduleManager->registerModule($analyticsModule);
        
        // Initialize modules to set up event listeners
        $persianModule->initialize();
        $analyticsModule->initialize();
        
        $user = User::factory()->create(['telegram_id' => 67890]);
        
        // Test that language change event is tracked by analytics
        $eventTracked = false;
        $this->eventBus->listen('user.language_changed', function($data) use (&$eventTracked) {
            $eventTracked = true;
        });
        
        // Simulate language change
        $this->eventBus->dispatch('user.language_changed', [
            'user_id' => $user->id,
            'old_language' => 'en',
            'new_language' => 'fa'
        ]);
        
        $this->assertTrue($eventTracked);
    }

    public function test_command_routing_across_modules()
    {
        $persianModule = new PersianModule($this->moduleManager, $this->eventBus, $this->configManager);
        $contentModule = new ContentModule($this->moduleManager, $this->eventBus, $this->configManager);
        
        $this->moduleManager->registerModule($persianModule);
        $this->moduleManager->registerModule($contentModule);
        
        // Test that different commands are routed to correct modules
        $languageModule = $this->moduleManager->findModuleForCommand('/language');
        $this->assertInstanceOf(PersianModule::class, $languageModule);
        
        $helpModule = $this->moduleManager->findModuleForCommand('/help');
        $this->assertInstanceOf(ContentModule::class, $helpModule);
    }

    public function test_configuration_sharing_between_modules()
    {
        $persianModule = new PersianModule($this->moduleManager, $this->eventBus, $this->configManager);
        $contentModule = new ContentModule($this->moduleManager, $this->eventBus, $this->configManager);
        
        $this->moduleManager->registerModule($persianModule);
        $this->moduleManager->registerModule($contentModule);
        
        // Set a user's language preference via Persian module
        $userId = 67890;
        $this->configManager->set("user.{$userId}.language", 'fa');
        
        // Content module should be able to access this setting
        $userLanguage = $this->configManager->get("user.{$userId}.language", 'en');
        $this->assertEquals('fa', $userLanguage);
    }

    public function test_module_dependency_resolution()
    {
        // Create a mock module with dependencies
        $dependentModule = Mockery::mock('App\Contracts\TelegramModuleInterface');
        $dependentModule->shouldReceive('getName')->andReturn('DependentModule');
        $dependentModule->shouldReceive('getVersion')->andReturn('1.0.0');
        $dependentModule->shouldReceive('getDescription')->andReturn('A module with dependencies');
        $dependentModule->shouldReceive('getHandledCommands')->andReturn(['/dependent']);
        $dependentModule->shouldReceive('getDependencies')->andReturn(['PersianModule']);
        
        $persianModule = new PersianModule($this->moduleManager, $this->eventBus, $this->configManager);
        
        // Register the dependency first
        $this->assertTrue($this->moduleManager->registerModule($persianModule));
        
        // Should be able to register dependent module
        $this->assertTrue($this->moduleManager->registerModule($dependentModule));
    }

    public function test_event_chain_processing()
    {
        $analyticsModule = new AnalyticsModule($this->moduleManager, $this->eventBus, $this->configManager);
        
        $this->moduleManager->registerModule($analyticsModule);
        $analyticsModule->initialize();
        
        $user = User::factory()->create(['telegram_id' => 67890]);
        
        $eventsProcessed = [];
        
        // Set up event chain
        $this->eventBus->listen('user.action', function($data) use (&$eventsProcessed) {
            $eventsProcessed[] = 'user.action';
            $this->eventBus->dispatch('analytics.track', $data);
        });
        
        $this->eventBus->listen('analytics.track', function($data) use (&$eventsProcessed) {
            $eventsProcessed[] = 'analytics.track';
        });
        
        // Trigger the chain
        $this->eventBus->dispatch('user.action', [
            'user_id' => $user->id,
            'action' => 'command_executed',
            'command' => '/help'
        ]);
        
        $this->assertEquals(['user.action', 'analytics.track'], $eventsProcessed);
    }

    public function test_module_error_isolation()
    {
        $workingModule = new PersianModule($this->moduleManager, $this->eventBus, $this->configManager);
        
        // Create a mock module that throws exceptions
        $faultyModule = Mockery::mock('App\Contracts\TelegramModuleInterface');
        $faultyModule->shouldReceive('getName')->andReturn('FaultyModule');
        $faultyModule->shouldReceive('getVersion')->andReturn('1.0.0');
        $faultyModule->shouldReceive('getDescription')->andReturn('A faulty module');
        $faultyModule->shouldReceive('getHandledCommands')->andReturn(['/faulty']);
        $faultyModule->shouldReceive('getDependencies')->andReturn([]);
        $faultyModule->shouldReceive('handleUpdate')->andThrow(new \Exception('Module error'));
        
        $this->moduleManager->registerModule($workingModule);
        $this->moduleManager->registerModule($faultyModule);
        
        $updateData = [
            'message' => [
                'text' => '/language',
                'chat' => ['id' => 12345],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ];
        
        $update = TelegramUpdateDTO::fromArray($updateData);
        
        // Working module should still function despite faulty module
        $workingModuleResult = $workingModule->handleUpdate($update);
        $this->assertTrue($workingModuleResult);
    }

    public function test_configuration_scoping_between_modules()
    {
        $this->configManager->setScope('PersianModule');
        $this->configManager->set('default_language', 'fa');
        
        $this->configManager->setScope('ContentModule');
        $this->configManager->set('default_language', 'en');
        
        // Check that scoped configurations don't interfere
        $this->configManager->setScope('PersianModule');
        $persianDefault = $this->configManager->get('default_language');
        
        $this->configManager->setScope('ContentModule');
        $contentDefault = $this->configManager->get('default_language');
        
        $this->assertEquals('fa', $persianDefault);
        $this->assertEquals('en', $contentDefault);
        
        // Reset scope
        $this->configManager->setScope(null);
    }

    public function test_real_world_user_interaction_flow()
    {
        // Set up all modules
        $persianModule = new PersianModule($this->moduleManager, $this->eventBus, $this->configManager);
        $contentModule = new ContentModule($this->moduleManager, $this->eventBus, $this->configManager);
        $analyticsModule = new AnalyticsModule($this->moduleManager, $this->eventBus, $this->configManager);
        
        $this->moduleManager->registerModule($persianModule);
        $this->moduleManager->registerModule($contentModule);
        $this->moduleManager->registerModule($analyticsModule);
        
        // Initialize all modules
        $persianModule->initialize();
        $contentModule->initialize();
        $analyticsModule->initialize();
        
        $user = User::factory()->create(['telegram_id' => 67890]);
        
        // Simulate user journey
        $eventsTriggered = [];
        
        $this->eventBus->listen('telegram.send_message', function($data) use (&$eventsTriggered) {
            $eventsTriggered[] = 'message_sent';
        });
        
        $this->eventBus->listen('user.language_changed', function($data) use (&$eventsTriggered) {
            $eventsTriggered[] = 'language_changed';
        });
        
        // 1. User starts with /language command
        $languageUpdate = TelegramUpdateDTO::fromArray([
            'message' => [
                'text' => '/language',
                'chat' => ['id' => 12345],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ]);
        
        $persianModule->handleUpdate($languageUpdate);
        
        // 2. User selects Persian language
        $callbackUpdate = TelegramUpdateDTO::fromArray([
            'callback_query' => [
                'data' => 'lang_fa',
                'message' => [
                    'chat' => ['id' => 12345],
                    'message_id' => 123
                ],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ]);
        
        $persianModule->handleCallback($callbackUpdate);
        
        // 3. User requests help
        $helpUpdate = TelegramUpdateDTO::fromArray([
            'message' => [
                'text' => '/help',
                'chat' => ['id' => 12345],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ]);
        
        $contentModule->handleUpdate($helpUpdate);
        
        // Verify that the flow worked correctly
        $this->assertContains('message_sent', $eventsTriggered);
        $this->assertContains('language_changed', $eventsTriggered);
        
        // Verify user's language was set
        $userLanguage = $this->configManager->get('user.67890.language');
        $this->assertEquals('fa', $userLanguage);
    }

    public function test_module_performance_isolation()
    {
        $fastModule = new PersianModule($this->moduleManager, $this->eventBus, $this->configManager);
        
        // Create a mock slow module
        $slowModule = Mockery::mock('App\Contracts\TelegramModuleInterface');
        $slowModule->shouldReceive('getName')->andReturn('SlowModule');
        $slowModule->shouldReceive('getVersion')->andReturn('1.0.0');
        $slowModule->shouldReceive('getDescription')->andReturn('A slow module');
        $slowModule->shouldReceive('getHandledCommands')->andReturn(['/slow']);
        $slowModule->shouldReceive('getDependencies')->andReturn([]);
        $slowModule->shouldReceive('handleUpdate')->andReturnUsing(function() {
            // Simulate slow processing
            usleep(100000); // 100ms delay
            return true;
        });
        
        $this->moduleManager->registerModule($fastModule);
        $this->moduleManager->registerModule($slowModule);
        
        $updateData = [
            'message' => [
                'text' => '/language',
                'chat' => ['id' => 12345],
                'from' => ['id' => 67890, 'first_name' => 'Test']
            ]
        ];
        
        $update = TelegramUpdateDTO::fromArray($updateData);
        
        // Fast module should execute quickly regardless of slow module
        $startTime = microtime(true);
        $fastModule->handleUpdate($update);
        $fastModuleTime = microtime(true) - $startTime;
        
        $this->assertLessThan(0.05, $fastModuleTime); // Should be under 50ms
    }
}