// tslint:disable:no-unused-expression
import { faker } from "@faker-js/faker";
import { fail } from "assert";
import * as chai from "chai";
import { KeycloakAdminClient } from "../src/client.js";
import { RequiredActionAlias } from "../src/defs/requiredActionProviderRepresentation.js";
import { credentials } from "./constants.js";

const expect = chai.expect;

describe("Authentication management", () => {
  let kcAdminClient: KeycloakAdminClient;
  let currentRealm: string;
  let requiredActionProvider: Record<string, any>;

  before(async () => {
    kcAdminClient = new KeycloakAdminClient();
    await kcAdminClient.auth(credentials);
    const realmName = faker.internet.username().toLowerCase();
    await kcAdminClient.realms.create({
      id: realmName,
      realm: realmName,
      enabled: true,
    });
    currentRealm = realmName;
    kcAdminClient.setConfig({
      realmName,
    });
  });

  after(async () => {
    // delete test realm
    await kcAdminClient.realms.del({ realm: currentRealm });
    const realm = await kcAdminClient.realms.findOne({
      realm: currentRealm,
    });
    expect(realm).to.be.null;
  });

  /**
   * Required Actions
   */
  describe("Required Actions", () => {
    it("should delete required action by alias", async () => {
      await kcAdminClient.authenticationManagement.deleteRequiredAction({
        alias: RequiredActionAlias.UPDATE_PROFILE,
      });
    });

    it("should get unregistered required actions", async () => {
      const unregisteredReqActions =
        await kcAdminClient.authenticationManagement.getUnregisteredRequiredActions();
      expect(unregisteredReqActions).to.be.an("array");
      expect(unregisteredReqActions.length).to.be.least(1);
      requiredActionProvider = unregisteredReqActions[0];
    });

    it("should register new required action", async () => {
      const requiredAction =
        await kcAdminClient.authenticationManagement.registerRequiredAction({
          providerId: requiredActionProvider.providerId,
          name: requiredActionProvider.name,
        });
      expect(requiredAction).to.be.empty;
    });

    it("should get required actions", async () => {
      const requiredActions =
        await kcAdminClient.authenticationManagement.getRequiredActions();
      expect(requiredActions).to.be.an("array");
    });

    it("should get required action by alias", async () => {
      const requiredAction =
        await kcAdminClient.authenticationManagement.getRequiredActionForAlias({
          alias: requiredActionProvider.providerId,
        });
      expect(requiredAction).to.be.ok;
    });

    it("should update required action by alias", async () => {
      const requiredAction =
        await kcAdminClient.authenticationManagement.getRequiredActionForAlias({
          alias: requiredActionProvider.providerId,
        });
      const response =
        await kcAdminClient.authenticationManagement.updateRequiredAction(
          { alias: requiredActionProvider.providerId },
          {
            ...requiredAction,
            enabled: true,
            priority: 10,
          },
        );
      expect(response).to.be.empty;
    });

    it("should lower required action priority", async () => {
      const requiredAction =
        await kcAdminClient.authenticationManagement.getRequiredActionForAlias({
          alias: requiredActionProvider.providerId,
        });
      const response =
        await kcAdminClient.authenticationManagement.lowerRequiredActionPriority(
          { alias: requiredActionProvider.providerId },
        );
      expect(response).to.be.empty;
      const requiredActionUpdated =
        await kcAdminClient.authenticationManagement.getRequiredActionForAlias({
          alias: requiredActionProvider.providerId,
        });
      expect(requiredActionUpdated.priority).to.be.greaterThan(
        requiredAction.priority,
      );
    });

    it("should raise required action priority", async () => {
      const requiredAction =
        await kcAdminClient.authenticationManagement.getRequiredActionForAlias({
          alias: requiredActionProvider.providerId,
        });
      const response =
        await kcAdminClient.authenticationManagement.raiseRequiredActionPriority(
          { alias: requiredActionProvider.providerId },
        );
      expect(response).to.be.empty;
      const requiredActionUpdated =
        await kcAdminClient.authenticationManagement.getRequiredActionForAlias({
          alias: requiredActionProvider.providerId,
        });
      expect(requiredActionUpdated.priority).to.be.lessThan(
        requiredAction.priority,
      );
    });

    it("should fetch config description for required action", async () => {
      const configDescription =
        await kcAdminClient.authenticationManagement.getRequiredActionConfigDescription(
          {
            alias: "UPDATE_PASSWORD",
          },
        );

      expect(configDescription).is.ok;
      expect(configDescription.properties).is.ok;
    });

    it("should fetch required action config for update password", async () => {
      const actionConfig =
        await kcAdminClient.authenticationManagement.getRequiredActionConfig({
          alias: "UPDATE_PASSWORD",
        });

      expect(actionConfig).is.ok;
      expect(actionConfig.config).is.ok;
      expect(actionConfig.config!["max_auth_age"]).to.be.undefined; // default max_auth_age for update password
    });

    it("should update required action config for update password", async () => {
      await kcAdminClient.authenticationManagement.updateRequiredActionConfig(
        {
          alias: "UPDATE_PASSWORD",
        },
        {
          config: {
            max_auth_age: "301",
          },
        },
      );

      const actionConfig =
        await kcAdminClient.authenticationManagement.getRequiredActionConfig({
          alias: "UPDATE_PASSWORD",
        });

      expect(actionConfig).is.ok;
      expect(actionConfig.config).is.ok;
      expect(actionConfig.config!["max_auth_age"]).to.be.eq("301"); // updated value max_auth_age for update password
    });

    it("should reset required action config for update password", async () => {
      await kcAdminClient.authenticationManagement.removeRequiredActionConfig({
        alias: "UPDATE_PASSWORD",
      });

      const actionConfig =
        await kcAdminClient.authenticationManagement.getRequiredActionConfig({
          alias: "UPDATE_PASSWORD",
        });

      expect(actionConfig).is.ok;
      expect(actionConfig.config).is.ok;
      expect(actionConfig.config!["max_auth_age"]).to.be.undefined; // default max_auth_age for update password
    });

    it("should get client authenticator providers", async () => {
      const authenticationProviders =
        await kcAdminClient.authenticationManagement.getClientAuthenticatorProviders();

      expect(authenticationProviders).is.ok;
      expect(authenticationProviders.length).to.be.equal(5);
    });

    it("should fetch form providers", async () => {
      const formProviders =
        await kcAdminClient.authenticationManagement.getFormActionProviders();
      expect(formProviders).is.ok;
      expect(formProviders.length).to.be.eq(5);
    });

    it("should fetch authenticator providers", async () => {
      const providers =
        await kcAdminClient.authenticationManagement.getAuthenticatorProviders();
      expect(providers).is.ok;
      expect(providers.length).to.be.greaterThan(1);
    });
  });
  describe("Flows", () => {
    it("should get the registered form providers", async () => {
      const formProviders =
        await kcAdminClient.authenticationManagement.getFormProviders();

      expect(formProviders).to.be.ok;
      expect(formProviders.length).to.be.eq(1);
      expect(formProviders[0].displayName).to.be.eq("Registration Page");
    });

    it("should get authentication flows", async () => {
      const flows = await kcAdminClient.authenticationManagement.getFlows();

      expect(flows.map((flow) => flow.alias)).to.be.deep.eq([
        "browser",
        "direct grant",
        "registration",
        "reset credentials",
        "clients",
        "first broker login",
        "docker auth",
      ]);
    });

    it("should get authentication flow", async () => {
      const flows = await kcAdminClient.authenticationManagement.getFlows();
      const flow = await kcAdminClient.authenticationManagement.getFlow({
        flowId: flows[0].id!,
      });

      expect(flow.alias).to.be.eq("browser");
    });

    it("should create new authentication flow", async () => {
      const flow = "test";
      await kcAdminClient.authenticationManagement.createFlow({
        alias: flow,
        providerId: "basic-flow",
        description: "",
        topLevel: true,
        builtIn: false,
      });

      const flows = await kcAdminClient.authenticationManagement.getFlows();
      expect(flows.find((f) => f.alias === flow)).to.be.ok;
    });

    const flowName = "copy of browser";
    it("should copy existing authentication flow", async () => {
      await kcAdminClient.authenticationManagement.copyFlow({
        flow: "browser",
        newName: flowName,
      });

      const flows = await kcAdminClient.authenticationManagement.getFlows();
      const flow = flows.find((f) => f.alias === flowName);
      expect(flow).to.be.ok;
    });

    it("should update authentication flow", async () => {
      const flows = await kcAdminClient.authenticationManagement.getFlows();
      const flow = flows.find((f) => f.alias === flowName)!;
      const description = "Updated description";
      flow.description = description;
      await kcAdminClient.authenticationManagement.updateFlow(
        { flowId: flow.id! },
        flow,
      );
      const updatedFlow = await kcAdminClient.authenticationManagement.getFlow({
        flowId: flow.id!,
      });

      expect(updatedFlow.description).to.be.eq(description);
    });

    it("should delete authentication flow", async () => {
      let flows = await kcAdminClient.authenticationManagement.getFlows();
      const flow = flows.find((f) => f.alias === flowName)!;
      await kcAdminClient.authenticationManagement.deleteFlow({
        flowId: flow.id!,
      });

      flows = await kcAdminClient.authenticationManagement.getFlows();
      expect(flows.find((f) => f.alias === flowName)).to.be.undefined;
    });
  });
  describe("Flow executions", () => {
    it("should fetch all executions for a flow", async () => {
      const executions =
        await kcAdminClient.authenticationManagement.getExecutions({
          flow: "browser",
        });
      expect(executions.length).to.be.gt(5);
    });

    const flowName = "executionTest";
    it("should add execution to a flow", async () => {
      await kcAdminClient.authenticationManagement.copyFlow({
        flow: "browser",
        newName: flowName,
      });
      const execution =
        await kcAdminClient.authenticationManagement.addExecutionToFlow({
          flow: flowName,
          provider: "auth-otp-form",
        });

      expect(execution.id).to.be.ok;
    });

    it("should add flow to a flow", async () => {
      const flow = await kcAdminClient.authenticationManagement.addFlowToFlow({
        flow: flowName,
        alias: "subFlow",
        description: "",
        provider: "registration-page-form",
        type: "basic-flow",
      });
      const executions =
        await kcAdminClient.authenticationManagement.getExecutions({
          flow: flowName,
        });
      expect(flow.id).to.be.ok;

      expect(executions.map((execution) => execution.displayName)).includes(
        "subFlow",
      );
    });

    it("should update execution to a flow", async () => {
      let executions =
        await kcAdminClient.authenticationManagement.getExecutions({
          flow: flowName,
        });
      let execution = executions[executions.length - 1];
      const choice = execution.requirementChoices![1];
      execution.requirement = choice;
      await kcAdminClient.authenticationManagement.updateExecution(
        { flow: flowName },
        execution,
      );

      executions = await kcAdminClient.authenticationManagement.getExecutions({
        flow: flowName,
      });
      execution = executions[executions.length - 1];

      expect(execution.requirement).to.be.eq(choice);
    });

    it("should delete execution", async () => {
      let executions =
        await kcAdminClient.authenticationManagement.getExecutions({
          flow: flowName,
        });
      const id = executions[0].id!;
      await kcAdminClient.authenticationManagement.delExecution({ id });
      executions = await kcAdminClient.authenticationManagement.getExecutions({
        flow: flowName,
      });
      expect(executions.find((ex) => ex.id === id)).to.be.undefined;
    });

    it("should raise priority of execution", async () => {
      let executions =
        await kcAdminClient.authenticationManagement.getExecutions({
          flow: flowName,
        });
      let execution = executions[executions.length - 1];
      const priority = execution.index!;
      await kcAdminClient.authenticationManagement.raisePriorityExecution({
        id: execution.id!,
      });

      executions = await kcAdminClient.authenticationManagement.getExecutions({
        flow: flowName,
      });
      execution = executions.find((ex) => ex.id === execution.id)!;

      expect(execution.index).to.be.eq(priority - 1);
    });

    it("should lower priority of execution", async () => {
      let executions =
        await kcAdminClient.authenticationManagement.getExecutions({
          flow: flowName,
        });
      let execution = executions[0];
      const priority = execution.index!;
      await kcAdminClient.authenticationManagement.lowerPriorityExecution({
        id: execution.id!,
      });

      executions = await kcAdminClient.authenticationManagement.getExecutions({
        flow: flowName,
      });
      execution = executions.find((ex) => ex.id === execution.id)!;

      expect(execution.index).to.be.eq(priority + 1);
    });

    it("should create, update and delete config for execution", async () => {
      const execution = (
        await kcAdminClient.authenticationManagement.getExecutions({
          flow: flowName,
        })
      )[0];
      const alias = "test";
      let config = await kcAdminClient.authenticationManagement.createConfig({
        id: execution.id,
        alias,
      });
      config = await kcAdminClient.authenticationManagement.getConfig({
        id: config.id!,
      });
      expect(config.alias).to.be.eq(alias);

      const extraConfig = { defaultProvider: "sdf" };
      await kcAdminClient.authenticationManagement.updateConfig({
        ...config,
        config: extraConfig,
      });
      config = await kcAdminClient.authenticationManagement.getConfig({
        id: config.id!,
      });

      expect(config.config!.defaultProvider).to.be.eq(
        extraConfig.defaultProvider,
      );

      await kcAdminClient.authenticationManagement.delConfig({
        id: config.id!,
      });
      try {
        await kcAdminClient.authenticationManagement.getConfig({
          id: config.id!,
        });
        fail("should not find deleted config");
      } catch {
        // ignore
      }
    });

    it("should fetch config description for execution", async () => {
      const execution = (
        await kcAdminClient.authenticationManagement.getExecutions({
          flow: flowName,
        })
      )[0];

      const configDescription =
        await kcAdminClient.authenticationManagement.getConfigDescription({
          providerId: execution.providerId!,
        });
      expect(configDescription).is.ok;
      expect(configDescription.providerId).to.be.eq(execution.providerId);
    });
  });
});
