package io.delphi.qa.auto.awsmfrw.tests.slz;

import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.google.common.io.Files;
import com.google.gson.internal.LinkedTreeMap;
import io.delphi.qa.auto.awsmfrw.common_baby.IsaCommonMethods;
import io.delphi.qa.auto.awsmfrw.common_baby.IsaLogging;
import io.delphi.qa.auto.awsmfrw.common_baby.delphi.aws.EnAwsS3Buckets;
import io.delphi.qa.auto.awsmfrw.common_baby.delphi.aws.EnAwsS3DspConfigs;
import io.delphi.qa.auto.awsmfrw.common_baby.delphi.aws.EnAwsSecrets;
import io.delphi.qa.auto.awsmfrw.common_baby.file_system.EnFsResourcesLocators;
import io.delphi.qa.auto.awsmfrw.models.MlFw_ReportWrapper;
import io.delphi.qa.auto.awsmfrw.models.aws.lm.WpSlzUnitOfWork;
import io.delphi.qa.auto.awsmfrw.models.aws.s3.S3DspConfigItem;
import io.delphi.qa.auto.awsmfrw.models.aws.s3.butch_run_config.S3ButchRunConfig;
import io.delphi.qa.auto.awsmfrw.models.aws.s3.complete_criteria.CompleteCriteria;
import io.delphi.qa.auto.awsmfrw.models.aws.s3.complete_criteria.S3DspCompleteCriteria;
import io.delphi.qa.auto.awsmfrw.models.aws.s3.dsp_specific_settings.S3DspSpecificSettings;
import io.delphi.qa.auto.awsmfrw.models.db.sql.pg.BsPgEntity;
import io.delphi.qa.auto.awsmfrw.models.db.sql.pg.MAIN.PUBLIC.*;
import io.delphi.qa.auto.awsmfrw.models.db.sql.pg.WpPgContent;
import io.delphi.qa.auto.awsmfrw.models.db.sql.pg.WpPgUnitOfWork;
import io.delphi.qa.auto.awsmfrw.tests.BsTestCase;
import io.qameta.allure.Step;
import org.joda.time.DateTime;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public abstract class BsSlzTest extends BsTestCase {

  protected Map<String, String> secretMap =
      fetchAwsSecret(EnAwsSecrets.DELPHI_SLZ_STORAGE_PG_PROXY_USER);

  protected String buildMockContextFileName(
      WpSlzUnitOfWork xWpSlzUnitOfWork,
      String mockCode,
      File file,
      CompleteCriteria completeCriteria) {
    return IsaCommonMethods.resolvePattern(
        file.getName().split("-")[1],
        new HashMap<String, String>() {
          {
            String apple =
                mockCode != null
                    ? envConfig.isLocalS3()
                        ? completeCriteria.getContext()
                        : xWpSlzUnitOfWork.getDsp().equalsIgnoreCase("apple")
                            ? completeCriteria.getContext().split("::")[1]
                            : completeCriteria.getContext()
                    : "null";
            put("context", apple);
            put(
                "yyyyMMdd",
                IsaCommonMethods.printDateTimeText(xWpSlzUnitOfWork.getReportDate(), "yyyyMMdd"));
          }
        });
  }

  @Step("Clear SQL table records")
  protected <T extends BsPgEntity<T>> int clearTableRecords(Set<T> set, String column) {
    List<Long> collect = set.stream().map(T::getId).collect(Collectors.toList());
    if (collect.size() > 0) {
      return delphi.db().postgres(secretMap).awsmDelete(set, column);
    } else return 0;
  }

  @Step(
      "Ensure records not in tables: CONTENT_FAILURE_LOG, DISASSEMBLE_CONTENT_STATUS, CONTENT_STATUS, UNIT_OF_WORK")
  protected void
      ensureRecordsNotInTables_ContentFailureLog_DisassembleContentStatus_ContentStatus_UnitOfWork(
          Set<MlPg_UnitOfWork> mlPgUnitOfWorks) {
    int dcss = 0;
    int css = 0;
    int pgp = 0;
    int uows = 0;
    int cfls = 0;
    if (mlPgUnitOfWorks.size() > 0) {
      // collect uow ids
      String uowIds =
          mlPgUnitOfWorks.stream()
              .map(MlPg_UnitOfWork::getId)
              .map(String::valueOf)
              .collect(Collectors.joining("\n, "));
      // select cs records by uow ids
      Set<MlPg_ContentStatus> mlPgContentStatuses =
          delphi
              .db()
              .postgres(secretMap)
              .awsmSelect(
                  new MlPg_ContentStatus(), String.format("unit_of_work_id in (%s)", uowIds));
      // select dcs records by uow ids
      Set<MlPg_DisassembleContentStatus> mlPgDisassembleContentStatuses =
          delphi
              .db()
              .postgres(secretMap)
              .awsmSelect(
                  new MlPg_DisassembleContentStatus(),
                  String.format("unit_of_work_id in (%s)", uowIds));
      // select dcs records by uow ids
      Set<MlPg_UnitOfWorkPipeline> mlPgUnitOfWorksPipelinePipelines =
              delphi
                      .db()
                      .postgres(secretMap)
                      .awsmSelect(
                              new MlPg_UnitOfWorkPipeline(),
                              String.format("unit_of_work_id in (%s)", uowIds));
      // clear cfl records
      if (mlPgContentStatuses.size() > 0) {
        // collect uow ids
        String csIds =
            mlPgContentStatuses.stream()
                .map(MlPg_ContentStatus::getId)
                .map(String::valueOf)
                .collect(Collectors.joining("\n, "));
        // select cfl records
        Set<MlPg_ContentFailureLog> mlPgContentFailureLogs =
            delphi
                .db()
                .postgres(secretMap)
                .awsmSelect(
                    new MlPg_ContentFailureLog(),
                    String.format("content_status_id in (%s)", csIds));
        cfls += clearTableRecords(mlPgContentFailureLogs, null);
      }
      dcss += clearTableRecords(mlPgDisassembleContentStatuses, null);
      css += clearTableRecords(mlPgContentStatuses, null);
      pgp += clearTableRecords(mlPgUnitOfWorksPipelinePipelines, "unit_of_work_id");
      uows += clearTableRecords(mlPgUnitOfWorks, null);
    }
    IsaLogging.log(
        this.getClass(),
        String.format(
            "processed tables:\n- CONTENT_FAILURE_LOG records:%d,\n- DISASSEMBLE_CONTENT_STATUS records=%d,\n- CONTENT_STATUS records=%d,\n- UNIT_OF_WORK records=%d, \n UNIT_OF_WORK_PIPELINE records=%d",
            cfls, dcss, css, uows, pgp));
  }

  @Step("Ensure AWS S3 bucket has no prefix")
  public void ensureS3HasNoObjects(String bucketName, String prefix) {
    // list s3 bucket
    Set<S3ObjectSummary> s3ObjectIdSet = delphi.aws().s3().listS3Objects(bucketName, prefix);
    // clear s3 bucket with dsp contexts
    s3ObjectIdSet.forEach(delphi.aws().s3()::deleteS3ObjectSummary);
  }

  @Step
  protected File filterContextSource(String mockCode, File[] listContextSourceFiles) {
    final File file;
    if (mockCode != null) {
      file =
          Arrays.stream(listContextSourceFiles)
              .filter(o -> o.getName().split("-")[0].equals(mockCode))
              .collect(Collectors.toList())
              .get(0);
    } else {
      file =
          Arrays.stream(listContextSourceFiles)
              .filter(o -> o.getName().split("-")[0].equals("0000"))
              .collect(Collectors.toList())
              .get(0);
    }
    return file;
  }

  @Step
  protected Set<CompleteCriteria> getCustomCompleteCriteriaItem(
      MlFw_ReportWrapper mlFwReportWrapper, Integer isOneRequired) {
    S3DspCompleteCriteria dspCompleteCriteria = getDspCompleteCriteria(mlFwReportWrapper);
    Set<CompleteCriteria> full = dspCompleteCriteria.getCompleteCriteria();
    if (isOneRequired == -1) {
      return full;
    } else if (isOneRequired == 0) {
      return Collections.singleton(
          full.stream()
              .filter(CompleteCriteria::getRequired)
              .collect(Collectors.toSet())
              .iterator()
              .next());
    } else {
      return full.stream().filter(CompleteCriteria::getRequired).collect(Collectors.toSet());
    }
  }

  @Step("Get dsp_completed_criteria under test")
  public S3DspCompleteCriteria getDspCompleteCriteria(MlFw_ReportWrapper mlFwReportWrapper) {
    List<HashMap> s3ObjectList =
        delphi
            .aws()
            .s3()
            .getS3Object(
                EnAwsS3Buckets.DELPHI_CONFIG.get(),
                EnAwsS3DspConfigs.DSP_COMPLETE_CRITERIA_KEY.get(),
                new HashMap());
    String key =
        String.format(
            "%s-%s-%s",
            mlFwReportWrapper.getDsp().get(),
            mlFwReportWrapper.getLicensor().get(),
            mlFwReportWrapper.getReport().getReportName());
    return gson.fromJson(
        gson.toJson(
            Objects.requireNonNull(
                s3ObjectList.get(0).get(key),
                String.format(
                    "%s report is absent in %s s3 file.",
                    key, EnAwsS3DspConfigs.DSP_COMPLETE_CRITERIA_KEY.get()))),
        S3DspCompleteCriteria.class);
  }

  @Step("Get dsp_config under test")
  public List<S3DspConfigItem> getDspConfigItem(WpSlzUnitOfWork mlWpAwsUow) {
    // get dsp_configs list from s3
    List<S3DspConfigItem> dspConfigList =
        delphi
            .aws()
            .s3()
            .getS3Object(
                EnAwsS3Buckets.DELPHI_CONFIG.get(),
                EnAwsS3DspConfigs.DSP_CONFIG_KEY.get(),
                new S3DspConfigItem());
    // filter dspConfigList to get list within uow under test
    return dspConfigList.stream()
        .filter(o -> o.getArgs().getDsp().equals(mlWpAwsUow.getDsp()))
        .filter(o -> o.getArgs().getLicensor().equals(mlWpAwsUow.getLicensor()))
        .filter(o -> o.getArgs().getVersion().equals(mlWpAwsUow.getVersion()))
        .filter(
            o ->
                o.getArgs().getValidFrom() != null
                    && new DateTime(o.getArgs().getValidFrom()).isBefore(new DateTime()))
        .filter(o -> o.getArgs().getType().equals(mlWpAwsUow.getReportName()))
        .collect(Collectors.toList());
  }

  @Step("Get dsp-specific-settings under test")
  public S3DspSpecificSettings getDspSpecificSettings(String dsp) {
    List<HashMap> s3ObjectList =
        delphi
            .aws()
            .s3()
            .getS3Object(
                EnAwsS3Buckets.DELPHI_CONFIG.get(),
                EnAwsS3DspConfigs.DSP_SPECIFIC_SETTINGS_KEY.get(),
                new HashMap());
    return gson.fromJson(gson.toJson(s3ObjectList.get(0).get(dsp)), S3DspSpecificSettings.class);
  }

  @Step("Ensure records in tables: UNIT_OF_WORK")
  public WpPgUnitOfWork insertRecordsInTable_UnitOfWork_ContentStatuses(
      WpPgUnitOfWork wpPgUnitOfWork) {
    wpPgUnitOfWork.withPgUnitOfWork(
        delphi
            .db()
            .postgres(secretMap)
            .awsmInsert(Collections.singleton(wpPgUnitOfWork.getMlPgUnitOfWork()))
            .iterator()
            .next());
    if (wpPgUnitOfWork.getWpPgContents() != null) {
      wpPgUnitOfWork
          .getWpPgContents()
          .forEach(
              c ->
                  c.getMlPgContentStatus()
                      .withUnitOfWorkId(wpPgUnitOfWork.getMlPgUnitOfWork().getId()));
      delphi
          .db()
          .postgres(secretMap)
          .awsmInsert(
              wpPgUnitOfWork.getWpPgContents().stream()
                  .map(WpPgContent::getMlPgContentStatus)
                  .collect(Collectors.toCollection(HashSet::new)))
          .iterator()
          .next();
    }
    return new WpPgUnitOfWork()
        .withPgUnitOfWork(wpPgUnitOfWork.getMlPgUnitOfWork())
        .withWpPgContents(wpPgUnitOfWork.getWpPgContents());
  }

  protected File makeTextFile(File patternFile, String contextFileName) {
    String textFilePath =
        String.format("%s/%s", EnFsResourcesLocators.BUILD.get(), contextFileName);
    File textFile = new File(textFilePath);
    try {
      byte[] bytes = Files.toByteArray(patternFile);
      Files.write(bytes, textFile);
    } catch (IOException e) {
      IsaLogging.log(this.getClass(), "reading file io exception: " + e);
      throw new RuntimeException(e);
    }
    return textFile;
  }

  @Step("Update COMPLETE_CRITERIA rows by predicate")
  protected WpSlzUnitOfWork updateUowCompleteCriteriaByPredicate(
      WpSlzUnitOfWork wpSlzUnitOfWork, Predicate<CompleteCriteria> completeCriteriaPredicate) {
    return wpSlzUnitOfWork.withCompleteCriteriaSet(
        wpSlzUnitOfWork.getCompleteCriteriaSet().stream()
            .filter(completeCriteriaPredicate)
            .collect(Collectors.toSet()));
  }

  @Step("Upload custom context to bucket")
  protected Set<S3ObjectSummary> uploadContext(
      WpSlzUnitOfWork xWpSlzUnitOfWork, String mockCode, boolean isArchive, String bucketName) {
    File[] listContextSourceFiles =
        delphi
            .fs()
            .listContextSourceFiles(
                xWpSlzUnitOfWork.getDsp().equalsIgnoreCase("apple")
                    ? xWpSlzUnitOfWork.getReportName().substring(2)
                    : xWpSlzUnitOfWork.getReportName());
    File patternFile = filterContextSource(mockCode, listContextSourceFiles);
    Set<File> files =
        xWpSlzUnitOfWork.getCompleteCriteriaSet().stream()
            .map(
                completeCriteria ->
                    makeTextFile(
                        patternFile,
                        buildMockContextFileName(
                            xWpSlzUnitOfWork, mockCode, patternFile, completeCriteria)))
            .collect(Collectors.toSet());
    if (isArchive) {
      files =
          files.stream()
              .map(
                  file ->
                      IsaCommonMethods.archiveFile(
                          file,
                          file.getName(),
                          EnFsResourcesLocators.BUILD.get(),
                          String.format(
                              "%s.%s",
                              this.getDspConfigItem(xWpSlzUnitOfWork)
                                  .get(0)
                                  .getArgs()
                                  .getExtension(),
                              gzSet.contains(xWpSlzUnitOfWork.getReportName().toLowerCase())
                                  ? "gz"
                                  : "zip")))
              .collect(Collectors.toSet());
    }
    return files.stream()
        .map(
            file ->
                delphi
                    .aws()
                    .s3()
                    .addS3File(
                        bucketName,
                        IsaCommonMethods.buildS3BucketPrefix(xWpSlzUnitOfWork) + file.getName(),
                        file))
        .collect(Collectors.toSet());
  }

  @Step("Upload custom batch-run-config-auto_tc_id_%s.json")
  public String uploadCustomButchRunConfig(WpSlzUnitOfWork mlWpAwsUow) {
    // get dsp_configs list from s3
    List<S3ButchRunConfig> s3ButchRunConfigs =
        delphi
            .aws()
            .s3()
            .getS3Object(
                EnAwsS3Buckets.DELPHI_CONFIG.get(),
                EnAwsS3DspConfigs.BATCH_RUN_CONFIG_KEY.get(),
                new S3ButchRunConfig());
    String fileName =
        String.format(EnAwsS3DspConfigs.BATCH_RUN_CONFIG_KEY_AUTO_PATTERN.get(), getTestRunId());
    if (s3ButchRunConfigs.iterator().hasNext()) {
      List<String> strings = new ArrayList<>();
      if (envConfig.customValidationThreshold() != -1)
        strings.add("--validation-threshold-min-file-size=100");
      if (envConfig.isLocalS3())
        strings.add(String.format("--bucket-source=%s", EnAwsS3Buckets.DELPHI_AUTOTESTS.get()));
      s3ButchRunConfigs.iterator().next().getCommand().addAll(strings);
      // Upload filteredBatchRunConfigsUT to S3
      return delphi
          .aws()
          .s3()
          .addS3Object(
              EnAwsS3Buckets.DELPHI_CONFIG.get(), fileName, s3ButchRunConfigs.iterator().next());
    } else throw new RuntimeException(String.format("Exception: %s not found", fileName));
  }

  @Step("Upload custom dsp-complete-criteria-auto_tc_id_%s.json")
  protected String uploadCustomCompleteCriteriaFile(MlFw_ReportWrapper mlFwReportWrapper, String testRunId) {
    S3DspCompleteCriteria completeCriteriaItem = getDspCompleteCriteria(mlFwReportWrapper);
    completeCriteriaItem.withCompleteCriteria(
        getCustomCompleteCriteriaItem(mlFwReportWrapper, envConfig.whichCompleteCriteria()));
    String name =
        String.format(
            "%s-%s-%s",
            mlFwReportWrapper.getDsp().get(),
            mlFwReportWrapper.getLicensor().get(),
            mlFwReportWrapper.getReport().getReportName());
    LinkedTreeMap<String, Object> map = new LinkedTreeMap<>();
    map.put(name, completeCriteriaItem);
    return delphi
        .aws()
        .s3()
        .addS3Object(
            EnAwsS3Buckets.DELPHI_CONFIG.get(),
            String.format(
                EnAwsS3DspConfigs.DSP_COMPLETE_CRITERIA_KEY_AUTO_PATTERN.get(), testRunId),
            map);
  }

  @Step("Upload custom dsp_config_auto_tc_id_%s.json")
  public String uploadCustomDspConfigFile(WpSlzUnitOfWork mlWpAwsUow, String testRunId) {
    List<S3DspConfigItem> s3DspConfigItems = getDspConfigItem(mlWpAwsUow);
    // Upload s3DspConfigItems to S3
    String fileName = String.format(EnAwsS3DspConfigs.DSP_CONFIG_KEY_AUTO_PATTERN.get(), testRunId);
    if (s3DspConfigItems.size() != 0) {
      return delphi
          .aws()
          .s3()
          .addS3Object(EnAwsS3Buckets.DELPHI_CONFIG.get(), fileName, s3DspConfigItems);
    } else
      throw new RuntimeException(
          "Exception: SLZ unit_of_work under test not found in dsp_config.json:\n"
              + "- unit_of_work_code parameters mismatch, check: version, dsp, licensor, report_name.");
  }

  //  @Step("BS::SLZ: upload dsp mock context as decompressed")
  //  protected Set<S3ObjectSummary> uploadDspMockContext(
  //      WpSlzUnitOfWork xWpSlzUnitOfWork, String mockCode, boolean isArchive) {
  //    File[] listContextSourceFiles =
  //        delphi
  //            .fs()
  //            .listContextSourceFiles(
  //                xWpSlzUnitOfWork.getDsp().equalsIgnoreCase("apple")
  //                    ? xWpSlzUnitOfWork.getReportName().substring(2)
  //                    : xWpSlzUnitOfWork.getReportName());
  //    File patternFile = filterContextSource(mockCode, listContextSourceFiles);
  //    return xWpSlzUnitOfWork.getCompleteCriteriaSet().stream()
  //        .map(
  //            o ->
  //                delphi
  //                    .aws()
  //                    .s3()
  //                    .addS3File(
  //                        EnAwsS3Buckets.DELPHI_AUTOTESTS.get(),
  //                        buildMockContextPath(xWpSlzUnitOfWork, mockCode, patternFile, o),
  //                        patternFile))
  //        .collect(Collectors.toSet());
  //  }

  @Step("Upload custom context")
  protected Set<S3ObjectSummary> uploadMockDspContextFile(
      WpSlzUnitOfWork xWpSlzUnitOfWork, String mockCode, boolean isArchive) {
    return uploadContext(
        xWpSlzUnitOfWork, mockCode, isArchive, EnAwsS3Buckets.DELPHI_AUTOTESTS.get());
  }
}
