/**
   * Copy existing authentication flow under a new name
   *
   * <p>The new name is given as 'newName' attribute of the passed JSON object
   *
   * @param flowAlias Name of the existing authentication flow
   * @param data JSON containing 'newName' attribute
   */
  @Path("/flows/{flowAlias}/copy")
  @POST
  @NoCache
  @Consumes(MediaType.APPLICATION_JSON)
  public Response copy(@PathParam("flowAlias") String flowAlias, Map<String, String> data) {
    auth.requireManage();

    String newName = data.get("newName");
    if (realm.getFlowByAlias(newName) != null) {
      return Response.status(Response.Status.CONFLICT).build();
    }

    AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
    if (flow == null) {
      logger.debug("flow not found: " + flowAlias);
      return Response.status(NOT_FOUND).build();
    }
    AuthenticationFlowModel copy = new AuthenticationFlowModel();
    copy.setAlias(newName);
    copy.setDescription(flow.getDescription());
    copy.setProviderId(flow.getProviderId());
    copy.setBuiltIn(false);
    copy.setTopLevel(flow.isTopLevel());
    copy = realm.addAuthenticationFlow(copy);
    copy(newName, flow, copy);

    data.put("id", copy.getId());
    adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(data).success();

    return Response.status(201).build();
  }
  /**
   * Update authentication executions of a flow
   *
   * @param flowAlias Flow alias
   * @param rep
   */
  @Path("/flows/{flowAlias}/executions")
  @PUT
  @NoCache
  @Consumes(MediaType.APPLICATION_JSON)
  public void updateExecutions(
      @PathParam("flowAlias") String flowAlias, AuthenticationExecutionInfoRepresentation rep) {
    auth.requireManage();

    AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
    if (flow == null) {
      logger.debug("flow not found: " + flowAlias);
      throw new NotFoundException("flow not found");
    }

    AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(rep.getId());
    if (model == null) {
      session.getTransaction().setRollbackOnly();
      throw new NotFoundException("Illegal execution");
    }
    if (!model.getRequirement().name().equals(rep.getRequirement())) {
      model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement()));
      realm.updateAuthenticatorExecution(model);
      adminEvent
          .operation(OperationType.UPDATE)
          .resourcePath(uriInfo)
          .representation(rep)
          .success();
    }
  }
  /**
   * Create a new authentication flow
   *
   * @param flow Authentication flow representation
   * @return
   */
  @Path("/flows")
  @POST
  @NoCache
  @Consumes(MediaType.APPLICATION_JSON)
  public Response createFlow(AuthenticationFlowRepresentation flow) {
    auth.requireManage();

    if (flow.getAlias() == null || flow.getAlias().isEmpty()) {
      return ErrorResponse.exists("Failed to create flow with empty alias name");
    }

    if (realm.getFlowByAlias(flow.getAlias()) != null) {
      return ErrorResponse.exists("Flow " + flow.getAlias() + " already exists");
    }

    AuthenticationFlowModel createdModel =
        realm.addAuthenticationFlow(RepresentationToModel.toModel(flow));

    flow.setId(createdModel.getId());
    adminEvent
        .operation(OperationType.CREATE)
        .resourcePath(uriInfo, createdModel.getId())
        .representation(flow)
        .success();
    return Response.status(201).build();
  }
  /**
   * Add new flow with new execution to existing flow
   *
   * @param flowAlias Alias of parent authentication flow
   * @param data New authentication flow / execution JSON data containing 'alias', 'type',
   *     'provider', and 'description' attributes
   */
  @Path("/flows/{flowAlias}/executions/flow")
  @POST
  @NoCache
  @Consumes(MediaType.APPLICATION_JSON)
  public void addExecutionFlow(@PathParam("flowAlias") String flowAlias, Map<String, String> data) {
    auth.requireManage();

    AuthenticationFlowModel parentFlow = realm.getFlowByAlias(flowAlias);
    if (parentFlow == null) {
      throw new BadRequestException("Parent flow doesn't exists");
    }
    String alias = data.get("alias");
    String type = data.get("type");
    String provider = data.get("provider");
    String description = data.get("description");

    AuthenticationFlowModel newFlow = realm.getFlowByAlias(alias);
    if (newFlow != null) {
      throw new BadRequestException("New flow alias name already exists");
    }
    newFlow = new AuthenticationFlowModel();
    newFlow.setAlias(alias);
    newFlow.setDescription(description);
    newFlow.setProviderId(type);
    newFlow = realm.addAuthenticationFlow(newFlow);
    AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
    execution.setParentFlow(parentFlow.getId());
    execution.setFlowId(newFlow.getId());
    execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
    execution.setAuthenticatorFlow(true);
    execution.setAuthenticator(provider);
    execution.setPriority(getNextPriority(parentFlow));
    execution = realm.addAuthenticatorExecution(execution);

    data.put("id", execution.getId());
    adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(data).success();
  }
  /**
   * Add new authentication execution to a flow
   *
   * @param flowAlias Alias of parent flow
   * @param data New execution JSON data containing 'provider' attribute
   */
  @Path("/flows/{flowAlias}/executions/execution")
  @POST
  @NoCache
  @Consumes(MediaType.APPLICATION_JSON)
  public void addExecution(@PathParam("flowAlias") String flowAlias, Map<String, String> data) {
    auth.requireManage();

    AuthenticationFlowModel parentFlow = realm.getFlowByAlias(flowAlias);
    if (parentFlow == null) {
      throw new BadRequestException("Parent flow doesn't exists");
    }
    if (parentFlow.isBuiltIn()) {
      throw new BadRequestException("It is illegal to add execution to a built in flow");
    }
    String provider = data.get("provider");

    // make sure provider is one of the registered providers
    ProviderFactory f;
    if (parentFlow.getProviderId().equals(AuthenticationFlow.CLIENT_FLOW)) {
      f =
          session
              .getKeycloakSessionFactory()
              .getProviderFactory(ClientAuthenticator.class, provider);
    } else if (parentFlow.getProviderId().equals(AuthenticationFlow.FORM_FLOW)) {
      f = session.getKeycloakSessionFactory().getProviderFactory(FormAction.class, provider);
    } else {
      f = session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, provider);
    }
    if (f == null) {
      throw new BadRequestException("No authentication provider found for id: " + provider);
    }

    AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
    execution.setParentFlow(parentFlow.getId());
    execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
    execution.setAuthenticatorFlow(false);
    execution.setAuthenticator(provider);
    execution.setPriority(getNextPriority(parentFlow));

    execution = realm.addAuthenticatorExecution(execution);

    data.put("id", execution.getId());
    adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(data).success();
  }
  /**
   * Get authentication executions for a flow
   *
   * @param flowAlias Flow alias
   */
  @Path("/flows/{flowAlias}/executions")
  @GET
  @NoCache
  @Produces(MediaType.APPLICATION_JSON)
  public Response getExecutions(@PathParam("flowAlias") String flowAlias) {
    auth.requireView();

    AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
    if (flow == null) {
      logger.debug("flow not found: " + flowAlias);
      return Response.status(NOT_FOUND).build();
    }
    List<AuthenticationExecutionInfoRepresentation> result = new LinkedList<>();

    int level = 0;

    recurseExecutions(flow, result, level);
    return Response.ok(result).build();
  }