/*
 * 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.controller;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import jakarta.persistence.RollbackException;

import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.orm.dao.ExtensionDAO;
import org.apache.ambari.server.orm.dao.ExtensionLinkDAO;
import org.apache.ambari.server.orm.dao.StackDAO;
import org.apache.ambari.server.orm.entities.ExtensionEntity;
import org.apache.ambari.server.orm.entities.ExtensionLinkEntity;
import org.apache.ambari.server.orm.entities.StackEntity;
import org.apache.ambari.server.stack.ExtensionHelper;
import org.apache.ambari.server.stack.StackManager;
import org.apache.ambari.server.state.ExtensionInfo;
import org.apache.ambari.server.state.StackInfo;
import org.apache.ambari.server.state.stack.ExtensionMetainfoXml;
import org.apache.ambari.server.utils.VersionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
public class AmbariManagementHelper {

  private final static Logger LOG =
      LoggerFactory.getLogger(AmbariManagementHelper.class);

  private ExtensionLinkDAO linkDAO;
  private ExtensionDAO extensionDAO;
  private StackDAO stackDAO;

  @Inject
  public AmbariManagementHelper(StackDAO stackDAO, ExtensionDAO extensionDAO, ExtensionLinkDAO linkDAO) {
    this.stackDAO = stackDAO;
    this.extensionDAO = extensionDAO;
    this.linkDAO = linkDAO;
  }

  /**
   * This method will create a link between an extension version and a stack version (Extension Link).
   *
   * An extension version is like a stack version but it contains custom services.  Linking an extension
   * version to the current stack version allows the cluster to install the custom services contained in
   * the extension version.
   */
  public void createExtensionLink(StackManager stackManager, StackInfo stackInfo, ExtensionInfo extensionInfo) throws AmbariException {
    validateCreateExtensionLinkRequest(stackInfo, extensionInfo);
    ExtensionHelper.validateCreateLink(stackManager, stackInfo, extensionInfo);
    ExtensionLinkEntity linkEntity = createExtensionLinkEntity(stackInfo, extensionInfo);
    stackManager.linkStackToExtension(stackInfo, extensionInfo);

    try {
      linkDAO.create(linkEntity);
    } catch (RollbackException e) {
      String message = "Unable to create extension link";
      LOG.debug(message, e);
      String errorMessage = message
              + ", stackName=" + stackInfo.getName()
              + ", stackVersion=" + stackInfo.getVersion()
              + ", extensionName=" + extensionInfo.getName()
              + ", extensionVersion=" + extensionInfo.getVersion();
      LOG.warn(errorMessage);
      throw new AmbariException(errorMessage, e);
    }
  }

  /**
   * This method will create a link between an extension version and a stack version (Extension Link).
   *
   * An extension version is like a stack version but it contains custom services.  Linking an extension
   * version to the current stack version allows the cluster to install the custom services contained in
   * the extension version.
   */
  public void createExtensionLinks(StackManager stackManager, List<ExtensionInfo> extensions) throws AmbariException {
    Map<String, List<StackInfo>> stackMap = stackManager.getStacksByName();
    for (List<StackInfo> stacks : stackMap.values()) {
      Collections.sort(stacks);
      Collections.reverse(stacks);
    }

    Collections.sort(extensions);
    Collections.reverse(extensions);
    for (ExtensionInfo extension : extensions) {
      if (extension.isActive() && extension.isAutoLink()) {
        LOG.debug("Autolink - looking for matching stack versions for extension:{}/{} ", extension.getName(), extension.getVersion());
        for (ExtensionMetainfoXml.Stack supportedStack : extension.getStacks()) {
          List<StackInfo> stacks = stackMap.get(supportedStack.getName());
          for (StackInfo stack : stacks) {
            // If the stack version is not currently linked to a version of the extension and it meets the minimum stack version then link them
            if (stack.getExtension(extension.getName()) == null && VersionUtils.compareVersions(stack.getVersion(), supportedStack.getVersion()) > -1) {
              LOG.debug("Autolink - extension: {}/{} stack: {}/{}", extension.getName(), extension.getVersion(),
                       stack.getName(), stack.getVersion());
              createExtensionLink(stackManager, stack, extension);
            }
            else {
              LOG.debug("Autolink - not a match extension: {}/{} stack: {}/{}", extension.getName(), extension.getVersion(),
                       stack.getName(), stack.getVersion());
            }
          }
        }
      }
      else {
        LOG.debug("Autolink - skipping extension: {}/{}.  It is either not active or set to autolink.", extension.getName(), extension.getVersion());
      }
    }
  }

  /**
   * Validates the stackInfo and extensionInfo parameters are valid.
   * If they are then it confirms that the stack and extension are not already linked.
   */
  private void validateCreateExtensionLinkRequest(StackInfo stackInfo, ExtensionInfo extensionInfo) throws AmbariException {
    if (stackInfo == null) {
      throw new IllegalArgumentException("Stack should be provided");
    }
    if (extensionInfo == null) {
      throw new IllegalArgumentException("Extension should be provided");
    }
    if (StringUtils.isBlank(stackInfo.getName())
            || StringUtils.isBlank(stackInfo.getVersion())
            || StringUtils.isBlank(extensionInfo.getName())
            || StringUtils.isBlank(extensionInfo.getVersion())) {

      throw new IllegalArgumentException("Stack name, stack version, extension name and extension version should be provided");
    }

    ExtensionLinkEntity entity = linkDAO.findByStackAndExtension(stackInfo.getName(), stackInfo.getVersion(),
		extensionInfo.getName(), extensionInfo.getVersion());

    if (entity != null) {
      throw new AmbariException("The stack and extension are already linked"
                + ", stackName=" + stackInfo.getName()
                + ", stackVersion=" + stackInfo.getVersion()
                + ", extensionName=" + extensionInfo.getName()
                + ", extensionVersion=" + extensionInfo.getVersion());
    }
  }

  /**
   * Updates the extension version of the currently linked extension to the stack version
   */
  public void updateExtensionLink(StackManager stackManager, ExtensionLinkEntity linkEntity, StackInfo stackInfo,
                                  ExtensionInfo oldExtensionInfo, ExtensionInfo newExtensionInfo) throws AmbariException {
    //validateUpdateExtensionLinkRequest(stackInfo, extensionInfo);
    ExtensionHelper.validateUpdateLink(stackManager, stackInfo, oldExtensionInfo, newExtensionInfo);

    ExtensionEntity extension = extensionDAO.find(newExtensionInfo.getName(), newExtensionInfo.getVersion());
    linkEntity.setExtension(extension);

    try {
      linkEntity = linkDAO.merge(linkEntity);
    } catch (RollbackException e) {
      String message = "Unable to update extension link";
      LOG.debug(message, e);
      String errorMessage = message
              + ", stackName=" + stackInfo.getName()
              + ", stackVersion=" + stackInfo.getVersion()
              + ", extensionName=" + newExtensionInfo.getName()
              + ", extensionVersion=" + newExtensionInfo.getVersion();
      LOG.warn(errorMessage);
      throw new AmbariException(errorMessage, e);
    }
  }

  private ExtensionLinkEntity createExtensionLinkEntity(StackInfo stackInfo, ExtensionInfo extensionInfo) throws AmbariException {
    StackEntity stack = stackDAO.find(stackInfo.getName(), stackInfo.getVersion());
    ExtensionEntity extension = extensionDAO.find(extensionInfo.getName(), extensionInfo.getVersion());

    ExtensionLinkEntity linkEntity = new ExtensionLinkEntity();
    linkEntity.setStack(stack);
    linkEntity.setExtension(extension);
    return linkEntity;
  }

}
