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

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;
import jakarta.persistence.TableGenerator;

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

/**
 * The {@link AlertHistoryEntity} class is an instance of an alert state change
 * that was received. Instances are only stored in the history if there is a
 * state change event. Subsequent alerts that are received for the same state
 * update the timestamps in {@link AlertNoticeEntity} but do not receive a new
 * {@link AlertHistoryEntity}.
 */
@Entity
@Table(name = "alert_history")
@TableGenerator(name = "alert_history_id_generator", table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_value", pkColumnValue = "alert_history_id_seq", initialValue = 0)
@NamedQueries({
  @NamedQuery(name = "AlertHistoryEntity.findAll", query = "SELECT alertHistory FROM AlertHistoryEntity alertHistory"),
  @NamedQuery(name = "AlertHistoryEntity.findAllInCluster", query = "SELECT alertHistory FROM AlertHistoryEntity alertHistory WHERE alertHistory.clusterId = :clusterId"),
  @NamedQuery(name = "AlertHistoryEntity.findAllInClusterWithState", query = "SELECT alertHistory FROM AlertHistoryEntity alertHistory WHERE alertHistory.clusterId = :clusterId AND alertHistory.alertState IN :alertStates"),
  @NamedQuery(name = "AlertHistoryEntity.findAllInClusterBetweenDates", query = "SELECT alertHistory FROM AlertHistoryEntity alertHistory WHERE alertHistory.clusterId = :clusterId AND alertHistory.alertTimestamp BETWEEN :startDate AND :endDate"),
  @NamedQuery(name = "AlertHistoryEntity.findAllInClusterBeforeDate", query = "SELECT alertHistory FROM AlertHistoryEntity alertHistory WHERE alertHistory.clusterId = :clusterId AND alertHistory.alertTimestamp <= :beforeDate"),
  @NamedQuery(name = "AlertHistoryEntity.findAllIdsInClusterBeforeDate", query = "SELECT alertHistory.alertId FROM AlertHistoryEntity alertHistory WHERE alertHistory.clusterId = :clusterId AND alertHistory.alertTimestamp <= :beforeDate"),
  @NamedQuery(name = "AlertHistoryEntity.findAllInClusterAfterDate", query = "SELECT alertHistory FROM AlertHistoryEntity alertHistory WHERE alertHistory.clusterId = :clusterId AND alertHistory.alertTimestamp >= :afterDate"),
  @NamedQuery(name = "AlertHistoryEntity.removeByDefinitionId", query = "DELETE FROM AlertHistoryEntity alertHistory WHERE alertHistory.alertDefinitionId = :definitionId"),
  @NamedQuery(name = "AlertHistoryEntity.removeInClusterBeforeDate", query = "DELETE FROM AlertHistoryEntity alertHistory WHERE alertHistory.clusterId = :clusterId AND alertHistory.alertTimestamp <= :beforeDate"),
  @NamedQuery(name = "AlertHistoryEntity.findHistoryIdsByDefinitionId", query = "SELECT alertHistory.alertId FROM AlertHistoryEntity alertHistory WHERE alertHistory.alertDefinitionId = :definitionId ORDER BY alertHistory.alertId")
})
public class AlertHistoryEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.TABLE, generator = "alert_history_id_generator")
  @Column(name = "alert_id", nullable = false, updatable = false)
  private Long alertId;

  @Column(name = "alert_instance", length = 255)
  private String alertInstance;

  @Column(name = "alert_label", length = 1024)
  private String alertLabel;

  @Enumerated(value = EnumType.STRING)
  @Column(name = "alert_state", nullable = false, length = 255)
  private AlertState alertState;

  @Lob
  @Column(name = "alert_text")
  private String alertText;

  @Column(name = "alert_timestamp", nullable = false)
  private Long alertTimestamp;

  @Column(name = "cluster_id", nullable = false)
  private Long clusterId;

  @Column(name = "component_name", length = 255)
  private String componentName;

  @Column(name = "host_name", length = 255)
  private String hostName;

  @Column(name = "service_name", nullable = false, length = 255)
  private String serviceName;

  /**
   * Unidirectional many-to-one association to {@link AlertDefinitionEntity}
   */
  @ManyToOne
  @JoinColumn(name = "alert_definition_id", nullable = false)
  private AlertDefinitionEntity alertDefinition;

  @Column(name = "alert_definition_id", nullable = false, insertable = false, updatable = false, length = 10)
  private Long alertDefinitionId;

  /**
   * Constructor.
   */
  public AlertHistoryEntity() {
  }

  /**
   * Gets the unique ID for this alert instance.
   *
   * @return the unique ID (never {@code null}).
   */
  public Long getAlertId() {
    return alertId;
  }

  /**
   * Sets the unique ID for this alert instance.
   *
   * @param alertId
   *          the unique ID (not {@code null}).
   */
  public void setAlertId(Long alertId) {
    this.alertId = alertId;
  }

  /**
   * Gets the instance identifier, if any, for this alert instance.
   *
   * @return the instance ID or {@code null} if none.
   */
  public String getAlertInstance() {
    return alertInstance;
  }

  /**
   * Sets the instance identifier, if any, for this alert instance.
   *
   * @param alertInstance
   *          the instance ID or {@code null} if none.
   */
  public void setAlertInstance(String alertInstance) {
    this.alertInstance = alertInstance;
  }

  /**
   * Gets the label for this alert instance. The label is typically an
   * abbreviated form of the alert text.
   *
   * @return the alert instance label or {@code null} if none.
   * @see #getAlertText()
   */
  public String getAlertLabel() {
    return alertLabel;
  }

  /**
   * Sets the label for this alert instance.
   *
   * @param alertLabel
   *          the label or {@code null} if none.
   */
  public void setAlertLabel(String alertLabel) {
    this.alertLabel = alertLabel;
  }

  /**
   * Gets the state of this alert instance.
   *
   * @return the alert state (never {@code null}).
   */
  public AlertState getAlertState() {
    return alertState;
  }

  /**
   * Sets the state of this alert instance.
   *
   * @param alertState
   *          the alert state (not {@code null}).
   */
  public void setAlertState(AlertState alertState) {
    this.alertState = alertState;
  }

  /**
   * Gets the text of the alert instance.
   *
   * @return the text of the alert instance or {@code null} if none.
   */
  public String getAlertText() {
    return alertText;
  }

  /**
   * Sets the text of the alert instance.
   *
   * @param alertText
   *          the text, or {@code null} if none.
   */
  public void setAlertText(String alertText) {
    this.alertText = alertText;
  }

  /**
   * Gets the time that the alert instace was received. This will be the value,
   * in milliseconds, since the UNIX/Java epoch, represented in UTC time.
   *
   * @return the time of the alert instance (never {@code null}).
   */
  public Long getAlertTimestamp() {
    return alertTimestamp;
  }

  /**
   * Sets the time that the alert instace was received. This should be the
   * value, in milliseconds, since the UNIX/Java epoch, represented in UTC time.
   *
   * @param alertTimestamp
   *          the time of the alert instance (not {@code null}).
   */
  public void setAlertTimestamp(Long alertTimestamp) {
    this.alertTimestamp = alertTimestamp;
  }

  /**
   * Gets the ID of the cluster that this alert is associated with.
   *
   * @return the ID of the cluster for the server that this alert is for (never
   *         {@code null}).
   */
  public Long getClusterId() {
    return clusterId;
  }

  /**
   * Sets the ID of the cluster that this alert is associated with.
   *
   * @param clusterId
   *          the ID of the cluster for the server that this alert is for (never
   *          {@code null}).
   */
  public void setClusterId(Long clusterId) {
    this.clusterId = clusterId;
  }

  /**
   * Gets the name of the component, if any, that this alert instance is for.
   * Some alerts, such as those that are scoped for the entire service, do not
   * have component names.
   *
   * @return the name of the component, or {@code null} for none.
   */
  public String getComponentName() {
    return componentName;
  }

  /**
   * Sets the name of the component that this alert instance is associated with.
   * Component names are not required if the alert definition is scoped for a
   * service. If specified, there is always a 1:1 mapping between alert
   * definitions and components.
   *
   * @param componentName
   *          the name of the component, or {@code null} if none.
   */
  public void setComponentName(String componentName) {
    this.componentName = componentName;
  }

  /**
   * Gets the name of the host that the alert is for. Some alerts do not run
   * against hosts, such as aggregate alert definitions, so this may be
   * {@code null}.
   *
   * @return the name of the host or {@code null} if none.
   */
  public String getHostName() {
    return hostName;
  }

  /**
   * Sets the name of the host that the alert is for.
   *
   * @param hostName
   *          the name of the host or {@code null} if none.
   */
  public void setHostName(String hostName) {
    this.hostName = hostName;
  }

  /**
   * Gets the name of the service that the alert is defined for.
   *
   * @return the name of the service (never {@code null}).
   */
  public String getServiceName() {
    return serviceName;
  }

  /**
   * Sets the name of the service that the alert is defined for. Every alert
   * definition is related to exactly 1 service.
   *
   * @param serviceName
   *          the name of the service (not {@code null}).
   */
  public void setServiceName(String serviceName) {
    this.serviceName = serviceName;
  }

  /**
   * Gets the associated alert definition for this alert instance. The alert
   * definition can be used to retrieve global information about an alert such
   * as the interval and the name.
   *
   * @return the alert definition (never {@code null}).
   */
  public AlertDefinitionEntity getAlertDefinition() {
    return alertDefinition;
  }

  /**
   * Sets the associated alert definition for this alert instance.
   *
   * @param alertDefinition
   *          the alert definition (not {@code null}).
   */
  public void setAlertDefinition(AlertDefinitionEntity alertDefinition) {
    this.alertDefinition = alertDefinition;
    alertDefinitionId = alertDefinition.getDefinitionId();
  }

  /**
   * Get parent alert definition id
   * @return definition id
   */
  public Long getAlertDefinitionId() {
    return alertDefinitionId;
  }

  /**
   * Set parent alert definition id
   * @param alertDefinitionId definition id
   */
  public void setAlertDefinitionId(Long alertDefinitionId) {
    this.alertDefinitionId = alertDefinitionId;
  }

  /**
   * Gets the equality to another historical alert entry based on the following criteria:
   * <ul>
   * <li>{@link #alertId}
   * <li>{@link #clusterId}
   * <li>{@link #alertInstance}
   * <li>{@link #alertLabel}
   * <li>{@link #alertState}
   * <li>{@link #alertText}
   * <li>{@link #alertTimestamp}
   * <li>{@link #serviceName}
   * <li>{@link #componentName}
   * <li>{@link #hostName}
   * <li>{@link #alertDefinition}
   * </ul>
   * <p/>
   * However, since we're guaranteed that {@link #alertId} is unique among persisted entities, we
   * can return the hashCode based on this value if it is set.
   * <p/>
   * {@inheritDoc}
   */
  @Override
  public boolean equals(Object object) {
    if (this == object) {
      return true;
    }

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

    AlertHistoryEntity that = (AlertHistoryEntity) object;

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

    return Objects.equals(alertId, that.alertId) &&
        Objects.equals(clusterId, that.clusterId) &&
        Objects.equals(alertInstance, that.alertInstance) &&
        Objects.equals(alertLabel, that.alertLabel) &&
        Objects.equals(alertState, that.alertState) &&
        Objects.equals(alertText, that.alertText) &&
        Objects.equals(alertTimestamp, that.alertTimestamp) &&
        Objects.equals(serviceName, that.serviceName) &&
        Objects.equals(componentName, that.componentName) &&
        Objects.equals(hostName, that.hostName) &&
        Objects.equals(alertDefinition, that.alertDefinition);
  }

  /**
   * Gets a hash to uniquely identify this historical alert instance. Since historical entries
   * have no real uniqueness properties, we need to rely on a combination of the fields of this
   * entity.
   * <p/>
   * However, since we're guaranteed that {@link #alertId} is unique among persisted entities, we
   * can return the hashCode based on this value if it is set.
   * <p/>
   * {@inheritDoc}
   */
  @Override
  public int hashCode() {
    // use the unique ID if it exists
    if( null != alertId ){
      return alertId.hashCode();
    }

    return Objects.hash(alertId, clusterId, alertInstance, alertLabel, alertState, alertText,
        alertTimestamp, serviceName, componentName, hostName, alertDefinition);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    StringBuilder buffer = new StringBuilder();
    buffer.append(getClass().getSimpleName());
    buffer.append("{");
    buffer.append("id=").append(alertId);
    buffer.append(", serviceName=").append(serviceName);
    buffer.append(", componentName=").append(componentName);
    buffer.append(", state=").append(alertState);
    buffer.append(", label=").append(alertLabel);
    buffer.append("}");
    return buffer.toString();
  }

  public int getAlertDefinitionHash() {
    return this.getAlertDefinition().hashCode();
  }
}
