import { DateTime, Settings } from 'luxon';
import * as memCache from '../../../cache/memory';
import { clone } from '../../../clone';
import type { Timestamp } from '../../../timestamp';
import type { GithubDatasourceItem, GithubGraphqlCacheRecord } from '../types';
import { GithubGraphqlMemoryCacheStrategy } from './memory-cache-strategy';

// const isoTs = (t: string) => DateTime.fromJSDate(new Date(t)).toISO()!;

const hourMinRe = /T\d{2}:\d{2}$/;
const hourMinSecRe = /T\d{2}:\d{2}:\d{2}$/;
const hourMinSecMillisRe = /T\d{2}:\d{2}:\d{2}\.\d\d\d$/;

const isoTs = (t: string) => {
  let iso = t.replace(' ', 'T');
  if (hourMinSecMillisRe.test(iso)) {
    iso = iso + 'Z';
  } else if (hourMinSecRe.test(iso)) {
    iso = iso + '.000Z';
  } else if (hourMinRe.test(iso)) {
    iso = iso + ':00.000Z';
  } else {
    throw new Error('Unrecognized date-time string. ' + t);
  }
  return iso as Timestamp;
};

const mockTime = (input: string): void => {
  const now = DateTime.fromISO(isoTs(input)).valueOf();
  Settings.now = () => now;
};

type CacheRecord = GithubGraphqlCacheRecord<GithubDatasourceItem>;

describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => {
  beforeEach(() => {
    memCache.init();
  });

  it('resets old cache', async () => {
    const items = {
      '1': { version: '1', releaseTimestamp: isoTs('2020-01-01 10:00') },
    };
    const cacheRecord: CacheRecord = {
      items,
      createdAt: isoTs('2022-10-01 15:30'),
    };
    memCache.set(
      'github-graphql-cache:_test-namespace:bar',
      clone(cacheRecord),
    );

    // At this moment, cache is valid
    let now = '2022-10-31 15:29:59';
    mockTime(now);

    let strategy = new GithubGraphqlMemoryCacheStrategy(
      '_test-namespace',
      'bar',
    );
    let isPaginationDone = await strategy.reconcile([items['1']]);
    let res = await strategy.finalizeAndReturn();

    expect(res).toEqual(Object.values(items));
    expect(isPaginationDone).toBe(true);
    expect(memCache.get('github-graphql-cache:_test-namespace:bar')).toEqual(
      cacheRecord,
    );

    // One second later, the cache is invalid
    now = '2022-10-31 15:30:00';
    mockTime(now);

    strategy = new GithubGraphqlMemoryCacheStrategy('_test-namespace', 'bar');
    isPaginationDone = await strategy.reconcile([]);
    res = await strategy.finalizeAndReturn();

    expect(res).toEqual([]);
    expect(isPaginationDone).toBe(false);
    expect(memCache.get('github-graphql-cache:_test-namespace:bar')).toEqual(
      cacheRecord,
    );
  });

  it('reconciles old cache record with new items', async () => {
    const oldItems = {
      '1': { version: '1', releaseTimestamp: isoTs('2020-01-01 10:00') },
      '2': { version: '2', releaseTimestamp: isoTs('2020-01-01 11:00') },
      '3': { version: '3', releaseTimestamp: isoTs('2020-01-01 12:00') },
    };
    const cacheRecord: CacheRecord = {
      items: oldItems,
      createdAt: isoTs('2022-10-30 12:00'),
    };
    memCache.set(
      'github-graphql-cache:_test-namespace:bar',
      clone(cacheRecord),
    );

    const now = '2022-10-31 15:30';
    mockTime(now);

    const newItem = {
      version: '4',
      releaseTimestamp: isoTs('2022-10-15 18:00'),
    };
    const page = [newItem];

    const strategy = new GithubGraphqlMemoryCacheStrategy(
      '_test-namespace',
      'bar',
    );
    const isPaginationDone = await strategy.reconcile(page);
    const res = await strategy.finalizeAndReturn();

    expect(res).toEqual([...Object.values(oldItems), newItem]);
    expect(isPaginationDone).toBe(false);
    expect(memCache.get('github-graphql-cache:_test-namespace:bar')).toEqual({
      items: {
        ...oldItems,
        '4': newItem,
      },
      createdAt: isoTs('2022-10-30 12:00'),
    });
  });

  it('signals to stop pagination', async () => {
    const oldItems = {
      '1': { releaseTimestamp: isoTs('2020-01-01 10:00'), version: '1' },
      '2': { releaseTimestamp: isoTs('2020-01-01 11:00'), version: '2' },
      '3': { releaseTimestamp: isoTs('2020-01-01 12:00'), version: '3' },
    };
    const cacheRecord: CacheRecord = {
      items: oldItems,
      createdAt: isoTs('2022-10-30 12:00'),
    };
    memCache.set(
      'github-graphql-cache:_test-namespace:bar',
      clone(cacheRecord),
    );

    const now = '2022-10-31 15:30';
    mockTime(now);

    const page = [
      ...Object.values(oldItems),
      { version: '4', releaseTimestamp: isoTs('2022-10-15 18:00') },
    ].reverse();

    const strategy = new GithubGraphqlMemoryCacheStrategy(
      '_test-namespace',
      'bar',
    );
    const isPaginationDone = await strategy.reconcile(page);

    expect(isPaginationDone).toBe(true);
  });

  it('reconciles entire page', async () => {
    const oldItems = {
      '1': { releaseTimestamp: isoTs('2020-01-01 00:00'), version: '1' },
      '2': { releaseTimestamp: isoTs('2020-01-01 01:00'), version: '2' },
      '3': { releaseTimestamp: isoTs('2020-01-01 02:00'), version: '3' },
    };
    const cacheRecord: CacheRecord = {
      items: oldItems,
      createdAt: isoTs('2022-12-31 12:00'),
    };
    memCache.set(
      'github-graphql-cache:_test-namespace:bar',
      clone(cacheRecord),
    );

    const now = '2022-12-31 23:59';
    mockTime(now);

    const page = [
      { version: '1', releaseTimestamp: isoTs('2022-12-31 10:00') },
      { version: '2', releaseTimestamp: isoTs('2022-12-31 11:00') },
      { version: '3', releaseTimestamp: isoTs('2022-12-31 12:00') },
      { version: '4', releaseTimestamp: isoTs('2022-12-31 13:00') },
    ].reverse();

    const strategy = new GithubGraphqlMemoryCacheStrategy(
      '_test-namespace',
      'bar',
    );
    const isPaginationDone = await strategy.reconcile(page);

    expect(isPaginationDone).toBe(true);
    expect(
      memCache.get('github-graphql-cache:_test-namespace:bar'),
    ).toMatchObject({
      items: {
        '1': { releaseTimestamp: isoTs('2022-12-31 10:00') },
        '2': { releaseTimestamp: isoTs('2022-12-31 11:00') },
        '3': { releaseTimestamp: isoTs('2022-12-31 12:00') },
        '4': { releaseTimestamp: isoTs('2022-12-31 13:00') },
      },
    });
  });

  it('detects removed packages', async () => {
    const items = {
      // stabilized
      '0': { version: '0', releaseTimestamp: isoTs('2022-09-30 10:00') }, // to be preserved
      '1': { version: '1', releaseTimestamp: isoTs('2022-10-01 10:00') }, // to be preserved
      // not stabilized
      '2': { version: '2', releaseTimestamp: isoTs('2022-10-02 10:00') },
      '3': { version: '3', releaseTimestamp: isoTs('2022-10-03 10:00') }, // to be deleted
      '4': { version: '4', releaseTimestamp: isoTs('2022-10-04 10:00') },
      '5': { version: '5', releaseTimestamp: isoTs('2022-10-05 10:00') }, // to be deleted
      '6': { version: '6', releaseTimestamp: isoTs('2022-10-06 10:00') },
      '7': { version: '7', releaseTimestamp: isoTs('2022-10-07 10:00') }, // to be deleted
      '8': { version: '8', releaseTimestamp: isoTs('2022-10-08 10:00') },
    };
    const cacheRecord: CacheRecord = {
      items,
      createdAt: isoTs('2022-10-30 12:00'),
    };
    memCache.set(
      'github-graphql-cache:_test-namespace:bar',
      clone(cacheRecord),
    );

    const now = '2022-10-31 15:30';
    mockTime(now);

    const page = [
      items['1'],
      items['2'],
      items['4'],
      items['6'],
      items['8'],
    ].reverse();

    const strategy = new GithubGraphqlMemoryCacheStrategy(
      '_test-namespace',
      'bar',
    );
    const isPaginationDone = await strategy.reconcile(page);
    const res = await strategy.finalizeAndReturn();

    expect(res).toEqual([
      { version: '0', releaseTimestamp: isoTs('2022-09-30 10:00') },
      { version: '1', releaseTimestamp: isoTs('2022-10-01 10:00') },
      { version: '2', releaseTimestamp: isoTs('2022-10-02 10:00') },
      { version: '4', releaseTimestamp: isoTs('2022-10-04 10:00') },
      { version: '6', releaseTimestamp: isoTs('2022-10-06 10:00') },
      { version: '8', releaseTimestamp: isoTs('2022-10-08 10:00') },
    ]);
    expect(isPaginationDone).toBe(true);
    expect(memCache.get('github-graphql-cache:_test-namespace:bar')).toEqual({
      items: {
        '0': { version: '0', releaseTimestamp: isoTs('2022-09-30 10:00') },
        '1': { version: '1', releaseTimestamp: isoTs('2022-10-01 10:00') },
        '2': { version: '2', releaseTimestamp: isoTs('2022-10-02 10:00') },
        '4': { version: '4', releaseTimestamp: isoTs('2022-10-04 10:00') },
        '6': { version: '6', releaseTimestamp: isoTs('2022-10-06 10:00') },
        '8': { version: '8', releaseTimestamp: isoTs('2022-10-08 10:00') },
      },
      createdAt: isoTs('2022-10-30 12:00'),
    });
  });
});
