/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.geode.management.internal.cli.commands;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;

import org.apache.geode.cache.configuration.CacheConfig;
import org.apache.geode.cache.configuration.CacheElement;
import org.apache.geode.cache.query.management.configuration.QueryConfigService;
import org.apache.geode.distributed.ConfigurationPersistenceService;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.management.cli.SingleGfshCommand;
import org.apache.geode.management.internal.cli.functions.AlterQueryServiceFunction;
import org.apache.geode.management.internal.cli.result.model.ResultModel;
import org.apache.geode.management.internal.functions.CliFunctionResult;
import org.apache.geode.management.internal.security.ResourceOperation;
import org.apache.geode.security.ResourcePermission;

public class AlterQueryServiceCommand extends SingleGfshCommand {
  static final String COMMAND_NAME = "alter query-service";
  private static final String COMMAND_HELP = "Alter configuration parameters for the query service";
  static final String AUTHORIZER_NAME = "method-authorizer";
  private static final String METHOD_AUTHORIZER_NAME_HELP =
      "The name of the class to be used for OQL method authorization";
  static final String AUTHORIZER_PARAMETERS = "authorizer-parameters";
  private static final String AUTHORIZER_PARAMETERS_HELP =
      "A semicolon separated list of all parameter values for the specified method authorizer.";
  static final String FORCE_UPDATE = "force-update";
  private static final String FORCE_UPDATE_HELP =
      "Flag to indicate whether to forcibly update the currently configured authorizer, even when there are continuous queries registered (use with caution)";
  static final String NO_MEMBERS_FOUND_MESSAGE = "No members found.";
  public static final String PARTIAL_FAILURE_MESSAGE =
      "In the event of a partial failure of this command, re-running the command or restarting failing members should restore consistency.";
  static final String SPLITTING_REGEX = ";";

  private final AlterQueryServiceFunction alterQueryServiceFunction =
      new AlterQueryServiceFunction();

  @ShellMethod(value = COMMAND_HELP, key = COMMAND_NAME)
  @ResourceOperation(resource = ResourcePermission.Resource.CLUSTER,
      operation = ResourcePermission.Operation.MANAGE)
  public ResultModel execute(
      @ShellOption(value = AUTHORIZER_NAME,
          defaultValue = ShellOption.NULL,
          help = METHOD_AUTHORIZER_NAME_HELP) String methodAuthorizerName,
      @ShellOption(value = AUTHORIZER_PARAMETERS,
          defaultValue = ShellOption.NULL,
          help = AUTHORIZER_PARAMETERS_HELP) String[] authorizerParameters,
      @ShellOption(value = FORCE_UPDATE, help = FORCE_UPDATE_HELP,
          defaultValue = "false") boolean forceUpdate) {

    ResultModel result;
    Set<String> parametersSet = new HashSet<>();
    QueryConfigService queryConfigService = getQueryConfigService();
    if (authorizerParameters != null) {
      parametersSet.addAll(Arrays.asList(authorizerParameters));
    }
    populateMethodAuthorizer(methodAuthorizerName, parametersSet, queryConfigService);

    Set<DistributedMember> targetMembers = findMembers(null, null);
    if (targetMembers.isEmpty()) {
      result = ResultModel.createInfo(NO_MEMBERS_FOUND_MESSAGE);
    } else {
      String footer = null;
      Object[] args = new Object[] {forceUpdate, methodAuthorizerName, parametersSet};
      List<CliFunctionResult> functionResults =
          executeAndGetFunctionResult(alterQueryServiceFunction, args, targetMembers);

      if (functionResults.stream().anyMatch(CliFunctionResult::isSuccessful)
          && functionResults.stream().anyMatch(functionResult -> !functionResult.isSuccessful())) {
        footer = PARTIAL_FAILURE_MESSAGE;
      }

      result = ResultModel.createMemberStatusResult(functionResults, null, footer, false, true);
    }

    result.setConfigObject(queryConfigService);

    return result;
  }

  void populateMethodAuthorizer(String methodAuthorizerName, Set<String> parameterSet,
      QueryConfigService queryConfigService) {
    QueryConfigService.MethodAuthorizer methodAuthorizer =
        new QueryConfigService.MethodAuthorizer();
    methodAuthorizer.setClassName(methodAuthorizerName);

    if (!parameterSet.isEmpty()) {
      List<QueryConfigService.MethodAuthorizer.Parameter> parameterList = new ArrayList<>();

      for (String value : parameterSet) {
        QueryConfigService.MethodAuthorizer.Parameter parameter =
            new QueryConfigService.MethodAuthorizer.Parameter();
        parameter.setParameterValue(value);
        parameterList.add(parameter);
      }

      methodAuthorizer.setParameters(parameterList);
    }

    queryConfigService.setMethodAuthorizer(methodAuthorizer);
  }

  QueryConfigService getQueryConfigService() {
    ConfigurationPersistenceService configService = getConfigurationPersistenceService();

    if (configService != null) {
      CacheConfig cacheConfig = configService.getCacheConfig(null);

      if (cacheConfig != null) {
        QueryConfigService queryConfigService = cacheConfig
            .findCustomCacheElement(QueryConfigService.ELEMENT_ID, QueryConfigService.class);

        if (queryConfigService != null) {
          return queryConfigService;
        }
      }
    }

    return new QueryConfigService();
  }

  @Override
  public boolean updateConfigForGroup(String group, CacheConfig config, Object configObject) {
    if (configObject instanceof QueryConfigService) {
      List<CacheElement> elements = config.getCustomCacheElements();
      elements.removeIf(e -> e instanceof QueryConfigService);
      elements.add((QueryConfigService) configObject);

      return true;
    }

    return false;
  }
}
