import { FOLLOW_REQUEST_APPROVED_EVENT, FollowRequestApprovedEvent } from '@app/events';
import { NotificationConsumer } from './notification.consumer';

const event: FollowRequestApprovedEvent = {
  eventId: 'event-1',
  eventType: FOLLOW_REQUEST_APPROVED_EVENT,
  occurredAt: '2026-08-03T10:00:00.000Z',
  payload: { requestId: 'request-1', actorId: 'actor-1', recipientId: 'recipient-1' },
};

function rmqContext() {
  const channel = { ack: jest.fn(), nack: jest.fn() };
  const message = {
    properties: { headers: { correlationId: 'correlation-1', requestId: 'request-1' } },
  };
  return {
    channel,
    message,
    context: {
      getChannelRef: () => channel,
      getMessage: () => message,
    } as never,
  };
}

describe('NotificationConsumer', () => {
  const notification = { toJSON: () => ({ _id: 'notification-1' }) };
  let notifications: Record<string, jest.Mock>;
  let consumer: NotificationConsumer;

  beforeEach(() => {
    notifications = {
      createFollowRequestApprovedNotification: jest.fn().mockResolvedValue(notification),
      getUnreadCounts: jest.fn().mockResolvedValue({ total: 1, follows: 0, followRequests: 1 }),
    };
    consumer = new NotificationConsumer(notifications as never);
    jest.spyOn(console, 'log').mockImplementation(() => undefined);
    jest.spyOn(console, 'error').mockImplementation(() => undefined);
  });

  afterEach(() => jest.restoreAllMocks());

  it('consumes follow.request.approved and acknowledges only after persistence', async () => {
    const rmq = rmqContext();
    const result = await consumer.followRequestApproved(event, rmq.context);
    expect(notifications.createFollowRequestApprovedNotification).toHaveBeenCalledWith({
      ...event.payload,
      eventId: event.eventId,
    });
    expect(result).toEqual(expect.objectContaining({
      result: notification,
      realtime: expect.objectContaining({ recipientId: 'recipient-1', unreadCount: 1 }),
    }));
    expect(rmq.channel.ack).toHaveBeenCalledWith(rmq.message);
    expect(rmq.channel.nack).not.toHaveBeenCalled();
  });

  it('retries transient failures with a finite limit', async () => {
    notifications.createFollowRequestApprovedNotification
      .mockRejectedValueOnce(new Error('temporary-1'))
      .mockRejectedValueOnce(new Error('temporary-2'))
      .mockResolvedValueOnce(notification);
    const rmq = rmqContext();
    await consumer.followRequestApproved(event, rmq.context);
    expect(notifications.createFollowRequestApprovedNotification).toHaveBeenCalledTimes(3);
    expect(rmq.channel.ack).toHaveBeenCalledTimes(1);
  });

  it('rejects to the DLQ after retry exhaustion', async () => {
    notifications.createFollowRequestApprovedNotification.mockRejectedValue(new Error('mongo down'));
    const rmq = rmqContext();
    await expect(consumer.followRequestApproved(event, rmq.context)).rejects.toThrow('mongo down');
    expect(notifications.createFollowRequestApprovedNotification).toHaveBeenCalledTimes(3);
    expect(rmq.channel.nack).toHaveBeenCalledWith(rmq.message, false, false);
    expect(rmq.channel.ack).not.toHaveBeenCalled();
  });

  it('rejects an invalid contract without persistence', async () => {
    const rmq = rmqContext();
    await expect(consumer.followRequestApproved({ ...event, eventId: '' }, rmq.context)).rejects.toThrow(
      'Invalid follow.request.approved contract',
    );
    expect(notifications.createFollowRequestApprovedNotification).not.toHaveBeenCalled();
    expect(rmq.channel.nack).toHaveBeenCalledWith(rmq.message, false, false);
  });

  it('requires non-empty string IDs and a canonical ISO occurredAt value', async () => {
    for (const invalid of [
      { ...event, occurredAt: 'August 3, 2026' },
      { ...event, payload: { ...event.payload, actorId: '' } },
    ]) {
      const rmq = rmqContext();
      await expect(consumer.followRequestApproved(invalid, rmq.context)).rejects.toThrow(
        'Invalid follow.request.approved contract',
      );
      expect(rmq.channel.nack).toHaveBeenCalledWith(rmq.message, false, false);
    }
    expect(notifications.createFollowRequestApprovedNotification).not.toHaveBeenCalled();
  });
});
