package gov.cms.grouper.snf.r2;

import com.mmm.his.cer.foundation.exception.FoundationException;
import gov.cms.grouper.snf.SnfComponentAbstract;
import gov.cms.grouper.snf.SnfComponentVersion;
import gov.cms.grouper.snf.SnfContext;
import gov.cms.grouper.snf.SnfRuntimeOption;
import gov.cms.grouper.snf.SnfTables;
import gov.cms.grouper.snf.model.SnfDiagnosisCode;
import gov.cms.grouper.snf.model.SnfProcessError;
import gov.cms.grouper.snf.model.enums.ClinicalCategory;
import gov.cms.grouper.snf.model.enums.CognitiveLevel;
import gov.cms.grouper.snf.model.enums.NursingCmg;
import gov.cms.grouper.snf.model.enums.Rai300;
import gov.cms.grouper.snf.model.enums.SnfError;
import gov.cms.grouper.snf.model.table.BasicRow;
import gov.cms.grouper.snf.model.table.ClinicalCategoryMasterRow;
import gov.cms.grouper.snf.model.table.NtaComorbidityMasterRow;
import gov.cms.grouper.snf.model.table.SlpComorbidityMasterRow;
import gov.cms.grouper.snf.r2.logic.CognitiveLevelLogic;
import gov.cms.grouper.snf.r2.logic.NtaLogic;
import gov.cms.grouper.snf.r2.logic.NursingLogic;
import gov.cms.grouper.snf.r2.logic.SpeechLogic;
import gov.cms.grouper.snf.r2.logic.TherapyLogic;
import gov.cms.grouper.snf.r2.logic.nursing.BscpLogic;
import gov.cms.grouper.snf.r2.logic.nursing.ReducedPhysicalFunctionLogic;
import gov.cms.grouper.snf.r2.logic.nursing.SpecialCare;
import gov.cms.grouper.snf.transfer.ISnfClaim;
import gov.cms.grouper.snf.util.ClaimInfo;
import gov.cms.grouper.snf.util.Reference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SnfComponent extends SnfComponentAbstract {

  private static final Logger LOG = LoggerFactory.getLogger(SnfComponent.class);

  // Used in reflection
  public SnfComponent() {
    this(100);
  }

  // TODO: This seems like a bad way to do this
  public SnfComponent(int dataVersion) {
    super(dataVersion);
    if (getClass().equals(SnfComponent.class)
        && !(SnfComponentVersion.r2.getFrom() <= dataVersion
        && dataVersion <= SnfComponentVersion.r2.getTo())) {
      throw new RuntimeException("Version miss match");
    }
  }

  /**
   * Generate HIPPS code based on CMG from each payment component
   *
   * @param therapyCmg        PT/OT CMG
   * @param speechCmg         SLP CMG
   * @param nursingCmg        Nursing CMG
   * @param ntaCmg            NTA CMG
   * @param aiCode                 Based on item A0310B, IPA is 0 and 5-Day is 1
   * @return HIPPS codes as five characters string
   */
  protected static String getHIPPSCode(String therapyCmg, String speechCmg,
      NursingCmg nursingCmg, String ntaCmg, Integer aiCode) {
    return String.join("",
        therapyCmg.substring(1),
        speechCmg.substring(1),
        nursingCmg.getHipps(),
        ntaCmg.substring(1),
        Objects.toString(aiCode, ""));
  }

    public static List<SnfDiagnosisCode> populate(int version, Iterable<SnfDiagnosisCode> dxCodes) {
    List<SnfDiagnosisCode> result = new ArrayList<>();
    for (SnfDiagnosisCode code : dxCodes) {
      try {
        String trimmedCode = code.getValue().replaceAll("[.^]", "").trim();
        ClinicalCategoryMasterRow clinicalCategoryMasterRow =
            SnfTables.get(SnfTables.clinicalCategoryMasterTable, trimmedCode,
                BasicRow::isVersion, version);
        SlpComorbidityMasterRow slpComorbidityMasterRow =
            SnfTables.get(SnfTables.slpComorbidityMasterTable, trimmedCode,
                BasicRow::isVersion, version);
        Set<NtaComorbidityMasterRow> ntaComorbidityMasterRows =
            SnfTables.getAll(SnfTables.ntaComorbidityMasterTable, trimmedCode,
                BasicRow::isVersion, version);

        // if nta comorbidity table don't have match for the code, pass in null for nta comorbidity
        // if nta comorbidity table has one or more mappings, loop thru each and create a
        // snfDiagnosisCode for each
        String clinicalCategory = clinicalCategoryMasterRow == null ? null : clinicalCategoryMasterRow.getClinicalCategory();
        String slpCategory = slpComorbidityMasterRow == null ? null : slpComorbidityMasterRow
            .getSlpCategory();
        if (ntaComorbidityMasterRows == null) {
          result.add(new SnfDiagnosisCode(trimmedCode, clinicalCategory, slpCategory, null));
        } else {
          ntaComorbidityMasterRows.forEach(ntaComorbidityMasterRow ->
              result.add(new SnfDiagnosisCode(trimmedCode, clinicalCategory, slpCategory,
              ntaComorbidityMasterRow.getNtaCategory())));
        }
      } catch (NullPointerException ex) {
        LOG.warn("Invalid ICD 10 code: {}", code.getValue());
      }
    }
    return result;
  }

  @Override
  public Boolean hasIPA(Integer aiCode, Integer obra) {
    Boolean result = null;
    if (aiCode != null) {
      if (aiCode == 1) {
        // 5-day assessment
        result = false;
      } else if (aiCode == 0) {
        // IPA - Interim payment assessment
        result = true;
      }
    }

    return result;
  }

  private static void addInvalidControlElementsError(ISnfClaim claim) {
    // when execute with snfClaim
    if (claim.getOriginalRecord() == null) {
      claim.addErrors(SnfError.INVALID_A0310A_OR_A0310B.getReason(claim.getAiCode().toString(),
          claim.getObra().toString()));
    }
    // when execute with fixed length string
    else {
      int begin = Rai300.A0310A.getIndex() - 1;
      int end = begin + Rai300.A0310A.getLength();
      String a0310aValue = claim.getOriginalRecord().substring(begin, end);
      begin = Rai300.A0310B.getIndex() - 1;
      end = begin + Rai300.A0310B.getLength();
      String a0310bValue = claim.getOriginalRecord().substring(begin, end);
      claim.addErrors(SnfError.INVALID_A0310A_OR_A0310B.getReason(a0310aValue, a0310bValue));
    }
  }

  @Override
  public void reconfigure(SnfRuntimeOption option) throws FoundationException {
    // TODO Auto-generated method stub

  }

  @Override
  public void exec(ISnfClaim claim) {
    SnfContext.trace(claim);

    Integer aiCode = claim.getAiCode();
    Boolean hasIPA = hasIPA(aiCode, claim.getObra());

    if (hasIPA == null) {
      addInvalidControlElementsError(claim);
    }

    if (claim.hasError()) {
      throw new SnfProcessError();
    }

    ClaimInfo asstInfo = ClaimInfo.of(getDataVersion(), hasIPA, claim.getAssessments());
    try {

      String trimmedPdx = claim.getPrimaryDiagnosis().getValue().replaceAll("[.^]", "").trim();
      ClinicalCategoryMasterRow clinicalCategoryMasterRow =
          SnfTables.get(SnfTables.clinicalCategoryMasterTable, trimmedPdx,
              BasicRow::isVersion, this.getDataVersion());
      SlpComorbidityMasterRow slpComorbidityMasterRow =
          SnfTables.get(SnfTables.slpComorbidityMasterTable, trimmedPdx,
              BasicRow::isVersion, this.getDataVersion());
      NtaComorbidityMasterRow ntaComorbidityMasterRow =
          SnfTables.get(SnfTables.ntaComorbidityMasterTable, trimmedPdx,
              BasicRow::isVersion, this.getDataVersion());

      String clinicalCategory = clinicalCategoryMasterRow == null ? null : clinicalCategoryMasterRow.getClinicalCategory();
      String slpCategory = slpComorbidityMasterRow == null ? null : slpComorbidityMasterRow
          .getSlpCategory();
      String ntaCategory = ntaComorbidityMasterRow == null ? null : ntaComorbidityMasterRow
          .getNtaCategory();

      SnfDiagnosisCode pdx = new SnfDiagnosisCode(trimmedPdx, clinicalCategory, slpCategory, ntaCategory);
      claim.setPrimaryDiagnosis(pdx);

    } catch (NullPointerException ex) {
      LOG.warn("Invalid Pdx code: {}", claim.getPrimaryDiagnosis().getValue());
    }

    // If PDX is RTP and it's PPS(5-day assessment & IPA), then blank out HIPPS code
    if (claim.getPrimaryDiagnosis().getClinicalCategory()
        .equals(ClinicalCategory.RETURN_TO_PROVIDER.getDescription()) && aiCode != null) {
      claim.setHippsCode("");
      return;
    }

    List<SnfDiagnosisCode> secondaryDiagnoses =
        SnfComponent.populate(getDataVersion(), claim.getSecondaryDiagnoses());

    ClinicalCategory pdxClinicalCategory = TherapyLogic.getPdxClinicalCategory(
        getDataVersion(), asstInfo, claim.getPrimaryDiagnosis().getValue());

    if (!claim.hasError()) {
      String therapyLogicCmg =
          new TherapyLogic(getDataVersion(), asstInfo, pdxClinicalCategory).exec();

      CognitiveLevel cognitiveLevel = new CognitiveLevelLogic(asstInfo).exec();
      String speechLogicCmg = new SpeechLogic(getDataVersion(), asstInfo, secondaryDiagnoses,
          cognitiveLevel, pdxClinicalCategory).exec();

      Reference<ReducedPhysicalFunctionLogic> physicRef = new Reference<>();

      SpecialCare specialCare = new SpecialCare(asstInfo);
      BscpLogic bscp = new BscpLogic(asstInfo, physicRef);
      ReducedPhysicalFunctionLogic physical = new ReducedPhysicalFunctionLogic(asstInfo, bscp);
      physicRef.set(physical);

      NursingCmg nursingLogicCmg = new NursingLogic(asstInfo, specialCare, physical, bscp).exec();

      String ntaPaymentCompLogicCmg = new NtaLogic(asstInfo, secondaryDiagnoses).exec();

      String hippsCode = SnfComponent.getHIPPSCode(therapyLogicCmg, speechLogicCmg, nursingLogicCmg,
          ntaPaymentCompLogicCmg, aiCode);

      claim.setHippsCode(hippsCode);
    }
  }

  @Override
  public void close() {
    try {
      super.close();
    } catch (Throwable th) {
      throw new RuntimeException(th);
    }
  }

  @Override
  public SnfError validates(ISnfClaim claim) {
    // SnfValidations valid = new SnfValidations(claim);
    return null;
  }


}
