// SPDX-License-Identifier: Apache-2.0
import sinon from 'sinon';
import { expect } from 'chai';
import { describe, it, beforeEach } from 'mocha';
import { DefaultSoloEventBus } from '../../../../src/core/events/default-solo-event-bus.js';
import { SoloEventType } from '../../../../src/core/events/event-types/event-types.js';
import { NetworkDeployedEvent } from '../../../../src/core/events/event-types/network-deployed-event.js';
import { MirrorNodeDeployedEvent } from '../../../../src/core/events/event-types/mirror-node-deployed-event.js';
import { container } from 'tsyringe-neo';
import { InjectTokens } from '../../../../src/core/dependency-injection/inject-tokens.js';
import { Duration } from '../../../../src/core/time/duration.js';
import { SoloError } from '../../../../src/core/errors/solo-error.js';
describe('SoloEventBus', () => {
    let bus;
    const networkEvent = new NetworkDeployedEvent('my-deployment');
    const mirrorEvent = new MirrorNodeDeployedEvent('my-deployment');
    beforeEach(() => {
        // resolve the test logger from the DI container
        const testLogger = container.resolve(InjectTokens.SoloLogger);
        bus = new DefaultSoloEventBus(testLogger);
    });
    it('should call a registered handler when the matching event is emitted', () => {
        const handler = sinon.spy();
        bus.on(SoloEventType.NetworkDeployed, handler);
        bus.emit(networkEvent);
        expect(handler).to.have.been.calledOnceWithExactly(networkEvent);
    });
    it('should not call a handler after it has been removed with off()', () => {
        const handler = sinon.spy();
        bus.on(SoloEventType.NetworkDeployed, handler);
        bus.off(SoloEventType.NetworkDeployed, handler);
        bus.emit(networkEvent);
        expect(handler).not.to.have.been.called;
    });
    it('should call all registered handlers for the same event type', () => {
        const handlerA = sinon.spy();
        const handlerB = sinon.spy();
        bus.on(SoloEventType.NetworkDeployed, handlerA);
        bus.on(SoloEventType.NetworkDeployed, handlerB);
        bus.emit(networkEvent);
        expect(handlerA).to.have.been.calledOnceWithExactly(networkEvent);
        expect(handlerB).to.have.been.calledOnceWithExactly(networkEvent);
    });
    it('should not call a handler registered for a different event type', () => {
        const handler = sinon.spy();
        bus.on(SoloEventType.MirrorNodeDeployed, handler);
        bus.emit(networkEvent);
        expect(handler).not.to.have.been.called;
    });
    it('should call handlers for different event types independently', () => {
        const networkHandler = sinon.spy();
        const mirrorHandler = sinon.spy();
        bus.on(SoloEventType.NetworkDeployed, networkHandler);
        bus.on(SoloEventType.MirrorNodeDeployed, mirrorHandler);
        bus.emit(networkEvent);
        bus.emit(mirrorEvent);
        expect(networkHandler).to.have.been.calledOnceWithExactly(networkEvent);
        expect(mirrorHandler).to.have.been.calledOnceWithExactly(mirrorEvent);
    });
    it('should only remove the specific handler passed to off(), leaving others intact', () => {
        const handlerA = sinon.spy();
        const handlerB = sinon.spy();
        bus.on(SoloEventType.NetworkDeployed, handlerA);
        bus.on(SoloEventType.NetworkDeployed, handlerB);
        bus.off(SoloEventType.NetworkDeployed, handlerA);
        bus.emit(networkEvent);
        expect(handlerA).not.to.have.been.called;
        expect(handlerB).to.have.been.calledOnceWithExactly(networkEvent);
    });
    it('should call a handler each time the event is emitted', () => {
        const handler = sinon.spy();
        bus.on(SoloEventType.NetworkDeployed, handler);
        bus.emit(networkEvent);
        bus.emit(networkEvent);
        expect(handler).to.have.been.calledTwice;
    });
    it('waitFor() should resolve with the event when it is emitted', async () => {
        const promise = bus.waitFor(SoloEventType.NetworkDeployed);
        bus.emit(networkEvent);
        const result = await promise;
        expect(result).to.equal(networkEvent);
    });
    it('waitFor() should resolve only once even if the event is emitted multiple times', async () => {
        const results = [];
        const promise = bus.waitFor(SoloEventType.NetworkDeployed);
        promise.then((soloEvent) => results.push(soloEvent));
        bus.emit(networkEvent);
        bus.emit(networkEvent);
        await promise;
        expect(results).to.have.lengthOf(1);
    });
    it('waitFor() should not resolve for a different event type', async () => {
        let resolved = false;
        bus.waitFor(SoloEventType.NetworkDeployed).then(() => {
            resolved = true;
        });
        bus.emit(mirrorEvent);
        await Promise.resolve(); // flush microtask queue
        expect(resolved).to.be.false;
    });
    it('waitFor() with predicate should resolve when the predicate returns true', async () => {
        const target = new NetworkDeployedEvent('target');
        const other = new NetworkDeployedEvent('other');
        const promise = bus.waitFor(SoloEventType.NetworkDeployed, (soloEvent) => soloEvent.deployment === 'target');
        bus.emit(other);
        bus.emit(target);
        expect(await promise).to.equal(target);
    });
    it('waitFor() with predicate should skip events that do not match', async () => {
        let resolved = false;
        bus
            .waitFor(SoloEventType.NetworkDeployed, (soloEvent) => soloEvent.deployment === 'target')
            .then(() => {
            resolved = true;
        });
        bus.emit(new NetworkDeployedEvent('other'));
        await Promise.resolve();
        expect(resolved).to.be.false;
    });
    it('waitFor() with predicate should resolve only once even if multiple matching events are emitted', async () => {
        const results = [];
        const promise = bus.waitFor(SoloEventType.NetworkDeployed, () => true);
        promise.then((soloEvent) => results.push(soloEvent));
        bus.emit(networkEvent);
        bus.emit(networkEvent);
        await promise;
        expect(results).to.have.lengthOf(1);
    });
    it('waitFor() for different types resolves each independently', async () => {
        const networkPromise = bus.waitFor(SoloEventType.NetworkDeployed);
        const mirrorPromise = bus.waitFor(SoloEventType.MirrorNodeDeployed);
        bus.emit(networkEvent);
        bus.emit(mirrorEvent);
        expect(await networkPromise).to.equal(networkEvent);
        expect(await mirrorPromise).to.equal(mirrorEvent);
    });
    it('NetworkDeployedEvent should have the correct type and deployment', () => {
        const event = new NetworkDeployedEvent('solo-deployment');
        expect(event.type).to.equal(SoloEventType.NetworkDeployed);
        expect(event.deployment).to.equal('solo-deployment');
    });
    it('MirrorNodeDeployedEvent should have the correct type and deployment', () => {
        const event = new MirrorNodeDeployedEvent('solo-deployment');
        expect(event.type).to.equal(SoloEventType.MirrorNodeDeployed);
        expect(event.deployment).to.equal('solo-deployment');
    });
    it('waitFor() should resolve if event was emitted before waitFor is called', async () => {
        // Emit the event first (before waitFor is called)
        bus.emit(networkEvent);
        // Now call waitFor; it should resolve immediately with the past event
        const promise = bus.waitFor(SoloEventType.NetworkDeployed);
        const result = await promise;
        expect(result).to.equal(networkEvent);
    });
    describe('waitFor() timeout', () => {
        it('should reject with a SoloError when the timeout elapses before the event is emitted', async () => {
            const promise = bus.waitFor(SoloEventType.NetworkDeployed, undefined, Duration.ofMillis(10));
            await expect(promise).to.be.rejectedWith(SoloError, /timed out/);
        });
        it('should resolve before the timeout if the event arrives in time', async () => {
            const promise = bus.waitFor(SoloEventType.NetworkDeployed, undefined, Duration.ofMillis(100));
            bus.emit(networkEvent);
            expect(await promise).to.equal(networkEvent);
        });
        it('should resolve immediately from history before the timeout fires', async () => {
            bus.emit(networkEvent);
            const result = await bus.waitFor(SoloEventType.NetworkDeployed, undefined, Duration.ofMillis(10));
            expect(result).to.equal(networkEvent);
        });
    });
    describe('clearHistory()', () => {
        it('should prevent waitFor() from resolving against a cleared event type', async () => {
            bus.emit(networkEvent);
            bus.clearHistory(SoloEventType.NetworkDeployed);
            let resolved = false;
            bus.waitFor(SoloEventType.NetworkDeployed).then(() => {
                resolved = true;
            });
            await Promise.resolve();
            expect(resolved).to.be.false;
        });
        it('should not affect history for other event types when clearing a specific type', async () => {
            bus.emit(networkEvent);
            bus.emit(mirrorEvent);
            bus.clearHistory(SoloEventType.NetworkDeployed);
            // mirror history should still resolve waitFor immediately
            const result = await bus.waitFor(SoloEventType.MirrorNodeDeployed);
            expect(result).to.equal(mirrorEvent);
        });
        it('should clear all event type histories when called with no argument', async () => {
            bus.emit(networkEvent);
            bus.emit(mirrorEvent);
            bus.clearHistory();
            let networkResolved = false;
            let mirrorResolved = false;
            bus.waitFor(SoloEventType.NetworkDeployed).then(() => {
                networkResolved = true;
            });
            bus.waitFor(SoloEventType.MirrorNodeDeployed).then(() => {
                mirrorResolved = true;
            });
            await Promise.resolve();
            expect(networkResolved).to.be.false;
            expect(mirrorResolved).to.be.false;
        });
        it('should allow new events to be recorded after history is cleared', async () => {
            bus.emit(networkEvent);
            bus.clearHistory(SoloEventType.NetworkDeployed);
            const newEvent = new NetworkDeployedEvent('new-deployment');
            bus.emit(newEvent);
            const result = await bus.waitFor(SoloEventType.NetworkDeployed);
            expect(result).to.equal(newEvent);
        });
        it('should be a no-op when called on a type with no recorded history', () => {
            expect(() => bus.clearHistory(SoloEventType.NetworkDeployed)).not.to.throw();
        });
        it('should be a no-op when called with no argument on an empty bus', () => {
            expect(() => bus.clearHistory()).not.to.throw();
        });
    });
});
//# sourceMappingURL=solo-event-bus.test.js.map