import {
  pushNotification,
  updateNotification,
  shiftNotification,
  clearNotifications,
  getVisibleNotification,
} from '../store';
import {InAppNotificationBody} from '../interfaces';

const createNotification = (
  id: string,
  overrides?: Partial<InAppNotificationBody>,
): InAppNotificationBody => ({
  id,
  title: `Title ${id}`,
  body: `Body ${id}`,
  ...overrides,
});

describe('InAppNotificationStore', () => {
  beforeEach(() => {
    clearNotifications();
  });

  describe('pushNotification', () => {
    it('should add a notification to empty queue', () => {
      const notification = createNotification('1');

      pushNotification(notification);

      expect(getVisibleNotification()).toEqual(notification);
    });

    it('should add notifications in FIFO order', () => {
      const first = createNotification('1');
      const second = createNotification('2');

      pushNotification(first);
      pushNotification(second);

      expect(getVisibleNotification()).toEqual(first);
    });

    it('should maintain queue order after multiple pushes', () => {
      const notifications = Array.from({length: 5}, (_, i) =>
        createNotification(`${i + 1}`),
      );

      notifications.forEach(pushNotification);

      expect(getVisibleNotification()).toEqual(notifications[0]);

      shiftNotification();
      expect(getVisibleNotification()).toEqual(notifications[1]);

      shiftNotification();
      expect(getVisibleNotification()).toEqual(notifications[2]);
    });
  });

  describe('queue max size handling', () => {
    const IN_APP_NOTIFICATION_MAX_QUEUE_SIZE = 10;

    it('should allow up to MAX_QUEUE_SIZE notifications', () => {
      const notifications = Array.from(
        {length: IN_APP_NOTIFICATION_MAX_QUEUE_SIZE},
        (_, i) => createNotification(`${i + 1}`),
      );

      notifications.forEach(pushNotification);

      expect(getVisibleNotification()).toEqual(notifications[0]);
    });

    it('should keep first notification when exceeding max size', () => {
      const notifications = Array.from(
        {length: IN_APP_NOTIFICATION_MAX_QUEUE_SIZE + 1},
        (_, i) => createNotification(`${i + 1}`),
      );

      notifications.forEach(pushNotification);

      expect(getVisibleNotification()).toEqual(notifications[0]);
    });

    it('should keep most recent notifications when exceeding max size', () => {
      const length = IN_APP_NOTIFICATION_MAX_QUEUE_SIZE + 5;
      const notifications = Array.from({length}, (_, i) =>
        createNotification(`${i + 1}`),
      );

      notifications.forEach(pushNotification);

      const firstNotification = getVisibleNotification();
      expect(firstNotification).toEqual(notifications[0]);

      shiftNotification();

      const secondVisible = getVisibleNotification();
      expect(secondVisible?.id).toBe(
        `${length - (IN_APP_NOTIFICATION_MAX_QUEUE_SIZE - 2)}`,
      );
    });

    it('should drop middle notifications when exceeding max size', () => {
      const notifications = Array.from({length: 15}, (_, i) =>
        createNotification(`${i + 1}`),
      );

      notifications.forEach(pushNotification);

      expect(getVisibleNotification()?.id).toBe('1');

      shiftNotification();
      expect(getVisibleNotification()?.id).toBe('7');

      shiftNotification();
      expect(getVisibleNotification()?.id).toBe('8');
    });

    it('should maintain max size after multiple overflow pushes', () => {
      const notifications = Array.from({length: 20}, (_, i) =>
        createNotification(`${i + 1}`),
      );

      notifications.forEach(pushNotification);

      let count = 0;
      while (getVisibleNotification()) {
        count++;
        shiftNotification();
      }

      expect(count).toBe(IN_APP_NOTIFICATION_MAX_QUEUE_SIZE);
    });
  });

  describe('shiftNotification', () => {
    it('should remove the first notification from queue', () => {
      const first = createNotification('1');
      const second = createNotification('2');

      pushNotification(first);
      pushNotification(second);
      shiftNotification();

      expect(getVisibleNotification()).toEqual(second);
    });
  });

  describe('updateNotification', () => {
    it('should update notification by collapsedId', () => {
      const notification = createNotification('1', {
        collapsedId: 'collapsed-1',
      });

      pushNotification(notification);

      const updatedNotification = createNotification('new-id', {
        collapsedId: 'collapsed-1',
        title: 'Updated Title',
        body: 'Updated Body',
      });

      updateNotification(updatedNotification);

      const visible = getVisibleNotification();
      expect(visible?.title).toBe('Updated Title');
      expect(visible?.body).toBe('Updated Body');
      expect(visible?.id).toBe('1');
    });

    it('should preserve original id when updating', () => {
      const originalId = 'original-id';
      const notification = createNotification(originalId, {
        collapsedId: 'collapsed-1',
      });

      pushNotification(notification);

      updateNotification(
        createNotification('different-id', {
          collapsedId: 'collapsed-1',
          title: 'New Title',
        }),
      );

      expect(getVisibleNotification()?.id).toBe(originalId);
    });

    it('should not affect notifications without collapsedId', () => {
      const notification = createNotification('1');

      pushNotification(notification);

      updateNotification(
        createNotification('2', {
          collapsedId: 'some-collapsed-id',
          title: 'Should Not Update',
        }),
      );

      expect(getVisibleNotification()?.title).toBe('Title 1');
    });

    it('should update correct notification in queue with multiple items', () => {
      const first = createNotification('1', {collapsedId: 'collapsed-1'});
      const second = createNotification('2', {collapsedId: 'collapsed-2'});
      const third = createNotification('3', {collapsedId: 'collapsed-3'});

      pushNotification(first);
      pushNotification(second);
      pushNotification(third);

      updateNotification(
        createNotification('new', {
          collapsedId: 'collapsed-2',
          title: 'Updated Second',
        }),
      );

      shiftNotification();
      expect(getVisibleNotification()?.title).toBe('Updated Second');
      expect(getVisibleNotification()?.id).toBe('2');
    });

    it('should handle update on empty queue gracefully', () => {
      updateNotification(
        createNotification('1', {
          collapsedId: 'collapsed-1',
        }),
      );

      expect(getVisibleNotification()).toBeUndefined();
    });
  });
});
