import { randomUUID } from "node:crypto";
import { Alepha, type Service } from "alepha";
import { DateTimeProvider } from "alepha/datetime";
import { LockProvider, lockOptions, MemoryLockProvider } from "alepha/lock";
import { expect } from "vitest";
import { $scheduler, type SchedulerPrimitiveOptions } from "../index.ts";

const store: Record<string, string> = {};

export class SharedLockProvider extends MemoryLockProvider {
  store = store;
}

export const testSchedulerBasic = async (options: {
  scheduler: Partial<SchedulerPrimitiveOptions>;
  lock?: Service<LockProvider>;
}) => {
  let count = 0;

  class TestApp {
    t = $scheduler({
      ...options.scheduler,
      lock: !!options.lock,
      handler: async () => {
        count += 1;
      },
    });
  }

  const prefix = randomUUID();

  const createApp = () => {
    const alepha = Alepha.create();
    alepha.store.mut(lockOptions, () => ({ prefixKey: prefix }));

    if (options.lock) {
      alepha.with({
        provide: LockProvider,
        use: options.lock,
      });
    }

    alepha.with(DateTimeProvider);
    alepha.with(TestApp);

    return alepha;
  };

  const apps = [createApp(), createApp(), createApp(), createApp()];

  expect(count).toEqual(0);

  await Promise.all(apps.map((app) => app.start()));

  expect(count).toEqual(0);

  if (options.scheduler.interval) {
    await Promise.all(
      apps.map((app) => app.inject(DateTimeProvider).travel([64, "seconds"])),
    );
  } else {
    await Promise.all(
      apps.map((app) => app.inject(DateTimeProvider).travel([1, "hour"])),
    );
  }

  await new Promise((r) => setTimeout(r, 100));

  if (options.lock) {
    await expect.poll(() => expect(count).toEqual(1)).toBeTruthy();
  } else {
    await expect
      .poll(() => expect(count).toEqual(1 * apps.length))
      .toBeTruthy();
  }

  await Promise.all(apps.map((app) => app.stop()));
};
