package com.oguerreiro.resilient.domain;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.Set;

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

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.oguerreiro.resilient.domain.enumeration.DataSourceType;
import com.oguerreiro.resilient.security.custom.ResilientSecurityResourceConfig;

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

/**
 * Input data Values The values inserted from: - Each Organic Unit - Variable -
 * PeriodVersion
 */
@Entity
@Table(name = "input_data")
@ResilientSecurityResourceConfig()
public class InputData extends AbstractResilientLongIdEntity<InputData> {
  private static final long serialVersionUID = 1L;

  /**
   * The value of the data when its a string.
   */
  @Column(name = "string_value")
  private String stringValue;

  /**
   * The value of the data in the source Unit. This might be different from the
   * Variable baseUnit. @see variableValue and variableUnit
   */
  @Column(name = "source_value", precision = 21, scale = 2, nullable = false)
  private BigDecimal sourceValue;

  /**
   * The value of the data converted to the variable baseUnit.
   */
  @Column(name = "variable_value", precision = 21, scale = 2, nullable = false)
  private BigDecimal variableValue;

  /**
   * The value that the InputData.owner is accounted for. In case a variableValue
   * is distributed to other Organization's
   * (InputData.source==DataSources.DISTRIB) , this will have the value to
   * consider when calculating the final inventory value for the variable
   */
  @Column(name = "imputed_value", precision = 21, scale = 2, nullable = false)
  private BigDecimal imputedValue;

  /**
   * What was the source of this data ?
   */
  @NotNull
  @Enumerated(EnumType.STRING)
  @Column(name = "source_type", nullable = false)
  private DataSourceType sourceType;

  /**
   * The date that this data relates to. Eg. energy consumptium happens every
   * month, this should be the date of invoice [optional]
   */
  @Column(name = "data_date")
  private LocalDate dataDate;

  /**
   * Last change date
   */
  @NotNull
  @Column(name = "change_date", nullable = false)
  private Instant changeDate;

  /**
   * The actual username logged into the system, that last changed the data
   */
  @NotNull
  @Column(name = "change_username", nullable = false)
  private String changeUsername;

  /**
   * Where this data originates from? Where were the data collected ?
   */
  @Column(name = "data_source")
  private String dataSource;

  /**
   * Who collected the data ?
   */
  @Column(name = "data_user")
  private String dataUser;

  /**
   * Comments added by the user
   */
  @Column(name = "data_comments")
  private String dataComments;

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

  /**
   * The variable class code, if selected. Persists the value and NOT a reference
   */
  @Column(name = "variable_class_code")
  private String variableClassCode;

  /**
   * The variable class name, if selected. Persists the value and NOT a reference
   */
  @Column(name = "variable_class_name")
  private String variableClassName;

  @OneToMany(fetch = FetchType.LAZY, mappedBy = "sourceInputData")
  @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
  @JsonIgnoreProperties(value = { "inputData", "variable", "period", "sourceUnit", "unit", "owner", "sourceInputData",
      "sourceInputDataUpload", }, allowSetters = true)
  private Set<InputData> inputData = new HashSet<>();

  @ManyToOne(fetch = FetchType.LAZY)
  @JsonIgnoreProperties(value = { "variableClasses", "inputData", "outputData", "variableUnits", "baseUnit",
      "variableScope", "variableCategory" }, allowSetters = true)
  private Variable variable;

  @ManyToOne(fetch = FetchType.LAZY)
  @JsonIgnoreProperties(value = { "periodVersions" }, allowSetters = true)
  private Period period;

  @ManyToOne(fetch = FetchType.LAZY)
  @JsonIgnoreProperties(value = { "unitType" }, allowSetters = true)
  private Unit sourceUnit;

  @ManyToOne(fetch = FetchType.LAZY)
  @JsonIgnoreProperties(value = { "unitType" }, allowSetters = true)
  private Unit unit;

  @ManyToOne(fetch = FetchType.LAZY)
  @JsonIgnoreProperties(value = { "user", "children", "parent", "organizationType" }, allowSetters = true)
  private Organization owner;

  @ManyToOne(fetch = FetchType.LAZY)
  @JsonIgnoreProperties(value = { "inputData", "variable", "period", "periodVersion", "sourceUnit", "unit", "owner",
      "sourceInputData", "sourceInputDataUpload", }, allowSetters = true)
  private InputData sourceInputData;

  @ManyToOne(fetch = FetchType.LAZY)
  @JsonIgnoreProperties(value = { "inputData", "inputDataUploadLogs", "period", "periodVersion",
      "owner" }, allowSetters = true)
  private InputDataUpload sourceInputDataUpload;

  public String getStringValue() {
    return this.stringValue;
  }

  public InputData stringValue(String stringValue) {
    this.setStringValue(stringValue);
    return this;
  }

  public void setStringValue(String stringValue) {
    this.stringValue = stringValue;
  }

  public BigDecimal getSourceValue() {
    return this.sourceValue;
  }

  public InputData sourceValue(BigDecimal sourceValue) {
    this.setSourceValue(sourceValue);
    return this;
  }

  public void setSourceValue(BigDecimal sourceValue) {
    this.sourceValue = sourceValue;
  }

  public BigDecimal getVariableValue() {
    return this.variableValue;
  }

  public InputData variableValue(BigDecimal variableValue) {
    this.setVariableValue(variableValue);
    return this;
  }

  public void setVariableValue(BigDecimal variableValue) {
    this.variableValue = variableValue;
  }

  public BigDecimal getImputedValue() {
    return this.imputedValue;
  }

  public InputData imputedValue(BigDecimal imputedValue) {
    this.setImputedValue(imputedValue);
    return this;
  }

  public void setImputedValue(BigDecimal imputedValue) {
    this.imputedValue = imputedValue;
  }

  public DataSourceType getSourceType() {
    return this.sourceType;
  }

  public InputData sourceType(DataSourceType sourceType) {
    this.setSourceType(sourceType);
    return this;
  }

  public void setSourceType(DataSourceType sourceType) {
    this.sourceType = sourceType;
  }

  public LocalDate getDataDate() {
    return this.dataDate;
  }

  public InputData dataDate(LocalDate dataDate) {
    this.setDataDate(dataDate);
    return this;
  }

  public void setDataDate(LocalDate dataDate) {
    this.dataDate = dataDate;
  }

  public Instant getChangeDate() {
    return this.changeDate;
  }

  public InputData changeDate(Instant changeDate) {
    this.setChangeDate(changeDate);
    return this;
  }

  public void setChangeDate(Instant changeDate) {
    this.changeDate = changeDate;
  }

  public String getChangeUsername() {
    return this.changeUsername;
  }

  public InputData changeUsername(String changeUsername) {
    this.setChangeUsername(changeUsername);
    return this;
  }

  public void setChangeUsername(String changeUsername) {
    this.changeUsername = changeUsername;
  }

  public String getDataSource() {
    return this.dataSource;
  }

  public InputData dataSource(String dataSource) {
    this.setDataSource(dataSource);
    return this;
  }

  public void setDataSource(String dataSource) {
    this.dataSource = dataSource;
  }

  public String getDataUser() {
    return this.dataUser;
  }

  public InputData dataUser(String dataUser) {
    this.setDataUser(dataUser);
    return this;
  }

  public void setDataUser(String dataUser) {
    this.dataUser = dataUser;
  }

  public String getDataComments() {
    return this.dataComments;
  }

  public InputData dataComments(String dataComments) {
    this.setDataComments(dataComments);
    return this;
  }

  public void setDataComments(String dataComments) {
    this.dataComments = dataComments;
  }

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

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

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

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

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

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

  public String getVariableClassCode() {
    return this.variableClassCode;
  }

  public InputData variableClassCode(String variableClassCode) {
    this.setVariableClassCode(variableClassCode);
    return this;
  }

  public void setVariableClassCode(String variableClassCode) {
    this.variableClassCode = variableClassCode;
  }

  public String getVariableClassName() {
    return this.variableClassName;
  }

  public InputData variableClassName(String variableClassName) {
    this.setVariableClassName(variableClassName);
    return this;
  }

  public void setVariableClassName(String variableClassName) {
    this.variableClassName = variableClassName;
  }

  public Set<InputData> getInputData() {
    return this.inputData;
  }

  public void setInputData(Set<InputData> inputData) {
    if (this.inputData != null) {
      this.inputData.forEach(i -> i.setSourceInputData(null));
    }
    if (inputData != null) {
      inputData.forEach(i -> i.setSourceInputData(this));
    }
    this.inputData = inputData;
  }

  public InputData inputData(Set<InputData> inputData) {
    this.setInputData(inputData);
    return this;
  }

  public InputData addInputData(InputData inputData) {
    this.inputData.add(inputData);
    inputData.setSourceInputData(this);
    return this;
  }

  public InputData removeInputData(InputData inputData) {
    this.inputData.remove(inputData);
    inputData.setSourceInputData(null);
    return this;
  }

  public Variable getVariable() {
    return this.variable;
  }

  public void setVariable(Variable variable) {
    this.variable = variable;
  }

  public InputData variable(Variable variable) {
    this.setVariable(variable);
    return this;
  }

  public Period getPeriod() {
    return this.period;
  }

  public void setPeriod(Period period) {
    this.period = period;
  }

  public InputData period(Period period) {
    this.setPeriod(period);
    return this;
  }

  public Unit getSourceUnit() {
    return this.sourceUnit;
  }

  public void setSourceUnit(Unit unit) {
    this.sourceUnit = unit;
  }

  public InputData sourceUnit(Unit unit) {
    this.setSourceUnit(unit);
    return this;
  }

  public Unit getUnit() {
    return this.unit;
  }

  public void setUnit(Unit unit) {
    this.unit = unit;
  }

  public InputData unit(Unit unit) {
    this.setUnit(unit);
    return this;
  }

  public Organization getOwner() {
    return this.owner;
  }

  public void setOwner(Organization organization) {
    this.owner = organization;
  }

  public InputData owner(Organization organization) {
    this.setOwner(organization);
    return this;
  }

  public InputData getSourceInputData() {
    return this.sourceInputData;
  }

  public void setSourceInputData(InputData inputData) {
    this.sourceInputData = inputData;
  }

  public InputData sourceInputData(InputData inputData) {
    this.setSourceInputData(inputData);
    return this;
  }

  public InputDataUpload getSourceInputDataUpload() {
    return this.sourceInputDataUpload;
  }

  public void setSourceInputDataUpload(InputDataUpload inputDataUpload) {
    this.sourceInputDataUpload = inputDataUpload;
  }

  public InputData sourceInputDataUpload(InputDataUpload inputDataUpload) {
    this.setSourceInputDataUpload(inputDataUpload);
    return this;
  }

//@formatter:off
  @Override
  public String toString() {
    return "InputData{" + 
              "id=" + getId() + 
              ", sourceValue=" + getSourceValue() + 
              ", variableValue=" + getVariableValue() + 
              ", imputedValue=" + getImputedValue() + 
              ", stringValue='" + getStringValue() + "'" + 
              ", sourceType='" + getSourceType() + "'" + 
              ", dataDate='" + getDataDate() + "'" + 
              ", changeDate='" + getChangeDate() + "'" + 
              ", changeUsername='" + getChangeUsername() + "'" + 
              ", dataSource='" + getDataSource() + "'" + 
              ", dataUser='" + getDataUser() + "'" + 
              ", dataComments='" + getDataComments() + "'" + 
              ", creationDate='" + getCreationDate() + "'" + 
              ", creationUsername='" + getCreationUsername() + "'" + 
              ", version=" + getVersion() + 
           "}";
  }
//@formatter:on
}
