/*
 * 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.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import jakarta.persistence.Basic;
import jakarta.persistence.CascadeType;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.OneToMany;
import jakarta.persistence.PreRemove;
import jakarta.persistence.Table;
import jakarta.persistence.TableGenerator;

import org.apache.ambari.server.state.AlertState;

import com.google.common.collect.ImmutableSet;

/**
 * The {@link AlertTargetEntity} class represents audience that will receive
 * dispatches when an alert is triggered.
 */
@Entity
@Table(name = "alert_target")
@TableGenerator(
    name = "alert_target_id_generator",
    table = "ambari_sequences",
    pkColumnName = "sequence_name",
    valueColumnName = "sequence_value",
    pkColumnValue = "alert_target_id_seq",
    initialValue = 0)
@NamedQueries({
    @NamedQuery(
        name = "AlertTargetEntity.findAll",
        query = "SELECT alertTarget FROM AlertTargetEntity alertTarget"),
    @NamedQuery(
        name = "AlertTargetEntity.findAllGlobal",
        query = "SELECT alertTarget FROM AlertTargetEntity alertTarget WHERE alertTarget.isGlobal = 1"),
    @NamedQuery(
        name = "AlertTargetEntity.findByName",
        query = "SELECT alertTarget FROM AlertTargetEntity alertTarget WHERE alertTarget.targetName = :targetName"),
    @NamedQuery(
        name = "AlertTargetEntity.findByIds",
        query = "SELECT alertTarget FROM AlertTargetEntity alertTarget WHERE alertTarget.targetId IN :targetIds") })
public class AlertTargetEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.TABLE, generator = "alert_target_id_generator")
  @Column(name = "target_id", nullable = false, updatable = false)
  private Long targetId;

  @Column(length = 1024)
  private String description;

  @Column(name = "notification_type", nullable = false, length = 64)
  private String notificationType;

  @Lob
  @Basic
  @Column(length = 32672)
  private String properties;

  @Column(name = "target_name", unique = true, nullable = false, length = 255)
  private String targetName;

  @Column(name = "is_global", nullable = false, length = 1)
  private Short isGlobal = Short.valueOf((short) 0);

  @Column(name = "is_enabled", nullable = false, length = 1)
  private Short isEnabled = Short.valueOf((short) 1);

  /**
   * Bi-directional many-to-many association to {@link AlertGroupEntity}
   */
  @ManyToMany(
      fetch = FetchType.EAGER,
      mappedBy = "alertTargets",
      cascade = { CascadeType.MERGE, CascadeType.REFRESH })
  private Set<AlertGroupEntity> alertGroups;

  /**
   * Gets the alert states that this target will be notified for. If this is
   * either empty or {@code null}, then it is implied that all alert states are
   * of interest to the target. A target without an alert states does not make
   * sense which is why the absence of states implies all states.
   */
  @Enumerated(value = EnumType.STRING)
  @ElementCollection(targetClass = AlertState.class)
  @CollectionTable(name = "alert_target_states", joinColumns = @JoinColumn(name = "target_id"))
  @Column(name = "alert_state")
  private Set<AlertState> alertStates = EnumSet.allOf(AlertState.class);

  /**
   * Bi-directional one-to-many association to {@link AlertNoticeEntity}.
   */
  @OneToMany(cascade = CascadeType.REMOVE, mappedBy = "alertTarget")
  private List<AlertNoticeEntity> alertNotices;

  /**
   * Gets the unique ID of this alert target.
   *
   * @return the ID of the target (never {@code null}).
   */
  public Long getTargetId() {
    return targetId;
  }

  /**
   * Sets the unique ID of this alert target.
   *
   * @param targetId
   *          the ID of the alert target (not {@code null}).
   */
  public void setTargetId(Long targetId) {
    this.targetId = targetId;
  }

  /**
   * Gets the description of this alert target.
   *
   * @return the description or {@code null} if none.
   */
  public String getDescription() {
    return description;
  }

  /**
   * Sets the description for this alert target.
   *
   * @param description
   *          the description or {@code null} for none.
   */
  public void setDescription(String description) {
    this.description = description;
  }

  /**
   * @return
   */
  public String getNotificationType() {
    return notificationType;
  }

  /**
   * @param notificationType
   */
  public void setNotificationType(String notificationType) {
    this.notificationType = notificationType;
  }

  /**
   * @return
   */
  public String getProperties() {
    return properties;
  }

  /**
   * @param properties
   */
  public void setProperties(String properties) {
    this.properties = properties;
  }

  /**
   * Gets the name of this alert target.
   *
   * @return the alert target name (never {@code null}).
   */
  public String getTargetName() {
    return targetName;
  }

  /**
   * Gets whether the alert target is a global target and will receive
   * notifications triggered for any alert group.
   *
   * @return the isGlobal {@code} if the target is global.
   */
  public boolean isGlobal() {
    return isGlobal == 0 ? false : true;
  }

  /**
   * Sets whether the alert target is a global target and will receive
   * notifications triggered for any alert group.
   *
   * @param isGlobal
   *          {@code} if the target is global.
   */
  public void setGlobal(boolean isGlobal) {
    this.isGlobal = isGlobal ? (short) 1 : (short) 0;
  }

  /**
   * Gets whether the alert target is enabled. Targets which are not enabled
   * will not receive notifications.
   *
   * @return the {@code true} if the target is enabled.
   */
  public boolean isEnabled() {
    return isEnabled == 0 ? false : true;
  }

  /**
   * Sets whether the alert target is enabled. Targets which are not enabled
   * will not receive notifications.
   *
   * @param isEnabled
   *          {@code} if the target is enabled.
   */
  public void setEnabled(boolean isEnabled) {
    this.isEnabled = isEnabled ? (short) 1 : (short) 0;
  }

  /**
   * Gets the alert states that will cause a triggered alert to be sent to this
   * target. A target may be associated with a group where an alert has changed
   * state, but if that new state is not of interest to the target, it will not
   * be sent.
   *
   * @return the set of alert states or {@code null} or empty to imply all.
   */
  public Set<AlertState> getAlertStates() {
    return alertStates;
  }

  /**
   * Sets the alert states that will cause a triggered alert to be sent to this
   * target. A target may be associated with a group where an alert has changed
   * state, but if that new state is not of interest to the target, it will not
   * be sent.
   *
   * @param alertStates
   *          the set of alert states or {@code null} or empty to imply all.
   */
  public void setAlertStates(Set<AlertState> alertStates) {
    this.alertStates = alertStates;
  }

  /**
   * Sets the name of this alert target.
   *
   * @param targetName
   *          the name (not {@code null}).
   */
  public void setTargetName(String targetName) {
    this.targetName = targetName;
  }

  /**
   * Gets an immutable set of the alert groups that this target is associated
   * with.
   *
   * @return the groups that will send to this target when an alert in that
   *         group is received, or an empty set for none.
   */
  public Set<AlertGroupEntity> getAlertGroups() {
    if (null == alertGroups) {
      return Collections.emptySet();
    }

    return ImmutableSet.copyOf(alertGroups);
  }

  /**
   * Sets all of the groups that are associated with this target.
   *
   * @param alertGroups
   *          the groups, or {@code null} if there are none.
   */
  public void setAlertGroups(Set<AlertGroupEntity> alertGroups) {
    Set<AlertGroupEntity> groups = getAlertGroups();
    for (AlertGroupEntity group : groups) {
      group.removeAlertTarget(this);
    }

    this.alertGroups = alertGroups;

    if (null != alertGroups) {
      for (AlertGroupEntity group : alertGroups) {
        group.addAlertTarget(this);
      }
    }
  }

  /**
   * Adds the specified alert group to the groups that this target is associated
   * with. This is used to complement the JPA bidirectional association.
   *
   * @param alertGroup
   */
  protected void addAlertGroup(AlertGroupEntity alertGroup) {
    if (null == alertGroups) {
      alertGroups = new HashSet<>();
    }

    alertGroups.add(alertGroup);
  }

  /**
   * Removes the specified alert group to the groups that this target is
   * associated with. This is used to complement the JPA bidirectional
   * association.
   *
   * @param alertGroup
   */
  protected void removeAlertGroup(AlertGroupEntity alertGroup) {
    if (null != alertGroups) {
      alertGroups.remove(alertGroup);
    }
  }

  /**
   * Adds the specified notice to the notices that have been sent out for this
   * target.
   *
   * @param notice
   *          the notice.
   */
  protected void addAlertNotice(AlertNoticeEntity notice) {
    if (null == alertNotices) {
      alertNotices = new ArrayList<>();
    }

    alertNotices.add(notice);
  }

  public List<AlertNoticeEntity> getAlertNotices() {
    return alertNotices;
  }

  public void setAlertNotices(List<AlertNoticeEntity> alertNotices) {
    this.alertNotices = alertNotices;
  }

  /**
   * Called before {@link EntityManager#remove(Object)} for this entity, removes
   * the non-owning relationship between targets and groups.
   */
  @PreRemove
  public void preRemove() {
    Set<AlertGroupEntity> groups = getAlertGroups();
    if (!groups.isEmpty()) {
      for (AlertGroupEntity group : groups) {
        group.removeAlertTarget(this);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean equals(Object object) {
    if (this == object) {
      return true;
    }

    if (object == null || getClass() != object.getClass()) {
      return false;
    }

    AlertTargetEntity that = (AlertTargetEntity) object;

    // use the unique ID if it exists
    if( null != targetId ){
      return Objects.equals(targetId, that.targetId);
    }

    return Objects.equals(targetId, that.targetId) &&
        Objects.equals(targetName, that.targetName) &&
        Objects.equals(notificationType, that.notificationType) &&
        Objects.equals(isEnabled, that.isEnabled) &&
        Objects.equals(description, that.description) &&
        Objects.equals(isGlobal, that.isGlobal);
    }

  /**
   * {@inheritDoc}
   */
  @Override
  public int hashCode() {
    // use the unique ID if it exists
    if (null != targetId) {
      return targetId.hashCode();
    }

    return Objects.hash(targetId, targetName, notificationType, isEnabled, description, isGlobal);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    StringBuilder buffer = new StringBuilder();
    buffer.append(getClass().getSimpleName());
    buffer.append("{");
    buffer.append("id=").append(targetId);
    buffer.append(", name=").append(targetName);
    buffer.append("}");
    return buffer.toString();
  }
}
