<?php

namespace Tests\Unit\Services;

use Tests\TestCase;
use App\Services\Core\EventBus;
use Illuminate\Foundation\Testing\RefreshDatabase;

class EventBusTest extends TestCase
{
    use RefreshDatabase;

    private EventBus $eventBus;

    protected function setUp(): void
    {
        parent::setUp();
        $this->eventBus = new EventBus();
    }

    public function test_can_register_listener()
    {
        $listenerCalled = false;
        $listener = function($data) use (&$listenerCalled) {
            $listenerCalled = true;
        };

        $this->eventBus->listen('test.event', $listener);
        $this->eventBus->dispatch('test.event', ['test' => 'data']);

        $this->assertTrue($listenerCalled);
    }

    public function test_can_register_multiple_listeners()
    {
        $listener1Called = false;
        $listener2Called = false;

        $listener1 = function($data) use (&$listener1Called) {
            $listener1Called = true;
        };

        $listener2 = function($data) use (&$listener2Called) {
            $listener2Called = true;
        };

        $this->eventBus->listen('test.event', $listener1);
        $this->eventBus->listen('test.event', $listener2);
        $this->eventBus->dispatch('test.event', ['test' => 'data']);

        $this->assertTrue($listener1Called);
        $this->assertTrue($listener2Called);
    }

    public function test_listeners_receive_correct_data()
    {
        $receivedData = null;
        $listener = function($data) use (&$receivedData) {
            $receivedData = $data;
        };

        $testData = ['message' => 'Hello World', 'user_id' => 123];

        $this->eventBus->listen('test.event', $listener);
        $this->eventBus->dispatch('test.event', $testData);

        $this->assertEquals($testData, $receivedData);
    }

    public function test_can_dispatch_without_listeners()
    {
        // Should not throw any exception
        $this->eventBus->dispatch('non.existent.event', ['test' => 'data']);
        $this->assertTrue(true); // If we get here, no exception was thrown
    }

    public function test_can_get_registered_listeners()
    {
        $listener1 = function($data) {};
        $listener2 = function($data) {};

        $this->eventBus->listen('test.event', $listener1);
        $this->eventBus->listen('test.event', $listener2);
        $this->eventBus->listen('other.event', function($data) {});

        $listeners = $this->eventBus->getListeners('test.event');
        $this->assertCount(2, $listeners);

        $allListeners = $this->eventBus->getAllListeners();
        $this->assertArrayHasKey('test.event', $allListeners);
        $this->assertArrayHasKey('other.event', $allListeners);
        $this->assertCount(2, $allListeners['test.event']);
        $this->assertCount(1, $allListeners['other.event']);
    }

    public function test_can_remove_listeners()
    {
        $listener = function($data) {};

        $this->eventBus->listen('test.event', $listener);
        $this->assertCount(1, $this->eventBus->getListeners('test.event'));

        $this->eventBus->removeListener('test.event', $listener);
        $this->assertCount(0, $this->eventBus->getListeners('test.event'));
    }

    public function test_can_clear_all_listeners()
    {
        $this->eventBus->listen('event1', function($data) {});
        $this->eventBus->listen('event2', function($data) {});
        $this->eventBus->listen('event3', function($data) {});

        $this->assertNotEmpty($this->eventBus->getAllListeners());

        $this->eventBus->clearListeners();
        $this->assertEmpty($this->eventBus->getAllListeners());
    }

    public function test_listener_exceptions_are_handled()
    {
        $goodListenerCalled = false;
        $goodListener = function($data) use (&$goodListenerCalled) {
            $goodListenerCalled = true;
        };

        $badListener = function($data) {
            throw new \Exception('Test exception');
        };

        $this->eventBus->listen('test.event', $badListener);
        $this->eventBus->listen('test.event', $goodListener);

        // Should not throw exception and good listener should still be called
        $this->eventBus->dispatch('test.event', ['test' => 'data']);
        $this->assertTrue($goodListenerCalled);
    }

    public function test_event_chaining()
    {
        $eventOrder = [];

        $this->eventBus->listen('chain.start', function($data) use (&$eventOrder) {
            $eventOrder[] = 'start';
            $this->eventBus->dispatch('chain.middle', $data);
        });

        $this->eventBus->listen('chain.middle', function($data) use (&$eventOrder) {
            $eventOrder[] = 'middle';
            $this->eventBus->dispatch('chain.end', $data);
        });

        $this->eventBus->listen('chain.end', function($data) use (&$eventOrder) {
            $eventOrder[] = 'end';
        });

        $this->eventBus->dispatch('chain.start', ['test' => 'data']);

        $this->assertEquals(['start', 'middle', 'end'], $eventOrder);
    }
}