/*
 * 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 static org.apache.geode.management.internal.i18n.CliStrings.GROUP;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Properties;
import java.util.Set;

import joptsimple.internal.Strings;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.logging.log4j.Logger;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;

import org.apache.geode.distributed.ConfigurationPersistenceService;
import org.apache.geode.distributed.internal.InternalConfigurationPersistenceService;
import org.apache.geode.logging.internal.log4j.api.LogService;
import org.apache.geode.management.cli.CliMetaData;
import org.apache.geode.management.cli.GfshCommand;
import org.apache.geode.management.cli.Result;
import org.apache.geode.management.internal.cli.AbstractCliAroundInterceptor;
import org.apache.geode.management.internal.cli.GfshParseResult;
import org.apache.geode.management.internal.cli.result.model.DataResultModel;
import org.apache.geode.management.internal.cli.result.model.FileResultModel;
import org.apache.geode.management.internal.cli.result.model.InfoResultModel;
import org.apache.geode.management.internal.cli.result.model.ResultModel;
import org.apache.geode.management.internal.configuration.domain.Configuration;
import org.apache.geode.management.internal.configuration.utils.ZipUtils;
import org.apache.geode.management.internal.i18n.CliStrings;
import org.apache.geode.management.internal.security.ResourceOperation;
import org.apache.geode.security.ResourcePermission.Operation;
import org.apache.geode.security.ResourcePermission.Resource;

/**
 * Commands for the cluster configuration
 */
@SuppressWarnings("unused")
public class ExportClusterConfigurationCommand extends GfshCommand {
  private static final Logger logger = LogService.getLogger();
  public static final String XML_FILE = "xml-file";

  @ShellMethod(value = CliStrings.EXPORT_SHARED_CONFIG__HELP,
      key = {CliStrings.EXPORT_SHARED_CONFIG})
  @CliMetaData(
      interceptor = "org.apache.geode.management.internal.cli.commands.ExportClusterConfigurationCommand$ExportInterceptor",
      relatedTopic = {CliStrings.TOPIC_GEODE_CONFIG})
  @ResourceOperation(resource = Resource.CLUSTER, operation = Operation.READ)
  public ResultModel exportSharedConfig(
      @ShellOption(value = GROUP,
          defaultValue = ConfigurationPersistenceService.CLUSTER_CONFIG) String group,
      @ShellOption(value = XML_FILE) String xmlFile,
      @ShellOption(value = CliStrings.EXPORT_SHARED_CONFIG__FILE,
          help = CliStrings.EXPORT_SHARED_CONFIG__FILE__HELP) String zipFileName)
      throws IOException {

    if (!isSharedConfigurationRunning()) {
      return ResultModel.createError("Cluster configuration service is not running.");
    }

    ResultModel result = new ResultModel();
    InternalConfigurationPersistenceService configPersistenceService =
        getConfigurationPersistenceService();
    if (zipFileName != null) {
      Path tempDir = Files.createTempDirectory("temp");
      Path exportedDir = tempDir.resolve("cluster_config");
      Path zipFile = tempDir.resolve(FilenameUtils.getName(zipFileName));
      try {
        for (Configuration config : configPersistenceService.getEntireConfiguration().values()) {
          configPersistenceService.writeConfigToFile(config, exportedDir.toFile());
        }
        ZipUtils.zipDirectory(exportedDir, zipFile);
        result.addFile(zipFile.toFile(), FileResultModel.FILE_TYPE_BINARY);
      } catch (Exception e) {
        logger.error("unable to export configuration.", e);
      } finally {
        FileUtils.deleteQuietly(tempDir.toFile());
      }
    } else {
      Configuration configuration = configPersistenceService.getConfiguration(group);
      if (configuration == null) {
        return ResultModel.createError("No cluster configuration for '" + group + "'.");
      }

      String cacheXmlContent = configuration.getCacheXmlContent();
      if (cacheXmlContent != null) {
        InfoResultModel xmlSection = result.addInfo("xml");
        xmlSection.setHeader(configuration.getCacheXmlFileName() + ": ");
        xmlSection.addLine(cacheXmlContent);
      }

      Properties gemfireProperties = configuration.getGemfireProperties();
      if (gemfireProperties.size() > 0) {
        DataResultModel propertySection = result.addData("properties");
        propertySection.setHeader("Properties: ");
        propertySection.addData(gemfireProperties);
      }

      Set<String> jarNames = configuration.getJarNames();
      if (jarNames.size() > 0) {
        InfoResultModel jarSection = result.addInfo("jars");
        jarSection.setHeader("Jars: ");
        jarSection.addLine(Strings.join(jarNames, ", "));
      }
    }

    return result;
  }

  /**
   * Interceptor used by gfsh to intercept execution of export shared config command at "shell".
   */
  public static class ExportInterceptor extends AbstractCliAroundInterceptor {
    private String saveDirString;
    private static final Logger logger = LogService.getLogger();

    @Override
    public ResultModel preExecution(GfshParseResult parseResult) {
      String zip = parseResult.getParamValueAsString(CliStrings.EXPORT_SHARED_CONFIG__FILE);
      String xmlFile = parseResult.getParamValueAsString(XML_FILE);
      String group = parseResult.getParamValueAsString(GROUP);

      if (group != null && group.contains(",")) {
        return ResultModel.createError("Only a single group name is supported.");
      }

      if (zip != null && xmlFile != null) {
        return ResultModel.createError("Zip file and xml File can't both be specified.");
      }

      if (zip != null && !group.equals(ConfigurationPersistenceService.CLUSTER_CONFIG)) {
        return ResultModel.createError("zip file can not be exported with a specific group.");
      }

      if (zip != null && !zip.endsWith(".zip")) {
        return ResultModel
            .createError(CliStrings.format(CliStrings.INVALID_FILE_EXTENSION, ".zip"));
      }

      String exportedFile = (zip != null) ? zip : xmlFile;
      if (exportedFile != null) {
        // make sure the file does not exist so that we don't overwrite some existing file
        File file = new File(exportedFile).getAbsoluteFile();
        if (file.exists()) {
          String message = file.getAbsolutePath() + " already exists. Overwrite it? ";
          if (readYesNo(message, Response.YES) == Response.NO) {
            return ResultModel.createError("Aborted. " + exportedFile + "already exists.");
          }
        }
      }

      return ResultModel.createInfo("");
    }

    @Override
    public ResultModel postExecution(GfshParseResult parseResult, ResultModel result, Path tempFile)
        throws IOException {
      if (result.getStatus() == Result.Status.ERROR) {
        return result;
      }
      String xmlFile = parseResult.getParamValueAsString(XML_FILE);
      String zipFile = parseResult.getParamValueAsString(CliStrings.EXPORT_SHARED_CONFIG__FILE);
      String group = parseResult.getParamValueAsString(GROUP);
      // save the result to the file
      if (xmlFile != null) {
        InfoResultModel xmlSection = result.getInfoSection("xml");
        if (xmlSection == null) {
          InfoResultModel info = result.addInfo("info");
          info.addLine(String.format("xml content is empty. %s is not created.", xmlFile));
        } else {
          File file = new File(xmlFile).getAbsoluteFile();
          FileUtils.write(file, Strings.join(xmlSection.getContent(), System.lineSeparator()),
              Charset.defaultCharset());
          xmlSection.removeLine(0);
          xmlSection.addLine("xml content exported to " + file.getAbsolutePath());
        }
      } else if (zipFile != null) {
        // delete the existing file since at this point, user is OK to replace the old zip.
        File file = new File(zipFile).getAbsoluteFile();
        if (file.exists()) {
          FileUtils.deleteQuietly(file);
        }
        result.saveFileTo(file.getParentFile());
      }
      return result;
    }
  }
}
