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

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
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 Map<String, Assessment> assessmentMap = new HashMap<>();
  private LocalDate assessmentReferenceDate;

  // TODO change to enum
  private Integer obra;
  private Integer pps;

  // OUTPUTS
  private String hippsCode = "";
  private String recalculated_z0100b = "";
  private String mdsRaiVersion = "";

  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) {
    assessmentMap.put(assessment.getItem(), assessment);
  }

  @Override
  public Map<String, Assessment> getAssessmentMap() {
    return assessmentMap;
  }

  public void setAssessmentMap(Map<String, Assessment> assessments) {
    this.assessmentMap = 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;
  }

  /**
   * Return the assessment indicator.
   * @deprecated
   * This method can be replaced with {@link SnfClaim#getAssessmentIndicator()}.
   * @return the assessment indicator indicating which type of assessment was completed.
   */
  @Deprecated
  @Override
  public Integer getAiCode() {
    return getAssessmentIndicator();
  }

  /**
   * Return the assessment indicator based on the provided PPS value.
   * @since v2.2
   * @return the assessment indicator indicating which type of assessment was completed.
   */
  public Integer getAssessmentIndicator() {
    Optional<PpsAssessment> maybePpsAssessment = PpsAssessment.of(this.pps);
    if (maybePpsAssessment.isPresent()) {
      PpsAssessment ppsAssessment = maybePpsAssessment.get();
      switch (ppsAssessment) {
        case INTERIM_PAYMENT_ASSESSMENT:
          return AssessmentIndicator.INTERIM_PAYMENT_ASSESSMENT.getValue();
        case FIVE_DAY:
          return AssessmentIndicator.FIVE_DAY.getValue();
      }
    }
    return null;
  }

  /**
   * Set the assessment indicator indicating which type of assessment was completed.
   * @param code indicator value
   * @deprecated
   * This method is no longer has any functionality.
   */
  @Deprecated
  @Override
  public void setAiCode(Integer code) {}


  @Override
  public void setPps(Integer pps) {
    this.pps = pps;
  }

  @Override
  public Integer getPps() {
    return this.pps;
  }

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

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

  public String getMdsRaiVersion() {
    return mdsRaiVersion;
  }

  public void setMdsRaiVersion(String mdsRaiVersion) {
    this.mdsRaiVersion = mdsRaiVersion;
  }

  @Override
  public String toString() {
    return "SnfClaim{"
        + "version=" + version
        + ", assessmentReferenceDate=" + assessmentReferenceDate
        + ", aiCode=" + AssessmentIndicator.of(getAssessmentIndicator()).orElse(null)
        + ", obra=" + ObraAssessment.of(obra).orElse(null)
        + ", pps=" + PpsAssessment.of(pps).orElse(null)
        + ", codes=" + codes
        + ", hippsCode='" + hippsCode
        + "', errors=" + errors
        + ", assessments=" + assessmentMap
        + ", originalRecord='" + originalRecord
        + '}';
  }

  /**
   * 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.setAssessmentReferenceDate(null);
        }
      } 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;
        }
        claim.setPps(result);

      } 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 if (item.getAssessmentType() != AssessmentType.ERROR_REASON){
        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();
  }
}
