// tslint:disable:no-unused-expression
import * as chai from "chai";
import { KeycloakAdminClient } from "../src/client.js";
import type ClientRepresentation from "../src/defs/clientRepresentation.js";
import type ClientScopeRepresentation from "../src/defs/clientScopeRepresentation.js";
import type ProtocolMapperRepresentation from "../src/defs/protocolMapperRepresentation.js";
import { credentials } from "./constants.js";

const expect = chai.expect;

describe("Client Scopes", () => {
  let kcAdminClient: KeycloakAdminClient;
  let currentClientScope: ClientScopeRepresentation;
  let currentClientScopeName: string;
  let currentClient: ClientRepresentation;

  before(async () => {
    kcAdminClient = new KeycloakAdminClient();
    await kcAdminClient.auth(credentials);
  });

  beforeEach(async () => {
    currentClientScopeName = "best-of-the-bests-scope";
    await kcAdminClient.clientScopes.create({
      name: currentClientScopeName,
      protocol: "openid-connect",
    });
    currentClientScope = (await kcAdminClient.clientScopes.findOneByName({
      name: currentClientScopeName,
    }))!;
  });

  afterEach(async () => {
    // cleanup default client scopes
    try {
      await kcAdminClient.clientScopes.delDefaultClientScope({
        id: currentClientScope.id!,
      });
    } catch {
      // ignore
    }

    // cleanup optional client scopes
    try {
      await kcAdminClient.clientScopes.delDefaultOptionalClientScope({
        id: currentClientScope.id!,
      });
    } catch {
      // ignore
    }

    // cleanup client scopes
    try {
      await kcAdminClient.clientScopes.delByName({
        name: currentClientScopeName,
      });
    } catch {
      // ignore
    }
  });

  it("list client scopes", async () => {
    const scopes = await kcAdminClient.clientScopes.find();
    expect(scopes).to.be.ok;
  });

  it("create client scope and get by name", async () => {
    // ensure that the scope does not exist
    try {
      await kcAdminClient.clientScopes.delByName({
        name: currentClientScopeName,
      });
    } catch {
      // ignore
    }

    await kcAdminClient.clientScopes.create({
      name: currentClientScopeName,
      protocol: "openid-connect",
    });

    const scope = (await kcAdminClient.clientScopes.findOneByName({
      name: currentClientScopeName,
    }))!;
    expect(scope).to.be.ok;
    expect(scope.name).to.equal(currentClientScopeName);
  });

  it("create client scope and return id", async () => {
    // ensure that the scope does not exist
    try {
      await kcAdminClient.clientScopes.delByName({
        name: currentClientScopeName,
      });
    } catch {
      // ignore
    }

    const { id } = await kcAdminClient.clientScopes.create({
      name: currentClientScopeName,
      protocol: "openid-connect",
    });

    const scope = (await kcAdminClient.clientScopes.findOne({
      id,
    }))!;
    expect(scope).to.be.ok;
    expect(scope.name).to.equal(currentClientScopeName);
  });

  it("find scope by id", async () => {
    const scope = await kcAdminClient.clientScopes.findOne({
      id: currentClientScope.id!,
    });
    expect(scope).to.be.ok;
    expect(scope).to.eql(currentClientScope);
  });

  it("find scope by name", async () => {
    const scope = (await kcAdminClient.clientScopes.findOneByName({
      name: currentClientScopeName,
    }))!;
    expect(scope).to.be.ok;
    expect(scope.name).to.eql(currentClientScopeName);
  });

  it("return null if scope not found by id", async () => {
    const scope = await kcAdminClient.clientScopes.findOne({
      id: "I do not exist",
    });
    expect(scope).to.be.null;
  });

  it("return null if scope not found by name", async () => {
    const scope = await kcAdminClient.clientScopes.findOneByName({
      name: "I do not exist",
    });
    expect(scope).to.be.undefined;
  });

  it.skip("update client scope", async () => {
    const { id, description: oldDescription } = currentClientScope;
    const description = "This scope is totally awesome.";

    await kcAdminClient.clientScopes.update({ id: id! }, { description });
    const updatedScope = (await kcAdminClient.clientScopes.findOne({
      id: id!,
    }))!;
    expect(updatedScope).to.be.ok;
    expect(updatedScope).not.to.eql(currentClientScope);
    expect(updatedScope.description).to.eq(description);
    expect(updatedScope.description).not.to.eq(oldDescription);
  });

  it("delete single client scope by id", async () => {
    await kcAdminClient.clientScopes.del({
      id: currentClientScope.id!,
    });
    const scope = await kcAdminClient.clientScopes.findOne({
      id: currentClientScope.id!,
    });
    expect(scope).not.to.be.ok;
  });

  it("delete single client scope by name", async () => {
    await kcAdminClient.clientScopes.delByName({
      name: currentClientScopeName,
    });
    const scope = await kcAdminClient.clientScopes.findOneByName({
      name: currentClientScopeName,
    });
    expect(scope).not.to.be.ok;
  });

  describe("default client scope", () => {
    it("list default client scopes", async () => {
      const defaultClientScopes =
        await kcAdminClient.clientScopes.listDefaultClientScopes();
      expect(defaultClientScopes).to.be.ok;
    });

    it("add default client scope", async () => {
      const { id } = currentClientScope;
      await kcAdminClient.clientScopes.addDefaultClientScope({ id: id! });

      const defaultClientScopeList =
        await kcAdminClient.clientScopes.listDefaultClientScopes();
      const defaultClientScope = defaultClientScopeList.find(
        (scope) => scope.id === id,
      )!;

      expect(defaultClientScope).to.be.ok;
      expect(defaultClientScope.id).to.equal(currentClientScope.id);
      expect(defaultClientScope.name).to.equal(currentClientScope.name);
    });

    it("delete default client scope", async () => {
      const { id } = currentClientScope;
      await kcAdminClient.clientScopes.addDefaultClientScope({ id: id! });

      await kcAdminClient.clientScopes.delDefaultClientScope({ id: id! });

      const defaultClientScopeList =
        await kcAdminClient.clientScopes.listDefaultClientScopes();
      const defaultClientScope = defaultClientScopeList.find(
        (scope) => scope.id === id,
      );

      expect(defaultClientScope).not.to.be.ok;
    });
  });

  describe("default optional client scopes", () => {
    it("list default optional client scopes", async () => {
      const defaultOptionalClientScopes =
        await kcAdminClient.clientScopes.listDefaultOptionalClientScopes();
      expect(defaultOptionalClientScopes).to.be.ok;
    });

    it("add default optional client scope", async () => {
      const { id } = currentClientScope;
      await kcAdminClient.clientScopes.addDefaultOptionalClientScope({
        id: id!,
      });

      const defaultOptionalClientScopeList =
        await kcAdminClient.clientScopes.listDefaultOptionalClientScopes();
      const defaultOptionalClientScope = defaultOptionalClientScopeList.find(
        (scope) => scope.id === id,
      )!;

      expect(defaultOptionalClientScope).to.be.ok;
      expect(defaultOptionalClientScope.id).to.eq(currentClientScope.id);
      expect(defaultOptionalClientScope.name).to.eq(currentClientScope.name);
    });

    it("delete default optional client scope", async () => {
      const { id } = currentClientScope;
      await kcAdminClient.clientScopes.addDefaultOptionalClientScope({
        id: id!,
      });
      await kcAdminClient.clientScopes.delDefaultOptionalClientScope({
        id: id!,
      });

      const defaultOptionalClientScopeList =
        await kcAdminClient.clientScopes.listDefaultOptionalClientScopes();
      const defaultOptionalClientScope = defaultOptionalClientScopeList.find(
        (scope) => scope.id === id,
      );

      expect(defaultOptionalClientScope).not.to.be.ok;
    });
  });

  describe("protocol mappers", () => {
    let dummyMapper: ProtocolMapperRepresentation;

    beforeEach(() => {
      dummyMapper = {
        name: "mapping-maps-mapper",
        protocol: "openid-connect",
        protocolMapper: "oidc-audience-mapper",
      };
    });

    afterEach(async () => {
      try {
        const { id } = currentClientScope;
        const { id: mapperId } =
          (await kcAdminClient.clientScopes.findProtocolMapperByName({
            id: id!,
            name: dummyMapper.name!,
          }))!;
        await kcAdminClient.clientScopes.delProtocolMapper({
          id: id!,
          mapperId: mapperId!,
        });
      } catch {
        // ignore
      }
    });

    it("list protocol mappers", async () => {
      const { id } = currentClientScope;
      const mapperList = await kcAdminClient.clientScopes.listProtocolMappers({
        id: id!,
      });
      expect(mapperList).to.be.ok;
    });

    it("add multiple protocol mappers", async () => {
      const { id } = currentClientScope;
      await kcAdminClient.clientScopes.addMultipleProtocolMappers({ id: id! }, [
        dummyMapper,
      ]);

      const mapper = (await kcAdminClient.clientScopes.findProtocolMapperByName(
        {
          id: id!,
          name: dummyMapper.name!,
        },
      ))!;
      expect(mapper).to.be.ok;
      expect(mapper.protocol).to.eq(dummyMapper.protocol);
      expect(mapper.protocolMapper).to.eq(dummyMapper.protocolMapper);
    });

    it("add single protocol mapper", async () => {
      const { id } = currentClientScope;
      await kcAdminClient.clientScopes.addProtocolMapper(
        { id: id! },
        dummyMapper,
      );

      const mapper = (await kcAdminClient.clientScopes.findProtocolMapperByName(
        {
          id: id!,
          name: dummyMapper.name!,
        },
      ))!;
      expect(mapper).to.be.ok;
      expect(mapper.protocol).to.eq(dummyMapper.protocol);
      expect(mapper.protocolMapper).to.eq(dummyMapper.protocolMapper);
    });

    it("find protocol mapper by id", async () => {
      const { id } = currentClientScope;
      await kcAdminClient.clientScopes.addProtocolMapper(
        { id: id! },
        dummyMapper,
      );

      const { id: mapperId } =
        (await kcAdminClient.clientScopes.findProtocolMapperByName({
          id: id!,
          name: dummyMapper.name!,
        }))!;

      const mapper = await kcAdminClient.clientScopes.findProtocolMapper({
        id: id!,
        mapperId: mapperId!,
      });

      expect(mapper).to.be.ok;
      expect(mapper?.id).to.eql(mapperId);
    });

    it("find protocol mapper by name", async () => {
      const { id } = currentClientScope;
      await kcAdminClient.clientScopes.addProtocolMapper(
        { id: id! },
        dummyMapper,
      );

      const mapper = (await kcAdminClient.clientScopes.findProtocolMapperByName(
        {
          id: id!,
          name: dummyMapper.name!,
        },
      ))!;

      expect(mapper).to.be.ok;
      expect(mapper.name).to.eql(dummyMapper.name);
    });

    it("find protocol mappers by protocol", async () => {
      const { id } = currentClientScope;
      await kcAdminClient.clientScopes.addProtocolMapper(
        { id: id! },
        dummyMapper,
      );

      const mapperList =
        await kcAdminClient.clientScopes.findProtocolMappersByProtocol({
          id: id!,
          protocol: dummyMapper.protocol!,
        });

      expect(mapperList).to.be.ok;
      expect(mapperList.length).to.be.gte(1);

      const mapper = mapperList.find((item) => item.name === dummyMapper.name);
      expect(mapper).to.be.ok;
    });

    it("update protocol mapper", async () => {
      const { id } = currentClientScope;

      dummyMapper.config = { "access.token.claim": "true" };
      await kcAdminClient.clientScopes.addProtocolMapper(
        { id: id! },
        dummyMapper,
      );
      const mapper = (await kcAdminClient.clientScopes.findProtocolMapperByName(
        {
          id: id!,
          name: dummyMapper.name!,
        },
      ))!;

      expect(mapper.config!["access.token.claim"]).to.eq("true");

      mapper.config = { "access.token.claim": "false" };

      await kcAdminClient.clientScopes.updateProtocolMapper(
        { id: id!, mapperId: mapper.id! },
        mapper,
      );

      const updatedMapper =
        (await kcAdminClient.clientScopes.findProtocolMapperByName({
          id: id!,
          name: dummyMapper.name!,
        }))!;

      expect(updatedMapper.config!["access.token.claim"]).to.eq("false");
    });

    it("delete protocol mapper", async () => {
      const { id } = currentClientScope;
      await kcAdminClient.clientScopes.addProtocolMapper(
        { id: id! },
        dummyMapper,
      );

      const { id: mapperId } =
        (await kcAdminClient.clientScopes.findProtocolMapperByName({
          id: id!,
          name: dummyMapper.name!,
        }))!;

      await kcAdminClient.clientScopes.delProtocolMapper({
        id: id!,
        mapperId: mapperId!,
      });

      const mapper = await kcAdminClient.clientScopes.findProtocolMapperByName({
        id: id!,
        name: dummyMapper.name!,
      });

      expect(mapper).not.to.be.ok;
    });
  });

  describe("scope mappings", () => {
    it("list client and realm scope mappings", async () => {
      const { id } = currentClientScope;
      const scopes = await kcAdminClient.clientScopes.listScopeMappings({
        id: id!,
      });
      expect(scopes).to.be.ok;
    });

    describe("client", () => {
      const dummyClientId = "scopeMappings-dummy";
      const dummyRoleName = "scopeMappingsRole-dummy";

      beforeEach(async () => {
        const { id } = await kcAdminClient.clients.create({
          clientId: dummyClientId,
        });
        currentClient = (await kcAdminClient.clients.findOne({
          id,
        }))!;

        await kcAdminClient.clients.createRole({
          id,
          name: dummyRoleName,
        });
      });

      afterEach(async () => {
        const { id } = currentClient;
        await kcAdminClient.clients.delRole({
          id: id!,
          roleName: dummyRoleName,
        });
        await kcAdminClient.clients.del({ id: id! });
      });

      it("add scope mappings", async () => {
        const { id } = currentClientScope;
        const { id: clientUniqueId } = currentClient;

        const availableRoles =
          await kcAdminClient.clientScopes.listAvailableClientScopeMappings({
            id: id!,
            client: clientUniqueId!,
          });

        const filteredRoles = availableRoles.filter((role) => !role.composite);

        await kcAdminClient.clientScopes.addClientScopeMappings(
          {
            id: id!,
            client: clientUniqueId!,
          },
          filteredRoles,
        );

        const roles = await kcAdminClient.clientScopes.listClientScopeMappings({
          id: id!,
          client: clientUniqueId!,
        });

        expect(roles).to.be.ok;
        expect(roles).to.be.eql(filteredRoles);
      });

      it("list scope mappings", async () => {
        const { id } = currentClientScope;
        const { id: clientUniqueId } = currentClient;
        const roles = await kcAdminClient.clientScopes.listClientScopeMappings({
          id: id!,
          client: clientUniqueId!,
        });
        expect(roles).to.be.ok;
      });

      it("list available scope mappings", async () => {
        const { id } = currentClientScope;
        const { id: clientUniqueId } = currentClient;
        const roles =
          await kcAdminClient.clientScopes.listAvailableClientScopeMappings({
            id: id!,
            client: clientUniqueId!,
          });
        expect(roles).to.be.ok;
      });

      it("list composite scope mappings", async () => {
        const { id } = currentClientScope;
        const { id: clientUniqueId } = currentClient;
        const roles =
          await kcAdminClient.clientScopes.listCompositeClientScopeMappings({
            id: id!,
            client: clientUniqueId!,
          });
        expect(roles).to.be.ok;
      });

      it("delete scope mappings", async () => {
        const { id } = currentClientScope;
        const { id: clientUniqueId } = currentClient;

        const rolesBefore =
          await kcAdminClient.clientScopes.listClientScopeMappings({
            id: id!,
            client: clientUniqueId!,
          });

        await kcAdminClient.clientScopes.delClientScopeMappings(
          {
            id: id!,
            client: clientUniqueId!,
          },
          rolesBefore,
        );

        const rolesAfter =
          await kcAdminClient.clientScopes.listClientScopeMappings({
            id: id!,
            client: clientUniqueId!,
          });

        expect(rolesAfter).to.be.ok;
        expect(rolesAfter).to.eql([]);
      });
    });

    describe("realm", () => {
      const dummyRoleName = "realmScopeMappingsRole-dummy";

      beforeEach(async () => {
        await kcAdminClient.roles.create({
          name: dummyRoleName,
        });
      });

      afterEach(async () => {
        try {
          await kcAdminClient.roles.delByName({
            name: dummyRoleName,
          });
        } catch {
          // ignore
        }
      });

      it("add scope mappings", async () => {
        const { id } = currentClientScope;

        const availableRoles =
          await kcAdminClient.clientScopes.listAvailableRealmScopeMappings({
            id: id!,
          });

        const filteredRoles = availableRoles.filter((role) => !role.composite);

        await kcAdminClient.clientScopes.addRealmScopeMappings(
          { id: id! },
          filteredRoles,
        );

        const roles = await kcAdminClient.clientScopes.listRealmScopeMappings({
          id: id!,
        });

        expect(roles).to.be.ok;
        expect(roles).to.include.deep.members(filteredRoles);
      });

      it("list scope mappings", async () => {
        const { id } = currentClientScope;
        const roles = await kcAdminClient.clientScopes.listRealmScopeMappings({
          id: id!,
        });
        expect(roles).to.be.ok;
      });

      it("list available scope mappings", async () => {
        const { id } = currentClientScope;
        const roles =
          await kcAdminClient.clientScopes.listAvailableRealmScopeMappings({
            id: id!,
          });
        expect(roles).to.be.ok;
      });

      it("list composite scope mappings", async () => {
        const { id } = currentClientScope;
        const roles =
          await kcAdminClient.clientScopes.listCompositeRealmScopeMappings({
            id: id!,
          });
        expect(roles).to.be.ok;
      });

      it("delete scope mappings", async () => {
        const { id } = currentClientScope;

        const rolesBefore =
          await kcAdminClient.clientScopes.listRealmScopeMappings({
            id: id!,
          });

        await kcAdminClient.clientScopes.delRealmScopeMappings(
          {
            id: id!,
          },
          rolesBefore,
        );

        const rolesAfter =
          await kcAdminClient.clientScopes.listRealmScopeMappings({
            id: id!,
          });

        expect(rolesAfter).to.be.ok;
        expect(rolesAfter).to.eql([]);
      });
    });
  });
});
