package com.oguerreiro.resilient.domain;

import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.oguerreiro.resilient.activities.period.close.PeriodCloseActivity;
import com.oguerreiro.resilient.activities.period.process.PeriodProcessOutputsActivity;
import com.oguerreiro.resilient.activity.AbstractActivityDomain;
import com.oguerreiro.resilient.activity.Activity;
import com.oguerreiro.resilient.domain.enumeration.PeriodStatus;
import com.oguerreiro.resilient.security.custom.ResilientSecurityResourceConfig;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;

/**
 * <b>Period</b><br>
 * Domain that controls inventory data life cycle. An inventory as several
 * phases:
 * <ul>
 * <li>Gathering data - The first step is to insert the data source for the
 * inventory. This can only be done with the {@link Period#getState()
 * Period.state} in state {@link PeriodStatus#OPENED}.<br>
 * Data can be inserted by 2 ways.
 * <ul>
 * <li>Manually - directly in the APP using the {@link InputData} domain.</li>
 * <li>Import files - using the {@link InputDataUpload} domain.</li>
 * </ul>
 * </li>
 * <li>Process & Calculate Outputs - Once all inventory data is gathered, output
 * must be generated. First the {@link Period} must be
 * {@link PeriodStatus#CLOSED} for data input. To do this, use the following
 * {@link Activity Activities}:
 * <ol>
 * <li>{@link PeriodCloseActivity} - to close the {@link Period} for data
 * input.</li>
 * <li>{@link PeriodProcessOutputsActivity} - to calculate and generate output
 * variables.</li>
 * <ol>
 * </li>
 * </ul>
 * For more info on {@link PeriodStatus states} transitions and {@link Activity
 * activities}, see {@link PeriodStatus}
 */
@Entity
@Table(name = "period")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@ResilientSecurityResourceConfig()
public class Period extends AbstractActivityDomain<Period> implements Serializable {

  private static final long serialVersionUID = 1L;

  /**
   * Name of the period
   */
  @NotNull
  @Column(name = "name", nullable = false)
  private String name;

  /**
   * Description for the period
   */
  @Column(name = "description")
  private String description;

  /**
   * Period start date
   */
  @NotNull
  @Column(name = "begin_date", nullable = false)
  private LocalDate beginDate;

  /**
   * Period end date
   */
  @NotNull
  @Column(name = "end_date", nullable = false)
  private LocalDate endDate;

  /**
   * State of the Period
   */
  @NotNull
  @Enumerated(EnumType.STRING)
  @Column(name = "state", nullable = false)
  private PeriodStatus state;

  /**
   * The creation date (insert). When the data was inserted into the system.
   */
  @NotNull
  @Column(name = "creation_date", nullable = false)
  private Instant creationDate;

  /**
   * The actual username logged into the system, that inserted the data. NOT a
   * reference to the user, but the login username.
   */
  @NotNull
  @Column(name = "creation_username", nullable = false)
  private String creationUsername;

  @OneToMany(fetch = FetchType.LAZY, mappedBy = "period", cascade = CascadeType.ALL, orphanRemoval = true)
  @OrderBy("periodVersion ASC")
  @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
  @JsonIgnoreProperties(value = { "period" }, allowSetters = true)
  private List<PeriodVersion> periodVersions = new ArrayList<>();

  public String getName() {
    return this.name;
  }

  public Period name(String name) {
    this.setName(name);
    return this;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getDescription() {
    return this.description;
  }

  public Period description(String description) {
    this.setDescription(description);
    return this;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public LocalDate getBeginDate() {
    return this.beginDate;
  }

  public Period beginDate(LocalDate beginDate) {
    this.setBeginDate(beginDate);
    return this;
  }

  public void setBeginDate(LocalDate beginDate) {
    this.beginDate = beginDate;
  }

  public LocalDate getEndDate() {
    return this.endDate;
  }

  public Period endDate(LocalDate endDate) {
    this.setEndDate(endDate);
    return this;
  }

  public void setEndDate(LocalDate endDate) {
    this.endDate = endDate;
  }

  public PeriodStatus getState() {
    return this.state;
  }

  public Period state(PeriodStatus state) {
    this.setState(state);
    return this;
  }

  public void setState(PeriodStatus state) {
    this.state = state;
  }

  public Instant getCreationDate() {
    return this.creationDate;
  }

  public Period creationDate(Instant creationDate) {
    this.setCreationDate(creationDate);
    return this;
  }

  public void setCreationDate(Instant creationDate) {
    this.creationDate = creationDate;
  }

  public String getCreationUsername() {
    return this.creationUsername;
  }

  public Period creationUsername(String creationUsername) {
    this.setCreationUsername(creationUsername);
    return this;
  }

  public void setCreationUsername(String creationUsername) {
    this.creationUsername = creationUsername;
  }

  public List<PeriodVersion> getPeriodVersions() {
    return this.periodVersions;
  }

  public void setPeriodVersions(List<PeriodVersion> periodVersions) {
    if (this.periodVersions != null) {
      this.periodVersions.forEach(i -> i.setPeriod(null));
    }
    if (periodVersions != null) {
      periodVersions.forEach(i -> i.setPeriod(this));
    }
    this.periodVersions = periodVersions;
  }

  public Period periodVersions(List<PeriodVersion> periodVersions) {
    this.setPeriodVersions(periodVersions);
    return this;
  }

  public Period addPeriodVersion(PeriodVersion periodVersion) {
    this.periodVersions.add(periodVersion);
    periodVersion.setPeriod(this);
    return this;
  }

  public Period removePeriodVersion(PeriodVersion periodVersion) {
    this.periodVersions.remove(periodVersion);
    periodVersion.setPeriod(null);
    return this;
  }

//@formatter:off
  @Override
  public String toString() {
    return "Period{" + 
              "id=" + getId() + 
              ", name='" + getName() + "'" + 
              ", description='" + getDescription() + "'" + 
              ", beginDate='" + getBeginDate() + "'" + 
              ", endDate='" + getEndDate() + "'" + 
              ", state='" + getState() + "'" + 
              ", creationDate='" + getCreationDate() + "'" + 
              ", creationUsername='" + getCreationUsername() + "'" + 
              ", version=" + getVersion() + 
           "}";
  }
//@formatter:on
}
