Email.java

package com.albertsilva.dev.asjcatalog.domain.recovery;

import java.io.Serializable;
import java.time.Instant;

import com.albertsilva.dev.asjcatalog.domain.recovery.enums.EmailStatus;
import com.albertsilva.dev.asjcatalog.dto.email.request.EmailRegisterRequest;

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.Table;

/**
 * Registro persistido de um e-mail que o sistema enviou (ou tentou enviar).
 *
 * <p>
 * A entidade guarda apenas os dados do registro: remetente, destinatário,
 * conteúdo, data de criação e {@link EmailStatus}. Ela não realiza o envio, que
 * é responsabilidade de outra camada, e não possui relacionamento JPA com
 * outras entidades (o destinatário é guardado como texto, sem vínculo com
 * {@code User}).
 * </p>
 *
 * <p>
 * <b>Regras e invariantes:</b>
 * </p>
 * <ul>
 * <li>Remetente, destinatário, conteúdo, data de criação e status são
 * obrigatórios no mapeamento; a entidade não valida o formato dos endereços
 * (isso é feito no DTO {@code EmailRegisterRequest})</li>
 * <li>Um registro criado por {@link #Email(EmailRegisterRequest)} nasce com
 * {@code createdAt} igual ao instante atual e status
 * {@link EmailStatus#PENDING}</li>
 * <li>Não há callbacks JPA nem controle de transição de status: qualquer
 * mudança depende de chamada explícita a {@link #setStatus(EmailStatus)}</li>
 * <li>A identidade é definida somente pelo {@code id} (ver
 * {@link #equals(Object)})</li>
 * </ul>
 *
 * <p>
 * <b>Mapeamento:</b>
 * </p>
 * <ul>
 * <li>Tabela: {@code tb_email}; o conteúdo é uma coluna {@code TEXT}</li>
 * <li>O status é persistido pelo nome da constante de {@link EmailStatus}</li>
 * </ul>
 */
@Entity
@Table(name = "tb_email")
public class Email implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  /** Endereço do remetente registrado (obrigatório). */
  @Column(nullable = false)
  private String sender;

  /** Endereço do destinatário registrado (obrigatório). */
  @Column(nullable = false)
  private String recipient;

  /**
   * Texto registrado para o e-mail (obrigatório, coluna {@code TEXT}). A
   * entidade não impõe formato nem tamanho.
   */
  @Column(nullable = false, columnDefinition = "TEXT")
  private String content;

  /**
   * Instante de criação do registro (obrigatório). É definido apenas pelo
   * construtor {@link #Email(EmailRegisterRequest)}; não há callback JPA, então
   * uma instância criada de outro modo precisa recebê-lo por
   * {@link #setCreatedAt(Instant)} antes de ser persistida.
   */
  @Column(nullable = false, columnDefinition = "TIMESTAMP WITHOUT TIME ZONE")
  private Instant createdAt;

  /**
   * Situação do registro (obrigatória). Ver {@link EmailStatus}.
   */
  @Enumerated(EnumType.STRING)
  @Column(nullable = false)
  private EmailStatus status;

  /**
   * Cria um registro vazio, com todos os campos nulos. Construtor sem argumentos
   * exigido pela JPA: {@code createdAt} e {@code status}, que são obrigatórios,
   * não são preenchidos aqui.
   */
  public Email() {
  }

  /**
   * Cria um registro a partir dos dados de um {@link EmailRegisterRequest}.
   *
   * <p>
   * Copia remetente, destinatário e conteúdo; define {@code createdAt} como o
   * instante atual e o status como {@link EmailStatus#PENDING}. Os valores não
   * são validados por este construtor.
   * </p>
   *
   * @param request dados do e-mail a registrar
   * @throws NullPointerException se {@code request} for {@code null}
   */
  public Email(EmailRegisterRequest request) {
    this.sender = request.sender();
    this.recipient = request.recipient();
    this.content = request.content();
    this.createdAt = Instant.now();
    this.status = EmailStatus.PENDING;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getSender() {
    return sender;
  }

  public void setSender(String sender) {
    this.sender = sender;
  }

  public String getRecipient() {
    return recipient;
  }

  public void setRecipient(String recipient) {
    this.recipient = recipient;
  }

  public String getContent() {
    return content;
  }

  public void setContent(String content) {
    this.content = content;
  }

  public Instant getCreatedAt() {
    return createdAt;
  }

  public void setCreatedAt(Instant createdAt) {
    this.createdAt = createdAt;
  }

  public EmailStatus getStatus() {
    return status;
  }

  public void setStatus(EmailStatus status) {
    this.status = status;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    return result;
  }

  /**
   * Compara dois registros pelo identificador ({@code id}).
   *
   * <p>
   * Duas instâncias sem {@code id} (ainda não persistidas) são consideradas
   * iguais entre si, e {@link #hashCode()} depende apenas do {@code id}. A
   * comparação exige a mesma classe exata ({@code getClass()}), e não apenas
   * compatibilidade de tipo.
   * </p>
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    Email other = (Email) obj;
    if (id == null) {
      if (other.id != null)
        return false;
    } else if (!id.equals(other.id))
      return false;
    return true;
  }

}