/**
 * EventBus Tests
 *
 * Tests for the EventEmitterBus adapter implementation.
 * Verifies the event bus pattern works correctly with typed events.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { EventEmitterBus, createEventBus } from './index.js';
import type {
  DomainEvent,
  ProcessSpawnedEvent,
  ServerStartedEvent,
} from './types.js';

describe('EventEmitterBus', () => {
  let bus: EventEmitterBus;

  beforeEach(() => {
    bus = new EventEmitterBus();
  });

  describe('emit/on pattern', () => {
    it('should deliver events to subscribed handlers', () => {
      const handler = vi.fn();
      const event: DomainEvent = {
        type: 'test:event',
        timestamp: new Date(),
        payload: { data: 'test' },
      };

      bus.on('test:event', handler);
      bus.emit(event);

      expect(handler).toHaveBeenCalledOnce();
      expect(handler).toHaveBeenCalledWith(event);
    });

    it('should not deliver events to handlers of different types', () => {
      const handler = vi.fn();
      const event: DomainEvent = {
        type: 'test:event',
        timestamp: new Date(),
        payload: { data: 'test' },
      };

      bus.on('other:event', handler);
      bus.emit(event);

      expect(handler).not.toHaveBeenCalled();
    });
  });

  describe('once', () => {
    it('should fire handler only once', () => {
      const handler = vi.fn();
      const event: DomainEvent = {
        type: 'test:event',
        timestamp: new Date(),
        payload: { data: 'test' },
      };

      bus.once('test:event', handler);
      bus.emit(event);
      bus.emit(event);
      bus.emit(event);

      expect(handler).toHaveBeenCalledOnce();
    });
  });

  describe('off', () => {
    it('should remove handler from event subscription', () => {
      const handler = vi.fn();
      const event: DomainEvent = {
        type: 'test:event',
        timestamp: new Date(),
        payload: { data: 'test' },
      };

      bus.on('test:event', handler);
      bus.emit(event);
      expect(handler).toHaveBeenCalledOnce();

      bus.off('test:event', handler);
      bus.emit(event);
      expect(handler).toHaveBeenCalledOnce(); // Still only once
    });
  });

  describe('multiple handlers', () => {
    it('should deliver events to all handlers for the same event type', () => {
      const handler1 = vi.fn();
      const handler2 = vi.fn();
      const handler3 = vi.fn();
      const event: DomainEvent = {
        type: 'test:event',
        timestamp: new Date(),
        payload: { data: 'test' },
      };

      bus.on('test:event', handler1);
      bus.on('test:event', handler2);
      bus.on('test:event', handler3);
      bus.emit(event);

      expect(handler1).toHaveBeenCalledOnce();
      expect(handler2).toHaveBeenCalledOnce();
      expect(handler3).toHaveBeenCalledOnce();
    });
  });

  describe('typed events', () => {
    it('should work with ProcessSpawnedEvent', () => {
      const handler = vi.fn();
      const event: ProcessSpawnedEvent = {
        type: 'process:spawned',
        timestamp: new Date(),
        payload: {
          processId: 'proc-1',
          pid: 12345,
          command: 'node server.js',
        },
      };

      bus.on<ProcessSpawnedEvent>('process:spawned', handler);
      bus.emit(event);

      expect(handler).toHaveBeenCalledWith(event);
      const receivedEvent = handler.mock.calls[0][0] as ProcessSpawnedEvent;
      expect(receivedEvent.payload.processId).toBe('proc-1');
      expect(receivedEvent.payload.pid).toBe(12345);
      expect(receivedEvent.payload.command).toBe('node server.js');
    });

    it('should work with ServerStartedEvent', () => {
      const handler = vi.fn();
      const event: ServerStartedEvent = {
        type: 'server:started',
        timestamp: new Date(),
        payload: {
          port: 3847,
          host: '127.0.0.1',
          pid: 54321,
        },
      };

      bus.on<ServerStartedEvent>('server:started', handler);
      bus.emit(event);

      expect(handler).toHaveBeenCalledWith(event);
      const receivedEvent = handler.mock.calls[0][0] as ServerStartedEvent;
      expect(receivedEvent.payload.port).toBe(3847);
      expect(receivedEvent.payload.host).toBe('127.0.0.1');
      expect(receivedEvent.payload.pid).toBe(54321);
    });
  });

  describe('timestamp', () => {
    it('should preserve event timestamp through emit/on cycle', () => {
      const handler = vi.fn();
      const timestamp = new Date('2026-01-30T12:00:00Z');
      const event: DomainEvent = {
        type: 'test:event',
        timestamp,
        payload: { data: 'test' },
      };

      bus.on('test:event', handler);
      bus.emit(event);

      const receivedEvent = handler.mock.calls[0][0] as DomainEvent;
      expect(receivedEvent.timestamp).toBe(timestamp);
      expect(receivedEvent.timestamp.toISOString()).toBe(
        '2026-01-30T12:00:00.000Z'
      );
    });
  });
});

describe('createEventBus', () => {
  it('should create an EventEmitterBus instance', () => {
    const bus = createEventBus();
    expect(bus).toBeInstanceOf(EventEmitterBus);
  });

  it('should return a working EventBus', () => {
    const bus = createEventBus();
    const handler = vi.fn();
    const event: DomainEvent = {
      type: 'test:event',
      timestamp: new Date(),
      payload: { data: 'test' },
    };

    bus.on('test:event', handler);
    bus.emit(event);

    expect(handler).toHaveBeenCalledWith(event);
  });
});
