import { describe, it, expect, beforeEach, vi } from 'vitest';
import { EventBus } from './memory.js';
import type { PlatformEvent } from './types.js';

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

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

  describe('on / emit', () => {
    it('should deliver events to registered handlers', async () => {
      const handler = vi.fn();
      bus.on('user.created', handler);

      await bus.emit('user.created', {
        userId: 'u1',
        email: 'test@example.com',
        plan: 'free',
        productId: 'lysnrai',
      });

      expect(handler).toHaveBeenCalledOnce();
      expect(handler.mock.calls[0][0]).toMatchObject({
        type: 'user.created',
        payload: { userId: 'u1', email: 'test@example.com' },
      });
    });

    it('should include event id and timestamp', async () => {
      let received: PlatformEvent<'user.created'> | undefined;
      bus.on('user.created', e => {
        received = e;
      });

      await bus.emit('user.created', {
        userId: 'u1',
        email: 'a@b.com',
        plan: 'free',
        productId: 'test',
      });

      expect(received).toBeDefined();
      expect(received!.id).toBeTruthy();
      expect(received!.timestamp).toBeTruthy();
    });

    it('should deliver to multiple handlers', async () => {
      const h1 = vi.fn();
      const h2 = vi.fn();
      const h3 = vi.fn();
      bus.on('user.created', h1);
      bus.on('user.created', h2);
      bus.on('user.created', h3);

      await bus.emit('user.created', {
        userId: 'u1',
        email: 'a@b.com',
        plan: 'free',
        productId: 'test',
      });

      expect(h1).toHaveBeenCalledOnce();
      expect(h2).toHaveBeenCalledOnce();
      expect(h3).toHaveBeenCalledOnce();
    });

    it('should not deliver to handlers of different event types', async () => {
      const userHandler = vi.fn();
      const paymentHandler = vi.fn();
      bus.on('user.created', userHandler);
      bus.on('payment.succeeded', paymentHandler);

      await bus.emit('user.created', {
        userId: 'u1',
        email: 'a@b.com',
        plan: 'free',
        productId: 'test',
      });

      expect(userHandler).toHaveBeenCalledOnce();
      expect(paymentHandler).not.toHaveBeenCalled();
    });

    it('should return result with zero handlers when no subscribers', async () => {
      const result = await bus.emit('user.deleted', {
        userId: 'u1',
        productId: 'test',
      });

      expect(result.handlerCount).toBe(0);
      expect(result.errors).toHaveLength(0);
      expect(result.eventId).toBeTruthy();
    });
  });

  describe('error isolation', () => {
    it('should not block other handlers when one throws', async () => {
      const h1 = vi.fn();
      const h2 = vi.fn(() => {
        throw new Error('handler crash');
      });
      const h3 = vi.fn();

      bus.on('user.created', h1);
      bus.on('user.created', h2);
      bus.on('user.created', h3);

      const result = await bus.emit('user.created', {
        userId: 'u1',
        email: 'a@b.com',
        plan: 'free',
        productId: 'test',
      });

      expect(h1).toHaveBeenCalledOnce();
      expect(h2).toHaveBeenCalledOnce();
      expect(h3).toHaveBeenCalledOnce();
      expect(result.errors).toHaveLength(1);
      expect(result.errors[0].error).toBe('handler crash');
    });

    it('should handle async handler rejection', async () => {
      bus.on('payment.failed', async () => {
        throw new Error('async fail');
      });

      const result = await bus.emit('payment.failed', {
        invoiceId: 'inv_1',
        userId: 'u1',
        amount: 999,
        retryCount: 1,
        productId: 'test',
      });

      expect(result.errors).toHaveLength(1);
      expect(result.errors[0].error).toBe('async fail');
    });
  });

  describe('unsubscribe', () => {
    it('should stop delivering events after unsubscribe', async () => {
      const handler = vi.fn();
      const sub = bus.on('user.created', handler);

      await bus.emit('user.created', {
        userId: 'u1',
        email: 'a@b.com',
        plan: 'free',
        productId: 'test',
      });
      expect(handler).toHaveBeenCalledOnce();

      sub.unsubscribe();

      await bus.emit('user.created', {
        userId: 'u2',
        email: 'b@c.com',
        plan: 'pro',
        productId: 'test',
      });
      expect(handler).toHaveBeenCalledOnce(); // still 1, not 2
    });

    it('should return subscription metadata', () => {
      const sub = bus.on('flag.toggled', () => {});
      expect(sub.id).toBeTruthy();
      expect(sub.eventType).toBe('flag.toggled');
      expect(typeof sub.unsubscribe).toBe('function');
    });
  });

  describe('clear', () => {
    it('should remove all handlers for a specific event type', async () => {
      const h1 = vi.fn();
      const h2 = vi.fn();
      bus.on('user.created', h1);
      bus.on('payment.succeeded', h2);

      bus.clear('user.created');

      await bus.emit('user.created', {
        userId: 'u1',
        email: 'a@b.com',
        plan: 'free',
        productId: 'test',
      });
      await bus.emit('payment.succeeded', {
        invoiceId: 'inv_1',
        userId: 'u1',
        amount: 100,
        currency: 'usd',
        productId: 'test',
      });

      expect(h1).not.toHaveBeenCalled();
      expect(h2).toHaveBeenCalledOnce();
    });

    it('should remove all handlers when called without args', () => {
      bus.on('user.created', () => {});
      bus.on('payment.failed', () => {});
      bus.on('flag.toggled', () => {});

      expect(bus.eventTypes().length).toBe(3);
      bus.clear();
      expect(bus.eventTypes().length).toBe(0);
    });
  });

  describe('listenerCount / eventTypes', () => {
    it('should count handlers per event type', () => {
      bus.on('user.created', () => {});
      bus.on('user.created', () => {});
      bus.on('payment.failed', () => {});

      expect(bus.listenerCount('user.created')).toBe(2);
      expect(bus.listenerCount('payment.failed')).toBe(1);
      expect(bus.listenerCount('user.deleted')).toBe(0);
    });

    it('should list event types with handlers', () => {
      bus.on('user.created', () => {});
      bus.on('payment.failed', () => {});

      const types = bus.eventTypes();
      expect(types).toContain('user.created');
      expect(types).toContain('payment.failed');
      expect(types).not.toContain('user.deleted');
    });
  });

  describe('source option', () => {
    it('should pass source through to handlers', async () => {
      let received: PlatformEvent<'user.created'> | undefined;
      bus.on('user.created', e => {
        received = e;
      });

      await bus.emit(
        'user.created',
        { userId: 'u1', email: 'a@b.com', plan: 'free', productId: 'test' },
        { source: 'auth-module' }
      );

      expect(received?.source).toBe('auth-module');
    });
  });
});
