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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.OneToMany;
import jakarta.persistence.QueryHint;
import jakarta.persistence.Table;
import jakarta.persistence.TableGenerator;

import org.apache.ambari.server.serveraction.kerberos.stageutils.ResolvedKerberosPrincipal;

import com.google.common.base.Objects;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

/**
 * Represents entity to hold principal for keytab.
 * Ideally this entity must have natural PK based on ({@link #keytabPath}, {@link #principalName}, {@link #hostId}),
 * but {@link #hostId} in some cases can be null, and also this entity must be used in service mappings(this can
 * cause dup of {@link #keytabPath}, {@link #principalName} fields in related entities), so we have surrogate {@link #kkpId}
 * id and unique constraint on ({@link #keytabPath}, {@link #principalName}, {@link #hostId}).
 */
@Entity
@Table(name = "kerberos_keytab_principal")
@TableGenerator(name = "kkp_id_generator",
  table = "ambari_sequences",
  pkColumnName = "sequence_name",
  valueColumnName = "sequence_value",
  pkColumnValue = "kkp_id_seq"
)
@NamedQueries({
  @NamedQuery(
    name = "KerberosKeytabPrincipalEntity.findAll",
    query = "SELECT kkpe FROM KerberosKeytabPrincipalEntity kkpe"
  ),
  @NamedQuery(
    name = "KerberosKeytabPrincipalEntity.findByHostAndKeytab",
    query = "SELECT kkpe FROM KerberosKeytabPrincipalEntity kkpe WHERE kkpe.hostId=:hostId AND kkpe.keytabPath=:keytabPath"
  ),
  @NamedQuery(
    name = "KerberosKeytabPrincipalEntity.findByPrincipal",
    query = "SELECT kkpe FROM KerberosKeytabPrincipalEntity kkpe WHERE kkpe.principalName=:principalName"
  ),
  @NamedQuery(
    name = "KerberosKeytabPrincipalEntity.findByHost",
    query = "SELECT kkpe FROM KerberosKeytabPrincipalEntity kkpe WHERE kkpe.hostId=:hostId"
  ),
  @NamedQuery(
    name = "KerberosKeytabPrincipalEntity.findByPrincipalAndHost",
    query = "SELECT kkpe FROM KerberosKeytabPrincipalEntity kkpe WHERE kkpe.hostId=:hostId AND kkpe.principalName=:principalName",
    hints = {
      @QueryHint(name = "eclipselink.query-results-cache", value = "true"),
      @QueryHint(name = "eclipselink.query-results-cache.size", value = "500") }
  ),
  @NamedQuery(
    name = "KerberosKeytabPrincipalEntity.findByHostKeytabAndPrincipal",
    query = "SELECT kkpe FROM KerberosKeytabPrincipalEntity kkpe WHERE kkpe.hostId=:hostId AND kkpe.keytabPath=:keytabPath AND kkpe.principalName=:principalName"
  ),
  @NamedQuery(
    name = "KerberosKeytabPrincipalEntity.findByKeytabAndPrincipalNullHost",
    query = "SELECT kkpe FROM KerberosKeytabPrincipalEntity kkpe WHERE kkpe.principalName=:principalName AND kkpe.keytabPath=:keytabPath AND kkpe.hostId IS NULL"
  )
})
public class KerberosKeytabPrincipalEntity {
  @Id
  @GeneratedValue(strategy = GenerationType.TABLE, generator = "kkp_id_generator")
  @Column(name = "kkp_id")
  private Long kkpId;

  @Column(name = "keytab_path", updatable = false, nullable = false)
  private String keytabPath;

  @Column(name = "principal_name", updatable = false, nullable = false)
  private String principalName;

  @Column(name = "host_id")
  private Long hostId;

  @Column(name = "is_distributed", nullable = false)
  private Integer isDistributed = 0;

  /**
   * The assocaited keytab entity must be fetched {@link FetchType#EAGER} to
   * ensure that it can be discovered when only the principal and host are
   * known.
   */
  @ManyToOne(fetch = FetchType.EAGER)
  @JoinColumn(name = "keytab_path", referencedColumnName = "keytab_path", updatable = false, nullable = false, insertable = false)
  private KerberosKeytabEntity kerberosKeytabEntity;

  @ManyToOne
  @JoinColumn(name = "host_id", referencedColumnName = "host_id", updatable = false, insertable = false)
  private HostEntity hostEntity;

  @ManyToOne
  @JoinColumn(name = "principal_name", referencedColumnName = "principal_name", updatable = false, nullable = false, insertable = false)
  private KerberosPrincipalEntity kerberosPrincipalEntity;

  /**
   * Service mappings must be fetched {@link FetchType#EAGER} since they are
   * required when processing {@link KerberosKeytabPrincipalEntity} instances
   * and turning them into {@link ResolvedKerberosPrincipal}.
   */
  @OneToMany(cascade = CascadeType.ALL, mappedBy = "kerberosKeytabPrincipalEntity", orphanRemoval = true, fetch = FetchType.EAGER)
  private List<KerberosKeytabServiceMappingEntity> serviceMapping = new ArrayList<>();

  public KerberosKeytabPrincipalEntity() {

  }

  public KerberosKeytabPrincipalEntity(KerberosKeytabEntity kerberosKeytabEntity, HostEntity hostEntity, KerberosPrincipalEntity kerberosPrincipalEntity) {
    setKerberosKeytabEntity(kerberosKeytabEntity);
    setHostEntity(hostEntity);
    setKerberosPrincipalEntity(kerberosPrincipalEntity);
  }

  public Long getKkpId() {
    return kkpId;
  }

  public void setKkpId(Long kkpId) {
    this.kkpId = kkpId;
  }

  public Boolean isDistributed() {
    return isDistributed == 1;
  }

  public void setDistributed(Boolean isDistributed) {
    this.isDistributed = isDistributed ? 1 : 0;
  }

  public KerberosKeytabEntity getKerberosKeytabEntity() {
    return kerberosKeytabEntity;
  }

  public void setKerberosKeytabEntity(KerberosKeytabEntity kke) {
    kerberosKeytabEntity = kke;
    if (kke != null) {
      keytabPath = kke.getKeytabPath();
    }
  }

  public HostEntity getHostEntity() {
    return hostEntity;
  }

  public void setHostEntity(HostEntity hostEntity) {
    this.hostEntity = hostEntity;
    if (hostEntity != null) {
      hostId = hostEntity.getHostId();
    }
  }

  public KerberosPrincipalEntity getKerberosPrincipalEntity() {
    return kerberosPrincipalEntity;
  }

  public void setKerberosPrincipalEntity(KerberosPrincipalEntity kerberosPrincipalEntity) {
    this.kerberosPrincipalEntity = kerberosPrincipalEntity;
    if (kerberosPrincipalEntity != null) {
      principalName = kerberosPrincipalEntity.getPrincipalName();
    }
  }

  public String getKeytabPath() {
    return kerberosKeytabEntity != null ? kerberosKeytabEntity.getKeytabPath() : null;
  }


  public String getPrincipalName() {
    return kerberosPrincipalEntity != null ? kerberosPrincipalEntity.getPrincipalName() : null;
  }

  public Long getHostId() {
    return hostEntity != null ? hostEntity.getHostId() : null;
  }

  public String getHostName() {
    return hostEntity != null ? hostEntity.getHostName() : null;
  }

  public List<KerberosKeytabServiceMappingEntity> getServiceMapping() {
    return serviceMapping;
  }

  public void setServiceMapping(List<KerberosKeytabServiceMappingEntity> serviceMapping) {
    this.serviceMapping = (serviceMapping == null)
        ? Collections.emptyList()
        : new ArrayList<>(serviceMapping);
  }

  public boolean putServiceMapping(String service, String component) {
    if (containsMapping(service, component)) {
      return false;
    } else {
      serviceMapping.add(new KerberosKeytabServiceMappingEntity(this, service, component));
      return true;
    }
  }

  public Multimap<String, String> getServiceMappingAsMultimap() {
    Multimap<String, String> result = ArrayListMultimap.create();
    for (KerberosKeytabServiceMappingEntity mappingEntity : serviceMapping) {
      result.put(mappingEntity.getServiceName(), mappingEntity.getComponentName());
    }
    return result;
  }

  public boolean containsMapping(String serviceName, String componentName) {
    for (KerberosKeytabServiceMappingEntity mappingEntity : serviceMapping) {
      if (Objects.equal(mappingEntity.getComponentName(), componentName)
        && Objects.equal(mappingEntity.getServiceName(), serviceName)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    KerberosKeytabPrincipalEntity that = (KerberosKeytabPrincipalEntity) o;
    return Objects.equal(keytabPath, that.keytabPath) &&
      Objects.equal(principalName, that.principalName) &&
      Objects.equal(hostId, that.hostId);
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(keytabPath, principalName, hostId);
  }
}
