package gov.cms.grouper.snf.transfer;

import com.mmm.his.cer.foundation.transfer.Claim;
import gov.cms.grouper.snf.model.Assessment;
import gov.cms.grouper.snf.model.SnfDiagnosisCode;
import gov.cms.grouper.snf.model.enums.AssessmentType;
import gov.cms.grouper.snf.model.enums.SnfError;
import gov.cms.grouper.snf.model.enums.Rai300;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * The SNF record containing MDS inputs and outputs
 */
public class SnfClaim extends Claim implements ISnfClaim {

  private static final long serialVersionUID = -6053717059913154415L;

  // INPUTS
  private int version;
  private List<Assessment> assessments = new ArrayList<>();
  private LocalDate assessmentReferenceDate;
  private Integer aiCode;
  private Integer obra;

  // OUTPUTS
  private String hippsCode;
  private String recalculated_z0100b;

  private final List<String> errors = new ArrayList<>();

  // Miscellaneous
  private String originalRecord;

  @Override
  public int getVersion() {
    return version;
  }

  @Override
  public void setVersion(int version) {
    this.version = version;
  }

  public String getRecalculated_z0100b() {
    return recalculated_z0100b;
  }

  public void setRecalculated_z0100b(String recalculated_z0100b) {
    this.recalculated_z0100b = recalculated_z0100b;
  }

  public SnfDiagnosisCode getPrimaryDiagnosis() {
    return getCodes(SnfDiagnosisCode.class).stream().findFirst().orElse(null);
  }

  public void setPrimaryDiagnosis(SnfDiagnosisCode pdx) {
    if (codes.isEmpty()) {
      codes.add(pdx);
    } else {
      codes.set(0, pdx);
    }
  }

  public void insertPrimaryDiagnosis(SnfDiagnosisCode pdx) {
    codes.add(0, pdx);
  }

  public List<SnfDiagnosisCode> getSecondaryDiagnoses() {
    return getCodes(SnfDiagnosisCode.class).stream().skip(1).collect(Collectors.toList());
  }

  @Override
  public void addAssessment(Assessment assessment) {
    assessments.add(assessment);
  }

  @Override
  public List<Assessment> getAssessments() {
    return assessments;
  }

  public void setAssessments(List<Assessment> assessments) {
    this.assessments = assessments;
  }

  public LocalDate getAssessmentReferenceDate() {
    return assessmentReferenceDate;
  }

  public void setAssessmentReferenceDate(LocalDate assessmentReferenceDate) {
    this.assessmentReferenceDate = assessmentReferenceDate;
  }

  /**
   * return list of errors on the record
   */
  @Override
  public List<String> getErrors() {
    return errors;
  }

  @Override
  public void addErrors(String error) {
    errors.add(error);
  }

  /**
   * return true when unable to compute hipps else there are no errors
   */
  public boolean hasError() {
    return !errors.isEmpty();
  }

  @Override
  public String getHippsCode() {
    return hippsCode;
  }

  @Override
  public void setHippsCode(String code) {
    hippsCode = code;
  }

  public String getOriginalRecord() {
    return originalRecord;
  }

  public void setOriginalRecord(String originalRecord) {
    this.originalRecord = originalRecord;
  }

  @Override
  public Integer getAiCode() {
    return aiCode;
  }

  @Override
  public void setAiCode(Integer code) {
    aiCode = code;
  }

  @Override
  public Integer getObra() {
    return obra;
  }

  @Override
  public void setObra(Integer obra) {
    this.obra = obra;
  }

  @Override
  public String toString() {
    return "SnfClaim{"
        + "version=" + version
        + ", assessments=" + assessments
        + ", assessmentReferenceDate=" + assessmentReferenceDate
        + ", aiCode=" + aiCode
        + ", obra=" + obra
        + ", hippsCode='" + hippsCode
        + "', errors=" + errors
        + ", originalRecord='" + originalRecord
        + "', codes=" + codes
        + '}';
  }

  /**
   * This wraps the claim in a proxy claim in order to store the original record for use later when
   * the record must be restored.
   */
  public static SnfClaim mapMdsToClaim(String utfRecord) {
    SnfClaim claim = new SnfClaim();
    claim.setOriginalRecord(utfRecord);
    if (utfRecord.length() < Rai300.FIXED_LENGTH_SIZE) {
      claim.addErrors(SnfError.INVALID_LINE_LENGTH.getReason(String.valueOf(utfRecord.length())));
    }

    for (Rai300 item : Rai300.values()) {
      int index = item.getIndex() - 1;
      String itemValue = utfRecord.substring(index, index + item.getLength());

      if (item.getAssessmentType() == AssessmentType.ARD) {
        try {
          LocalDate ard = LocalDate.parse(itemValue, DateTimeFormatter.BASIC_ISO_DATE);
          claim.setAssessmentReferenceDate(ard);
        } catch (DateTimeParseException e) {
          claim.addErrors(SnfError.INVALID_ASSESSMENT_REFERENCE_DATE.getReason(itemValue));
        }
      } else if (item.getAssessmentType() == AssessmentType.PDX) {
        claim.insertPrimaryDiagnosis(new SnfDiagnosisCode(itemValue, null, null, null));
      } else if (item.getAssessmentType() == AssessmentType.SERVICES) {
        if (!itemValue.replaceAll("[.^]", "").trim().isEmpty()) {
          claim.addCode(new SnfDiagnosisCode(itemValue, null, null, null));
        }
      } else if (item.getAssessmentType() == AssessmentType.PPS) {
        int result;
        try {
          result = Integer.parseInt(itemValue);
        } catch (Exception exception) {
          result = Integer.MIN_VALUE;
        }
        int value = result;
        if (value == 8) {
          claim.setAiCode(0);
        } else if (value == 1) {
          claim.setAiCode(1);
        } else if (value == Rai300.NONE_VALUE) {
          claim.setAiCode(null);
        }
      } else if (item.getAssessmentType() == AssessmentType.OBRA) {
        int result;
        try {
          result = Integer.parseInt(itemValue);
        } catch (Exception exception) {
          result = Integer.MIN_VALUE;
        }
        int value = result;
        if (Rai300.A0310A.getValidValues().contains(value)) {
          claim.setObra(value);
        }
      } else {
        Assessment assessment = new Assessment(item.name(), item.getXmlTag(), itemValue);
        claim.addAssessment(assessment);
      }
    }

    return claim;
  }

  /**
   * This retrieves the original record from the Claim and then puts the Hipps code into is proper
   * place in the record. No other data is altered.
   *
   * @return if the claim is a Proxy claim, then it returns the original record with the HIPPS code
   * placed within the record. Otherwise, it will just return the HIPPS Code (5 character string).
   */
  public String mapClaimToMds() {
    String hippsCode = hasError() ? "       " : getHippsCode();

    StringBuilder record = new StringBuilder(getOriginalRecord());
    int hippsIndex = Rai300.RECALCULATED_Z0100A.getIndex() - 1;
    record.replace(hippsIndex, hippsIndex + hippsCode.length(), hippsCode);

    int versionIndex = Rai300.RECALCULATED_Z0100B.getIndex() - 1;
    // substring to remove the leading V
    String version = getRecalculated_z0100b();
    record.replace(versionIndex, versionIndex + version.length(), version);

    return record.toString();
  }
}
