import { DateTime } from 'luxon';
import { ApiCache } from './api-cache';

describe('modules/platform/github/api-cache', () => {
  const now = DateTime.fromISO('2000-01-01T00:00:00.000+00:00');
  const t1 = now.plus({ years: 1 }).toISO()!;
  const t2 = now.plus({ years: 2 }).toISO()!;
  const t3 = now.plus({ years: 3 }).toISO()!;
  const t4 = now.plus({ years: 4 }).toISO()!;
  const t5 = now.plus({ years: 5 }).toISO()!;

  it('stores and retrieves items', () => {
    const item1 = { number: 1, updated_at: t1 };
    const item2 = { number: 2, updated_at: t2 };
    const apiCache = new ApiCache({
      items: { 1: item1 },
      lastModified: t1,
    });

    expect(apiCache.getItem(1)).toBe(item1);
    expect(apiCache.getItem(2)).toBeNull();

    apiCache.updateItem(item2);
    expect(apiCache.getItem(2)).toBe(item2);
    expect(apiCache.getItems()).toEqual([item1, item2]);
  });

  describe('getItems', () => {
    it('maps items', () => {
      const item1 = { number: 1, updated_at: t1 };
      const item2 = { number: 2, updated_at: t2 };
      const item3 = { number: 3, updated_at: t3 };
      const apiCache = new ApiCache({
        items: {
          1: item1,
          2: item2,
          3: item3,
        },
      });

      const res = apiCache.getItems();

      expect(res).toMatchObject([{ number: 1 }, { number: 2 }, { number: 3 }]);
    });

    it('resets cache on item update', () => {
      const item1 = { number: 1, updated_at: t1 };
      const item2 = { number: 2, updated_at: t2 };
      const item3 = { number: 3, updated_at: t3 };
      const apiCache = new ApiCache({
        items: {
          1: item1,
          2: item2,
        },
      });

      const numbers1 = apiCache.getItems();
      apiCache.updateItem(item3);
      const numbers2 = apiCache.getItems();

      expect(numbers1).toMatchObject([{ number: 1 }, { number: 2 }]);
      expect(numbers2).toMatchObject([
        { number: 1 },
        { number: 2 },
        { number: 3 },
      ]);
    });

    it('resets cache on page reconcile', () => {
      const item1 = { number: 1, updated_at: t1 };
      const item2 = { number: 2, updated_at: t2 };
      const item3 = { number: 3, updated_at: t3 };
      const apiCache = new ApiCache({
        items: {
          1: item1,
          2: item2,
        },
      });

      const numbers1 = apiCache.getItems();
      apiCache.reconcile([item3]);
      const numbers2 = apiCache.getItems();

      expect(numbers1).toMatchObject([{ number: 1 }, { number: 2 }]);
      expect(numbers2).toMatchObject([
        { number: 1 },
        { number: 2 },
        { number: 3 },
      ]);
    });
  });

  describe('reconcile', () => {
    it('appends new items', () => {
      const apiCache = new ApiCache({ items: {} });

      const res1 = apiCache.reconcile([
        { number: 2, updated_at: t2 },
        { number: 1, updated_at: t1 },
      ]);
      expect(res1).toBeTrue();

      const res2 = apiCache.reconcile([
        { number: 4, updated_at: t4 },
        { number: 3, updated_at: t3 },
      ]);
      expect(res2).toBeTrue();

      expect(apiCache.getItems()).toEqual([
        { number: 1, updated_at: t1 },
        { number: 2, updated_at: t2 },
        { number: 3, updated_at: t3 },
        { number: 4, updated_at: t4 },
      ]);
    });

    it('handles updated items', () => {
      const apiCache = new ApiCache({
        items: {
          1: { number: 1, updated_at: t1 },
          2: { number: 2, updated_at: t2 },
          3: { number: 3, updated_at: t3 },
        },
        lastModified: t3,
      });

      const needNextPage = apiCache.reconcile([
        { number: 1, updated_at: t5 },
        { number: 2, updated_at: t4 },
        { number: 3, updated_at: t3 },
      ]);

      expect(apiCache.getItems()).toEqual([
        { number: 1, updated_at: t5 },
        { number: 2, updated_at: t4 },
        { number: 3, updated_at: t3 },
      ]);
      expect(needNextPage).toBeFalse();
    });

    it('ignores page overlap', () => {
      const apiCache = new ApiCache({
        items: {},
      });

      const res1 = apiCache.reconcile([
        { number: 5, updated_at: t5 },
        { number: 4, updated_at: t4 },
        { number: 3, updated_at: t3 },
      ]);
      const res2 = apiCache.reconcile([
        { number: 3, updated_at: t3 },
        { number: 2, updated_at: t2 },
        { number: 1, updated_at: t1 },
      ]);

      expect(apiCache.getItems()).toEqual([
        { number: 1, updated_at: t1 },
        { number: 2, updated_at: t2 },
        { number: 3, updated_at: t3 },
        { number: 4, updated_at: t4 },
        { number: 5, updated_at: t5 },
      ]);
      expect(res1).toBeTrue();
      expect(res2).toBeTrue();
    });

    it('does not require new page if all items are old', () => {
      const apiCache = new ApiCache({
        items: {
          1: { number: 1, updated_at: t1 },
          2: { number: 2, updated_at: t2 },
          3: { number: 3, updated_at: t3 },
        },
        lastModified: t3,
      });

      const needNextPage = apiCache.reconcile([
        { number: 3, updated_at: t3 },
        { number: 2, updated_at: t2 },
        { number: 1, updated_at: t1 },
      ]);

      expect(apiCache.getItems()).toEqual([
        { number: 1, updated_at: t1 },
        { number: 2, updated_at: t2 },
        { number: 3, updated_at: t3 },
      ]);
      expect(needNextPage).toBeFalse();
    });
  });
});
