import { helper } from "@grouparoo/spec-helper";
import { specHelper, api, Connection } from "actionhero";
import { GrouparooModel, Run, Source } from "../../src";
import { SessionCreate } from "../../src/actions/session";
import {
  ScheduleCreate,
  ScheduleDestroy,
  ScheduleEdit,
  ScheduleFilterOptions,
  ScheduleRun,
  SchedulesRun,
  SchedulesList,
  ScheduleView,
} from "../../src/actions/schedules";
import { ConfigWriter } from "../../src/modules/configWriter";

describe("actions/schedules", () => {
  helper.grouparooTestServer({ truncate: true, enableTestPlugin: true });
  const configSpy = jest.spyOn(ConfigWriter, "run");

  let id: string;
  let model: GrouparooModel;
  let otherModel: GrouparooModel;
  let source: Source;
  let source2: Source;

  beforeAll(async () => {
    ({ model } = await helper.factories.properties());

    await specHelper.runAction("team:initialize", {
      firstName: "Mario",
      lastName: "Mario",
      password: "P@ssw0rd!",
      email: "mario@example.com",
    });
  });

  afterEach(() => {
    configSpy.mockClear();
  });

  describe("administrator signed in", () => {
    let connection: Connection;
    let csrfToken: string;

    beforeAll(async () => {
      // sign in
      connection = await specHelper.buildConnection();
      connection.params = { email: "mario@example.com", password: "P@ssw0rd!" };
      const sessionResponse = await specHelper.runAction<SessionCreate>(
        "session:create",
        connection
      );
      csrfToken = sessionResponse.csrfToken;

      otherModel = await helper.factories.model({
        id: "other_id",
        name: "other model",
      });
      //source using default model
      source = await helper.factories.source(null, { modelId: "mod_profiles" });
      await source.setOptions({ table: "users" });
      await source.setMapping({ id: "userId" });
      await source.update({ state: "ready" });

      //source using new model
      source2 = await helper.factories.source(null, {
        modelId: otherModel.id,
      });

      await source2.setOptions({ table: "admins" });
      const newProperty = await helper.factories.property(
        source2,
        {
          unique: true,
          key: "newProperty",
        },
        { column: "email" }
      );
      newProperty.update({ state: "ready" });
      await source2.setMapping({ id: newProperty.key });
      await source2.update({ state: "ready" });
    });

    test("an administrator can create a new schedule", async () => {
      connection.params = {
        csrfToken,
        name: "test schedule",
        type: "test-plugin-import",
        sourceId: source.id,
        recurring: false,
        confirmRecords: true,
      };
      const { error, schedule } = await specHelper.runAction<ScheduleCreate>(
        "schedule:create",
        connection
      );

      expect(error).toBeUndefined();
      expect(schedule.id).toBeTruthy();
      expect(schedule.name).toBe("test schedule");
      expect(schedule.confirmRecords).toBe(true);
      expect(configSpy).toBeCalledTimes(1);

      id = schedule.id;
    });

    describe("with schedule", () => {
      beforeAll(async () => {
        //schedule with created otherModel's source
        connection.params = {
          csrfToken,
          name: "test schedule 2",
          type: "test-plugin-import",
          sourceId: source2.id,
          recurring: false,
          confirmRecords: true,
        };
        const schedule2 = await specHelper.runAction<ScheduleCreate>(
          "schedule:create",
          connection
        );
      });

      test("an administrator can list all the schedules", async () => {
        connection.params = {
          csrfToken,
        };
        const { error, schedules, total } =
          await specHelper.runAction<SchedulesList>(
            "schedules:list",
            connection
          );

        expect(error).toBeUndefined();
        expect(schedules.length).toBe(2);
        expect(schedules[0].name).toBe("test schedule 2");
        expect(total).toBe(2);
      });

      test("an administrator can list all schedules by model id", async () => {
        connection.params = {
          csrfToken,
          modelId: model.id,
        };
        const { error, schedules, total } =
          await specHelper.runAction<SchedulesList>(
            "schedules:list",
            connection
          );

        expect(error).toBeUndefined();
        expect(schedules.length).toBe(1);
        expect(schedules[0].name).toBe("test schedule");
        expect(total).toBe(1);
      });

      test("an administrator can edit a schedule", async () => {
        connection.params = {
          csrfToken,
          id,
          name: "new schedule name",
          confirmRecords: false,
        };
        const { error, schedule } = await specHelper.runAction<ScheduleEdit>(
          "schedule:edit",
          connection
        );
        expect(error).toBeUndefined();
        expect(schedule.id).toBeTruthy();
        expect(schedule.name).toBe("new schedule name");
        expect(schedule.state).toBe("draft");
        expect(schedule.confirmRecords).toBe(false);
        expect(configSpy).toBeCalledTimes(1);
      });

      test("an administrator can edit recurring frequency", async () => {
        connection.params = {
          csrfToken,
          id,
          recurring: true,
          recurringFrequency: 60000,
        };

        const { error: firstError } = await specHelper.runAction<ScheduleEdit>(
          "schedule:edit",
          connection
        );
        expect(firstError).toBeUndefined();

        connection.params.recurringFrequency = 120000;

        const { error, schedule } = await specHelper.runAction<ScheduleEdit>(
          "schedule:edit",
          connection
        );

        expect(error).toBeUndefined();
        expect(schedule.recurring).toBe(true);
        expect(schedule.recurringFrequency).toBe(120000);
      });

      test("an administrator cannot edit recurring frequency when schedule is not recurring", async () => {
        connection.params = {
          csrfToken,
          id,
          recurring: false,
          recurringFrequency: 60000,
        };

        const { error, schedule } = await specHelper.runAction<ScheduleEdit>(
          "schedule:edit",
          connection
        );

        expect(error).toBeUndefined();
        expect(schedule.recurring).toBe(false);
        expect(schedule.recurringFrequency).toBe(0);
      });

      test("an administrator can update recurring to false", async () => {
        connection.params = {
          csrfToken,
          id,
          recurring: true,
          recurringFrequency: 60000,
        };

        const { error: firstError } = await specHelper.runAction<ScheduleEdit>(
          "schedule:edit",
          connection
        );
        expect(firstError).toBeUndefined();

        connection.params.recurring = false;
        delete connection.params["recurringFrequency"];

        const { error, schedule } = await specHelper.runAction<ScheduleEdit>(
          "schedule:edit",
          connection
        );

        expect(error).toBeUndefined();
        expect(schedule.recurring).toBe(false);
        expect(schedule.recurringFrequency).toBe(0);
      });

      test("an administrator can edit refresh enabled", async () => {
        connection.params = {
          csrfToken,
          id,
          refreshEnabled: true,
        };

        const { schedule: firstSchedule, error: firstError } =
          await specHelper.runAction<ScheduleEdit>("schedule:edit", connection);
        expect(firstError).toBeUndefined();
        expect(firstSchedule.refreshEnabled).toBe(true);

        connection.params.refreshEnabled = false;

        const { error, schedule } = await specHelper.runAction<ScheduleEdit>(
          "schedule:edit",
          connection
        );

        expect(error).toBeUndefined();
        expect(schedule.refreshEnabled).toBe(false);
      });

      test("an administrator can skip editing refresh enabled", async () => {
        connection.params = {
          csrfToken,
          id,
          refreshEnabled: true,
        };

        const { error: firstError } = await specHelper.runAction<ScheduleEdit>(
          "schedule:edit",
          connection
        );
        expect(firstError).toBeUndefined();

        delete connection.params["refreshEnabled"];

        const { error, schedule } = await specHelper.runAction<ScheduleEdit>(
          "schedule:edit",
          connection
        );

        expect(error).toBeUndefined();
        expect(schedule.refreshEnabled).toBe(true);
      });

      test("an administrator can view a schedule", async () => {
        connection.params = {
          csrfToken,
          id,
        };
        const { error, schedule, pluginOptions } =
          await specHelper.runAction<ScheduleView>("schedule:view", connection);
        expect(error).toBeUndefined();
        expect(schedule.id).toBeTruthy();
        expect(schedule.name).toBe("new schedule name");
        expect(pluginOptions[0].key).toBe("maxColumn");
        expect(configSpy).toBeCalledTimes(0);
      });

      test("an administrator can view the filter options for a schedule", async () => {
        connection.params = {
          csrfToken,
          id,
        };
        const { error, options } =
          await specHelper.runAction<ScheduleFilterOptions>(
            "schedule:filterOptions",
            connection
          );

        expect(error).toBeUndefined();
        expect(options).toEqual([
          {
            key: "id",
            ops: ["gt", "lt"],
            canHaveRelativeMatch: false,
          },
        ]);
      });

      test("an administrator can set the filters for a schedule", async () => {
        connection.params = {
          csrfToken,
          id,
          filters: [{ key: "id", op: "gt", match: 6 }],
        };
        const { error, schedule } = await specHelper.runAction<ScheduleEdit>(
          "schedule:edit",
          connection
        );

        expect(error).toBeUndefined();
        expect(schedule.filters).toEqual([
          {
            key: "id",
            match: "6",
            op: "gt",
            relativeMatchDirection: null,
            relativeMatchNumber: null,
            relativeMatchUnit: null,
          },
        ]);
        expect(configSpy).toBeCalledTimes(1);
      });

      test("an schedule can be made ready", async () => {
        connection.params = {
          csrfToken,
          id,
          options: { maxColumn: "created_at" },
          state: "ready",
        };
        const { error, schedule } = await specHelper.runAction<ScheduleEdit>(
          "schedule:edit",
          connection
        );
        expect(error).toBeUndefined();
        expect(schedule.state).toBe("ready");
      });

      test("an administrator can request a schedule run and enqueue a runConnection task", async () => {
        await api.resque.queue.connection.redis.flushdb();
        await Run.truncate();

        // first run will succeed
        connection.params = {
          csrfToken,
          id,
        };
        const { error, run } = await specHelper.runAction<ScheduleRun>(
          "schedule:run",
          connection
        );
        expect(error).toBeUndefined();
        expect(run.id).toBeTruthy();

        const tasks = await specHelper.findEnqueuedTasks("schedule:run");
        expect(tasks.length).toBe(1);
        expect(tasks[0].args[0].scheduleId).toBe(id);

        // second run will stop first run
        const { error: secondError, run: secondRun } =
          await specHelper.runAction<ScheduleRun>("schedule:run", connection);
        expect(secondError).toBeUndefined();
        expect(secondRun.id).toBeTruthy();
        expect(run.id).not.toEqual(secondRun.id);

        const runs = await Run.findAll({
          where: { creatorId: id, state: "running" },
        });
        expect(runs.map((r) => r.id)).toEqual([secondRun.id]);
      });

      test("an administrator can request to run all schedules and create new runs", async () => {
        await api.resque.queue.connection.redis.flushdb();
        await Run.truncate();

        connection.params = { csrfToken };
        const { error, runs } = await specHelper.runAction<SchedulesRun>(
          "schedules:run",
          connection
        );
        expect(error).toBeUndefined();
        expect(runs.length).toBe(1);

        const tasks = await specHelper.findEnqueuedTasks("schedule:run");
        expect(tasks.length).toBe(1);
        expect(tasks[0].args[0].scheduleId).toBe(id);
      });

      test("an administrator can request to run all schedules and stop running runs", async () => {
        await api.resque.queue.connection.redis.flushdb();
        await Run.truncate();
        const run = await Run.create({
          state: "running",
          creatorType: "schedule",
          creatorId: id,
        });

        connection.params = { csrfToken };
        const { error, runs } = await specHelper.runAction<SchedulesRun>(
          "schedules:run",
          connection
        );
        expect(error).toBeUndefined();
        expect(runs.length).toBe(1);
        expect(runs[0].id).not.toEqual(run.id);
      });

      test("an administrator can request to run some schedules by ID", async () => {
        connection.params = { csrfToken, scheduleIds: [id] };
        const { error, runs } = await specHelper.runAction<SchedulesRun>(
          "schedules:run",
          connection
        );
        expect(error).toBeUndefined();
        expect(runs.length).toBe(1);

        connection.params = { csrfToken, scheduleIds: ["foo"] };
        const { error: errorAgain, runs: runsAgain } =
          await specHelper.runAction<SchedulesRun>("schedules:run", connection);
        expect(errorAgain).toBeUndefined();
        expect(runsAgain.length).toBe(0);
      });

      test("an administrator can request to run some schedules by modelId", async () => {
        connection.params = { csrfToken, modelId: model.id };
        const { error, runs } = await specHelper.runAction<SchedulesRun>(
          "schedules:run",
          connection
        );
        expect(error).toBeUndefined();
        expect(runs.length).toBe(1);

        connection.params = { csrfToken, modelId: "foo" };
        const { error: errorAgain, runs: runsAgain } =
          await specHelper.runAction<SchedulesRun>("schedules:run", connection);
        expect(errorAgain).toBeUndefined();
        expect(runsAgain.length).toBe(0);
      });

      test("an administrator can destroy a schedule", async () => {
        connection.params = {
          csrfToken,
          id: id,
        };

        const destroyResponse = await specHelper.runAction<ScheduleDestroy>(
          "schedule:destroy",
          connection
        );

        expect(destroyResponse.error).toBeUndefined();
        expect(destroyResponse.success).toBe(true);
        expect(configSpy).toBeCalledTimes(1);
      });
    });
  });
});
