<?php

namespace Tests\Unit\Services;

use Tests\TestCase;
use App\Services\Core\ModuleManager;
use App\Contracts\TelegramModuleInterface;
use App\Services\Core\EventBus;
use App\Services\Core\ConfigurationManager;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;

class ModuleManagerTest extends TestCase
{
    use RefreshDatabase;

    private ModuleManager $moduleManager;
    private $mockEventBus;
    private $mockConfigManager;

    protected function setUp(): void
    {
        parent::setUp();
        
        $this->mockEventBus = Mockery::mock(EventBus::class);
        $this->mockConfigManager = Mockery::mock(ConfigurationManager::class);
        
        $this->moduleManager = new ModuleManager($this->mockEventBus, $this->mockConfigManager);
    }

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

    public function test_can_register_module()
    {
        $mockModule = Mockery::mock(TelegramModuleInterface::class);
        $mockModule->shouldReceive('getName')->andReturn('test_module');
        $mockModule->shouldReceive('getVersion')->andReturn('1.0.0');
        $mockModule->shouldReceive('getDescription')->andReturn('Test module');
        $mockModule->shouldReceive('getHandledCommands')->andReturn(['/test']);
        $mockModule->shouldReceive('getDependencies')->andReturn([]);
        
        $this->mockEventBus->shouldReceive('dispatch')
            ->with('module.registered', Mockery::any())
            ->once();

        $result = $this->moduleManager->registerModule($mockModule);

        $this->assertTrue($result);
        $this->assertTrue($this->moduleManager->isModuleRegistered('test_module'));
    }

    public function test_can_get_registered_modules()
    {
        $mockModule1 = Mockery::mock(TelegramModuleInterface::class);
        $mockModule1->shouldReceive('getName')->andReturn('module1');
        $mockModule1->shouldReceive('getVersion')->andReturn('1.0.0');
        $mockModule1->shouldReceive('getDescription')->andReturn('Module 1');
        $mockModule1->shouldReceive('getHandledCommands')->andReturn(['/cmd1']);
        $mockModule1->shouldReceive('getDependencies')->andReturn([]);

        $mockModule2 = Mockery::mock(TelegramModuleInterface::class);
        $mockModule2->shouldReceive('getName')->andReturn('module2');
        $mockModule2->shouldReceive('getVersion')->andReturn('1.0.0');
        $mockModule2->shouldReceive('getDescription')->andReturn('Module 2');
        $mockModule2->shouldReceive('getHandledCommands')->andReturn(['/cmd2']);
        $mockModule2->shouldReceive('getDependencies')->andReturn([]);

        $this->mockEventBus->shouldReceive('dispatch')->twice();

        $this->moduleManager->registerModule($mockModule1);
        $this->moduleManager->registerModule($mockModule2);

        $modules = $this->moduleManager->getRegisteredModules();

        $this->assertCount(2, $modules);
        $this->assertArrayHasKey('module1', $modules);
        $this->assertArrayHasKey('module2', $modules);
    }

    public function test_can_find_module_for_command()
    {
        $mockModule = Mockery::mock(TelegramModuleInterface::class);
        $mockModule->shouldReceive('getName')->andReturn('test_module');
        $mockModule->shouldReceive('getVersion')->andReturn('1.0.0');
        $mockModule->shouldReceive('getDescription')->andReturn('Test module');
        $mockModule->shouldReceive('getHandledCommands')->andReturn(['/test', '/example']);
        $mockModule->shouldReceive('getDependencies')->andReturn([]);

        $this->mockEventBus->shouldReceive('dispatch')->once();

        $this->moduleManager->registerModule($mockModule);

        $foundModule = $this->moduleManager->findModuleForCommand('/test');
        $this->assertSame($mockModule, $foundModule);

        $foundModule = $this->moduleManager->findModuleForCommand('/example');
        $this->assertSame($mockModule, $foundModule);

        $foundModule = $this->moduleManager->findModuleForCommand('/unknown');
        $this->assertNull($foundModule);
    }

    public function test_can_get_module_info()
    {
        $mockModule = Mockery::mock(TelegramModuleInterface::class);
        $mockModule->shouldReceive('getName')->andReturn('test_module');
        $mockModule->shouldReceive('getVersion')->andReturn('1.0.0');
        $mockModule->shouldReceive('getDescription')->andReturn('Test module description');
        $mockModule->shouldReceive('getHandledCommands')->andReturn(['/test']);
        $mockModule->shouldReceive('getDependencies')->andReturn(['dependency1']);

        $this->mockEventBus->shouldReceive('dispatch')->once();

        $this->moduleManager->registerModule($mockModule);

        $info = $this->moduleManager->getModuleInfo('test_module');

        $this->assertEquals('test_module', $info['name']);
        $this->assertEquals('1.0.0', $info['version']);
        $this->assertEquals('Test module description', $info['description']);
        $this->assertEquals(['/test'], $info['commands']);
        $this->assertEquals(['dependency1'], $info['dependencies']);
        $this->assertTrue($info['enabled']);
    }

    public function test_can_enable_and_disable_modules()
    {
        $mockModule = Mockery::mock(TelegramModuleInterface::class);
        $mockModule->shouldReceive('getName')->andReturn('test_module');
        $mockModule->shouldReceive('getVersion')->andReturn('1.0.0');
        $mockModule->shouldReceive('getDescription')->andReturn('Test module');
        $mockModule->shouldReceive('getHandledCommands')->andReturn(['/test']);
        $mockModule->shouldReceive('getDependencies')->andReturn([]);

        $this->mockEventBus->shouldReceive('dispatch')->times(3); // register, disable, enable

        $this->moduleManager->registerModule($mockModule);

        $this->assertTrue($this->moduleManager->isModuleEnabled('test_module'));

        $this->moduleManager->disableModule('test_module');
        $this->assertFalse($this->moduleManager->isModuleEnabled('test_module'));

        $this->moduleManager->enableModule('test_module');
        $this->assertTrue($this->moduleManager->isModuleEnabled('test_module'));
    }

    public function test_cannot_register_duplicate_module()
    {
        $mockModule1 = Mockery::mock(TelegramModuleInterface::class);
        $mockModule1->shouldReceive('getName')->andReturn('test_module');
        $mockModule1->shouldReceive('getVersion')->andReturn('1.0.0');
        $mockModule1->shouldReceive('getDescription')->andReturn('Test module 1');
        $mockModule1->shouldReceive('getHandledCommands')->andReturn(['/test']);
        $mockModule1->shouldReceive('getDependencies')->andReturn([]);

        $mockModule2 = Mockery::mock(TelegramModuleInterface::class);
        $mockModule2->shouldReceive('getName')->andReturn('test_module');

        $this->mockEventBus->shouldReceive('dispatch')->once();

        $this->assertTrue($this->moduleManager->registerModule($mockModule1));
        $this->assertFalse($this->moduleManager->registerModule($mockModule2));
    }

    public function test_modules_initialization()
    {
        $this->mockConfigManager->shouldReceive('get')
            ->with('modules.auto_load', [])
            ->andReturn(['TestModule']);

        $result = $this->moduleManager->initializeModules();
        
        $this->assertIsArray($result);
    }
}