import {
  ResqueDelayedJobs,
  ResqueDelLock,
  ResqueDelQueue,
  ResqueFailedCount,
  ResqueLoadWorkerQueues,
  ResqueLocks,
  ResqueQueued,
  ResqueRedisInfo,
  ResqueResqueDetails,
  ResqueResqueFailed,
} from "../src/actions/ah-resque-ui";
import { Process, task, api, specHelper } from "actionhero";

const sleep = (zzz = 100) => {
  return new Promise((resolve) => {
    setTimeout(resolve, zzz);
  });
};

const app = new Process();

describe("ah-resque-ui", () => {
  beforeAll(async () => {
    // jest.setTimeout(10000);

    await app.initialize();
    await api.redis.clients.client.flushdb();
    await app.start();

    // @ts-ignore
    api.tasks.tasks.testTask = {
      name: "testTask",
      description: "testTask",
      queue: "testQueue",
      frequency: 0,
      run: async (params) => {
        if (params.fail === true) {
          throw new Error("broken");
        }
      },
    };

    // api.resque.multiWorker.start();
    api.tasks.jobs.testTask = api.tasks.jobWrapper("testTask");
    await sleep(100); // for the workers to start
  });

  afterAll(async () => {
    // await api.resque.multiWorker.stop();
    delete api.tasks.tasks.testTask;
    delete api.tasks.jobs.testTask;
    await app.stop();
  });

  it("resque:redisInfo", async () => {
    const response = await specHelper.runAction<ResqueRedisInfo>(
      "resque:redisInfo"
    );
    const info = response.redisInfo.join("\n");
    expect(info).toMatch(/redis_version/);
    expect(info).toMatch(/used_memory/);
    expect(info).toMatch(/used_memory_human/);
  });

  it("resque:forceCleanWorker", () => {}); // TODO

  describe("with locks", () => {
    beforeEach(async () => {
      await api.resque.queue.connection.redis.set(
        api.resque.queue.connection.key("lock:lists:queueName:jobName:[{}]"),
        123
      );
    });
    beforeEach(async () => {
      await api.resque.queue.connection.redis.set(
        api.resque.queue.connection.key(
          "workerslock:lists:queueName:jobName:[{}]"
        ),
        456
      );
    });

    afterEach(async () => {
      await api.resque.queue.connection.redis.del(
        api.resque.queue.connection.key("lock:lists:queueName:jobName:[{}]")
      );
    });
    afterEach(async () => {
      await api.resque.queue.connection.redis.del(
        api.resque.queue.connection.key(
          "workerslock:lists:queueName:jobName:[{}]"
        )
      );
    });

    it("resque:locks", async () => {
      const response = await specHelper.runAction<ResqueLocks>("resque:locks");
      expect(Object.keys(response.locks).length).toEqual(2);
      expect(response.locks["lock:lists:queueName:jobName:[{}]"]).toEqual(
        "123"
      );
      expect(
        response.locks["workerslock:lists:queueName:jobName:[{}]"]
      ).toEqual("456");
    });

    it("resque:delLock", async () => {
      let response = await specHelper.runAction<ResqueDelLock>(
        "resque:delLock",
        {
          lock: "workerslock:lists:queueName:jobName:[{}]",
        }
      );
      expect(response.count).toEqual(1);
      let locksResponse = await specHelper.runAction<ResqueLocks>(
        "resque:locks"
      );
      expect(Object.keys(locksResponse.locks).length).toEqual(1);
      expect(locksResponse.locks["lock:lists:queueName:jobName:[{}]"]).toEqual(
        "123"
      );
    });
  });

  describe("with jobs", () => {
    beforeEach(async () => {
      await specHelper.runAction("resque:delQueue", { queue: "testQueue" });
      await task.enqueue("testTask", { a: 1 }, "testQueue");
      await task.enqueue("testTask", { b: 2 }, "testQueue");
      await task.enqueue("testTask", { c: 3 }, "testQueue");
    });

    it("resque:resqueDetails", async () => {
      const response = await specHelper.runAction<ResqueResqueDetails>(
        "resque:resqueDetails"
      );
      expect(response.resqueDetails.queues.testQueue.length).toEqual(3);
      expect(Object.keys(response.resqueDetails.workers).length).toEqual(1);
    });

    it("resque:loadWorkerQueues", async () => {
      const response = await specHelper.runAction<ResqueLoadWorkerQueues>(
        "resque:loadWorkerQueues"
      );
      const workerNames = Object.keys(response.workerQueues);
      expect(workerNames.length).toBe(1);
      expect(response.workerQueues[workerNames[0]]).toContain("*");
    });

    it("resque:queued (good)", async () => {
      const response = await specHelper.runAction<ResqueQueued>(
        "resque:queued",
        {
          queue: "testQueue",
        }
      );
      expect(response.jobs.length).toBe(3);
    });

    it("resque:queued (bad)", async () => {
      const response = await specHelper.runAction<ResqueQueued>(
        "resque:queued",
        {
          queue: "xxx",
        }
      );
      expect(response.jobs.length).toBe(0);
    });

    it("resque:delQueue", async () => {
      await specHelper.runAction<ResqueDelQueue>("resque:delQueue", {
        queue: "testQueue",
      });
      const response = await specHelper.runAction<ResqueQueued>(
        "resque:queued",
        {
          queue: "testQueue",
        }
      );
      expect(response.jobs.length).toBe(0);
    });
  });

  describe("with delayed jobs", () => {
    beforeAll(async () => {
      await task.enqueueAt(1000, "testTask", { a: 1 }, "testQueue");
      await task.enqueueAt(2000, "testTask", { b: 2 }, "testQueue");
      await task.enqueueAt(3000, "testTask", { c: 3 }, "testQueue");
    });

    it("resque:delayedjobs (defaults)", async () => {
      const response = await specHelper.runAction<ResqueDelayedJobs>(
        "resque:delayedjobs"
      );
      expect(response.timestampsCount).toBe(3);
      expect(Object.keys(response.delayedjobs).length).toBe(3);
      expect(response.delayedjobs["1000"].tasks[0].args[0].a).toBe(1);
      expect(response.delayedjobs["2000"].tasks[0].args[0].b).toBe(2);
      expect(response.delayedjobs["3000"].tasks[0].args[0].c).toBe(3);
    });

    it("resque:delayedjobs (pagination)", async () => {
      let response;
      response = await specHelper.runAction("resque:delayedjobs", {
        start: 0,
        stop: 1,
      });
      expect(response.timestampsCount).toBe(3);
      expect(Object.keys(response.delayedjobs).length).toBe(2);
      expect(response.delayedjobs["1000"].tasks[0].args[0].a).toBe(1);
      expect(response.delayedjobs["2000"].tasks[0].args[0].b).toBe(2);

      response = await specHelper.runAction("resque:delayedjobs", {
        start: 2,
        stop: 999,
      });
      expect(response.timestampsCount).toBe(3);
      expect(Object.keys(response.delayedjobs).length).toBe(1);
      expect(response.delayedjobs["3000"].tasks[0].args[0].c).toBe(3);
    });

    it("resque:delDelayed", () => {}); //TODO
    it("resque:runDelayed", () => {}); //TODO
  });

  describe("with failed jobs", () => {
    beforeEach(async () => {
      await specHelper.runAction("resque:delQueue", { queue: "testQueue" });
      await specHelper.runAction("resque:removeAllFailed");
      await task.enqueue("testTask", { a: 1, fail: true }, "testQueue");
      await task.enqueue("testTask", { b: 2, fail: true }, "testQueue");
      await task.enqueue("testTask", { c: 3, fail: true }, "testQueue");

      // should allow time to work the bad jobs
      await sleep(1000);
    });

    it("resque:resqueFailedCount", async () => {
      const response = await specHelper.runAction<ResqueFailedCount>(
        "resque:resqueFailedCount"
      );
      expect(response.failedCount).toBe(3);
    });

    it("resque:resqueFailed (defaults)", async () => {
      const response = await specHelper.runAction<ResqueResqueFailed>(
        "resque:resqueFailed"
      );
      expect(response.failed.length).toBe(3);
      response.failed.forEach((j: any) => {
        expect(j.queue).toBe("testQueue");
        expect(j.error).toMatch("broken");
        expect(j.payload.args[0].fail).toBe(true);
      });
    });

    it("resque:resqueFailed (pagination)", async () => {
      let response;
      response = await specHelper.runAction("resque:resqueFailed", {
        start: 0,
        stop: 1,
      });
      expect(response.failed.length).toBe(2);
      expect(response.failed[0].payload.args[0].a).toBe(1);
      expect(response.failed[1].payload.args[0].b).toBe(2);

      response = await specHelper.runAction("resque:resqueFailed", {
        start: 2,
        stop: 99,
      });
      expect(response.failed.length).toBe(1);
      expect(response.failed[0].payload.args[0].c).toBe(3);
    });

    it("resque:removeFailed", async () => {
      await specHelper.runAction("resque:removeFailed", { id: 1 });
      const response = await specHelper.runAction<ResqueResqueFailed>(
        "resque:resqueFailed"
      );
      expect(response.failed.length).toBe(2);
      expect((response.failed[0] as any).payload.args[0].a).toBe(1);
      expect((response.failed[1] as any).payload.args[0].c).toBe(3);
    });

    it("resque:removeAllFailed", async () => {
      await specHelper.runAction("resque:removeAllFailed");
      const response = await specHelper.runAction<ResqueResqueFailed>(
        "resque:resqueFailed"
      );
      expect(response.failed.length).toBe(0);
    });

    it("resque:retryAndRemoveFailed", async () => {
      await specHelper.runAction("resque:retryAndRemoveFailed", { id: 1 });
      const response = await specHelper.runAction<ResqueResqueFailed>(
        "resque:resqueFailed"
      );
      expect(response.failed.length).toBe(2);
      expect((response.failed[0] as any).payload.args[0].a).toBe(1);
      expect((response.failed[1] as any).payload.args[0].c).toBe(3);

      const details = await specHelper.runAction<ResqueResqueDetails>(
        "resque:resqueDetails"
      );
      expect(details.resqueDetails.queues.testQueue.length).toBe(1);
    });

    it("resque:retryAndRemoveAllFailed", async () => {
      await specHelper.runAction("resque:retryAndRemoveAllFailed");
      const response = await specHelper.runAction<ResqueResqueFailed>(
        "resque:resqueFailed"
      );
      expect(response.failed.length).toBe(0);

      const details = await specHelper.runAction<ResqueResqueDetails>(
        "resque:resqueDetails"
      );
      expect(details.resqueDetails.queues.testQueue.length).toBe(3);
    });
  });
});
