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

import java.util.Collection;
import java.util.List;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;

import org.apache.ambari.server.orm.RequiresSession;
import org.apache.ambari.server.orm.entities.HostEntity;
import org.apache.ambari.server.orm.entities.HostVersionEntity;
import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
import org.apache.ambari.server.state.RepositoryVersionState;
import org.apache.ambari.server.state.StackId;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.persist.Transactional;

/**
 * The {@link org.apache.ambari.server.orm.dao.HostVersionDAO} class manages the {@link org.apache.ambari.server.orm.entities.HostVersionEntity}
 * instances associated with a host. Each host can have multiple stack versions in {@link org.apache.ambari.server.state.RepositoryVersionState#INSTALLED}
 * which are installed, exactly one stack version that is either {@link org.apache.ambari.server.state.RepositoryVersionState#CURRENT} or
 * {@link org.apache.ambari.server.state.RepositoryVersionState#INSTALLING}.
 */
@Singleton
public class HostVersionDAO extends CrudDAO<HostVersionEntity, Long> {
  @Inject
  Provider<EntityManager> entityManagerProvider;
  @Inject
  DaoUtils daoUtils;

  /**
   * Constructor.
   */
  public HostVersionDAO() {
    super(HostVersionEntity.class);
  }

  /**
   * Construct a Host Version. Additionally this will update parent connection relations without
   * forcing refresh of parent entity
   * @param entity entity to create
   */
  @Override
  @Transactional
  public void create(HostVersionEntity entity) throws IllegalArgumentException{
    // check if repository version is not missing, to avoid NPE
    if (entity.getRepositoryVersion() == null) {
      throw new IllegalArgumentException("RepositoryVersion argument is not set for the entity");
    }

    super.create(entity);
    entity.getRepositoryVersion().updateHostVersionEntityRelation(entity);
  }

  /**
   * Retrieve all of the host versions for the given cluster name, stack name,
   * and stack version.
   *
   * @param clusterName
   *          Cluster name
   * @param stackId
   *          Stack (e.g., HDP-2.2)
   * @param version
   *          Stack version (e.g., 2.2.0.1-995)
   * @return Return all of the host versions that match the criteria.
   */
  @RequiresSession
  public List<HostVersionEntity> findByClusterStackAndVersion(
      String clusterName, StackId stackId, String version) {
    final TypedQuery<HostVersionEntity> query = entityManagerProvider.get().createNamedQuery("hostVersionByClusterAndStackAndVersion", HostVersionEntity.class);
    query.setParameter("clusterName", clusterName);
    query.setParameter("stackName", stackId.getStackName());
    query.setParameter("stackVersion", stackId.getStackVersion());
    query.setParameter("version", version);

    return daoUtils.selectList(query);
  }

  /**
   * Retrieve all of the host versions for the given host name across all clusters.
   *
   * @param hostName FQDN of host
   * @return Return all of the host versions that match the criteria.
   */
  @RequiresSession
  public List<HostVersionEntity> findByHost(String hostName) {
    final TypedQuery<HostVersionEntity> query = entityManagerProvider.get()
        .createNamedQuery("hostVersionByHostname", HostVersionEntity.class);
    query.setParameter("hostName", hostName);

    return daoUtils.selectList(query);
  }

  /**
   * Retrieve all of the host versions for the given cluster name and host name.
   *
   * @param clusterName Cluster name
   * @param hostName FQDN of host
   * @return Return all of the host versions that match the criteria.
   */
  @RequiresSession
  public List<HostVersionEntity> findByClusterAndHost(String  clusterName, String hostName) {
    final TypedQuery<HostVersionEntity> query = entityManagerProvider.get()
        .createNamedQuery("hostVersionByClusterAndHostname", HostVersionEntity.class);
    query.setParameter("clusterName", clusterName);
    query.setParameter("hostName", hostName);

    return daoUtils.selectList(query);
  }

  /**
   * Retrieve all of the cluster versions for the given cluster name.
   *
   * @param clusterName Cluster name
   * @return Return all of the host versions that match the criteria.
   */
  @RequiresSession
  public List<HostVersionEntity> findByCluster(String  clusterName) {
    final TypedQuery<HostVersionEntity> query = entityManagerProvider.get()
        .createNamedQuery("findByCluster", HostVersionEntity.class);
    query.setParameter("clusterName", clusterName);

    return daoUtils.selectList(query);
  }

  /**
   * Retrieve all of the host versions for the given cluster name, and state. <br/>
   * @param clusterName Cluster name
   * @param state repository version state
   * @return Return all of the host versions that match the criteria.
   */
  @RequiresSession
  public List<HostVersionEntity> findByClusterAndState(String clusterName, RepositoryVersionState state) {
    final TypedQuery<HostVersionEntity> query = entityManagerProvider.get()
        .createNamedQuery("findByClusterAndState", HostVersionEntity.class);
    query.setParameter("clusterName", clusterName);
    query.setParameter("state", state);

    return daoUtils.selectList(query);  
  }
  
  /**
   * Retrieve all of the host versions for the given cluster name, host name, and state. <br/>
   * @param clusterName Cluster name
   * @param hostName FQDN of host
   * @param state repository version state
   * @return Return all of the host versions that match the criteria.
   */
  @RequiresSession
  public List<HostVersionEntity> findByClusterHostAndState(String  clusterName, String hostName, RepositoryVersionState state) {
    final TypedQuery<HostVersionEntity> query = entityManagerProvider.get()
        .createNamedQuery("hostVersionByClusterHostnameAndState", HostVersionEntity.class);
    query.setParameter("clusterName", clusterName);
    query.setParameter("hostName", hostName);
    query.setParameter("state", state);

    return daoUtils.selectList(query);
  }

  /**
   * Retrieve the single host version for the given cluster, stack name, stack
   * version, and host name. <br/>
   * This query is slow and not suitable for frequent use.
   *
   * @param clusterName
   *          Cluster name
   * @param stackId
   *          Stack ID (e.g., HDP-2.2)
   * @param version
   *          Stack version (e.g., 2.2.0.1-995)
   * @param hostName
   *          FQDN of host
   * @return Returns the single host version that matches the criteria.
   */
  @RequiresSession
  public HostVersionEntity findByClusterStackVersionAndHost(String clusterName,
      StackId stackId, String version, String hostName) {

    final TypedQuery<HostVersionEntity> query = entityManagerProvider.get()
        .createNamedQuery("hostVersionByClusterStackVersionAndHostname", HostVersionEntity.class);
    query.setParameter("clusterName", clusterName);
    query.setParameter("stackName", stackId.getStackName());
    query.setParameter("stackVersion", stackId.getStackVersion());
    query.setParameter("version", version);
    query.setParameter("hostName", hostName);

    return daoUtils.selectSingle(query);
  }

  /**
   * Gets all host version entities assocaited with the specified cluster and
   * repository.
   *
   * @param clusterId
   *          the cluster ID.
   * @param repositoryVersion
   *          the repository (not {@code null}).
   * @return the host versions.
   */
  @RequiresSession
  public List<HostVersionEntity> findHostVersionByClusterAndRepository(long clusterId,
      RepositoryVersionEntity repositoryVersion) {
    TypedQuery<HostVersionEntity> query = entityManagerProvider.get().createNamedQuery(
        "findHostVersionByClusterAndRepository", HostVersionEntity.class);

    query.setParameter("clusterId", clusterId);
    query.setParameter("repositoryVersion", repositoryVersion);

    return daoUtils.selectList(query);
  }

  /**
   * Gets all host version entities that are of the given states
   *
   * @param repositoryVersion
   *          the repository (not {@code null})
   * @param states
   *          the states
   * @return the host versions
   */
  @RequiresSession
  public List<HostVersionEntity> findByRepositoryAndStates(RepositoryVersionEntity repositoryVersion,
      Collection<RepositoryVersionState> states) {

    TypedQuery<HostVersionEntity> query = entityManagerProvider.get().createNamedQuery(
        "hostVersionByRepositoryAndStates", HostVersionEntity.class);

    query.setParameter("repositoryVersion", repositoryVersion);
    query.setParameter("states", states);

    return daoUtils.selectList(query);
  }

  /**
   * Gets the {@link HostVersionEntity} associted with the specified host and
   * repository.
   *
   * @param host
   * @param repositoryVersion
   * @return
   */
  @RequiresSession
  public HostVersionEntity findHostVersionByHostAndRepository(HostEntity host,
      RepositoryVersionEntity repositoryVersion) {
    TypedQuery<HostVersionEntity> query = entityManagerProvider.get().createNamedQuery(
        "findByHostAndRepository", HostVersionEntity.class);

    query.setParameter("host", host);
    query.setParameter("repositoryVersion", repositoryVersion);

    return daoUtils.selectOne(query);
  }

  @Transactional
  public void removeByHostName(String hostName) {
    Collection<HostVersionEntity> hostVersions = findByHost(hostName);
    this.remove(hostVersions);
  }

}
