/*
 *
 *  * 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.ambari.server.serveraction.upgrades;

import static java.util.stream.Collectors.toList;
import static org.apache.commons.collections4.CollectionUtils.union;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Predicate;

import org.apache.ambari.annotations.Experimental;
import org.apache.ambari.annotations.ExperimentalFeature;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.actionmanager.HostRoleStatus;
import org.apache.ambari.server.agent.CommandReport;
import org.apache.ambari.server.controller.internal.DeleteHostComponentStatusMetaData;
import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
import org.apache.ambari.server.orm.entities.UpgradeEntity;
import org.apache.ambari.server.orm.entities.UpgradeHistoryEntity;
import org.apache.ambari.server.stack.upgrade.orchestrate.UpgradeContext;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.ServiceComponent;
import org.apache.ambari.server.state.ServiceComponentSupport;
import org.apache.ambari.server.topology.STOMPComponentsDeleteHandler;
import org.apache.commons.lang.StringUtils;

import com.google.inject.Inject;

/**
 * Upgrade Server Action that deletes the components and services which are no
 * longer supported in the target stack. The deletable component or service
 * should be in deletable state (stopped) before executing this.
 * <p/>
 * This will orphan Kerberos keytabs and identities that belonged to the removed
 * services and components. Since removing these automatically is a new and
 * untested feature, its best to leave this type of cleanup to a future
 * implementation.
 */
@Experimental(
    feature = ExperimentalFeature.ORPHAN_KERBEROS_IDENTITY_REMOVAL,
    comment = "Not removing identities yet")
public class DeleteUnsupportedServicesAndComponents extends AbstractUpgradeServerAction {
  @Inject
  private ServiceComponentSupport serviceComponentSupport;

  @Inject
  private STOMPComponentsDeleteHandler STOMPComponentsDeleteHandler;

  @Override
  public CommandReport execute(ConcurrentMap<String, Object> requestSharedDataContext) throws AmbariException, InterruptedException {
    Cluster cluster = getClusters().getCluster(getExecutionCommand().getClusterName());
    if (cluster.getUpgradeInProgress().isDowngradeAllowed()) {
      throw new AmbariException(this.getClass() + " should not be used in upgrade packs with downgrade support");
    }
    UpgradeContext upgradeContext = getUpgradeContext(cluster);
    Set<String> removedComponents = deleteUnsupportedComponents(cluster, upgradeContext.getRepositoryVersion());
    Set<String> removedServices = deleteUnsupportedServices(cluster, upgradeContext.getRepositoryVersion());
    return createCommandReport(0, HostRoleStatus.COMPLETED, "{}",
      "Removed services: " + StringUtils.join(union(removedComponents, removedServices), ", "), "");
  }

  private Set<String> deleteUnsupportedServices(Cluster cluster, RepositoryVersionEntity repoVersion) throws AmbariException {
    Set<String> servicesToBeRemoved = serviceComponentSupport.unsupportedServices(cluster, repoVersion.getStackName(), repoVersion.getStackVersion());
    for (String serviceName : servicesToBeRemoved) {
      DeleteHostComponentStatusMetaData deleteMetaData = new DeleteHostComponentStatusMetaData();
      cluster.deleteService(serviceName, deleteMetaData);
      STOMPComponentsDeleteHandler.processDeleteByMetaDataException(deleteMetaData);
      STOMPComponentsDeleteHandler.processDeleteByMetaData(deleteMetaData);
      deleteUpgradeHistory(cluster, history -> serviceName.equals(history.getServiceName()));
    }
    return servicesToBeRemoved;
  }

  private Set<String> deleteUnsupportedComponents(Cluster cluster, RepositoryVersionEntity repoVersion) throws AmbariException {
    Set<String> deletedComponents = new HashSet<>();
    for (ServiceComponent component : serviceComponentSupport.unsupportedComponents(cluster, repoVersion.getStackName(), repoVersion.getStackVersion())) {
      DeleteHostComponentStatusMetaData deleteMetaData = new DeleteHostComponentStatusMetaData();
      cluster.getService(component.getServiceName()).deleteServiceComponent(component.getName(), deleteMetaData);
      STOMPComponentsDeleteHandler.processDeleteByMetaDataException(deleteMetaData);
      STOMPComponentsDeleteHandler.processDeleteByMetaData(deleteMetaData);
      deleteUpgradeHistory(cluster, history -> component.getName().equals(history.getComponentName()));
      deletedComponents.add(component.getName());
    }
    return deletedComponents;
  }

  private void deleteUpgradeHistory(Cluster cluster, Predicate<UpgradeHistoryEntity> predicate) {
    UpgradeEntity upgradeInProgress = cluster.getUpgradeInProgress();
    List<UpgradeHistoryEntity> removed = upgradeInProgress.getHistory().stream()
      .filter(each -> each != null && predicate.test(each))
      .collect(toList());
    upgradeInProgress.removeHistories(removed);
    m_upgradeDAO.merge(upgradeInProgress);
  }
}
