package io.delphi.qa.auto.awsmfrw.suppliers;

import com.atlassian.jira.rest.client.api.domain.Issue;
import io.delphi.qa.auto.awsmfrw.common_baby.*;
import io.delphi.qa.auto.awsmfrw.common_baby.delphi.aws.EnAwsSecrets;
import io.delphi.qa.auto.awsmfrw.common_baby.delphi.aws.ICanFetchAwsSecret;
import io.delphi.qa.auto.awsmfrw.managers.HlJira;
import io.delphi.qa.auto.awsmfrw.managers.helpers.aws.HlAwsSecretManager;
import io.delphi.qa.auto.awsmfrw.models.EnDelphiLicensorItems;
import io.delphi.qa.auto.awsmfrw.models.MlFw_ReportWrapper;
import io.delphi.qa.auto.awsmfrw.models.api.rest.slz.apple.AppleReportBody;
import io.delphi.qa.auto.awsmfrw.models.api.rest.v3.EnDelphiApiV3_EndPointItems;
import io.delphi.qa.auto.awsmfrw.models.api.rest.v3.health.InterfaceEnvHealth;
import io.delphi.qa.auto.awsmfrw.models.api.rest.v3.methods.params.*;
import io.delphi.qa.auto.awsmfrw.models.api.rest.v3.oauth.token.PlCredentials;
import io.delphi.qa.auto.awsmfrw.models.aws.cw.*;
import io.delphi.qa.auto.awsmfrw.models.aws.lm.WpSlzUnitOfWork;
import io.delphi.qa.auto.awsmfrw.models.aws.sf.RqSlzJobManager;
import io.delphi.qa.auto.awsmfrw.models.db.sql.pg.*;
import io.delphi.qa.auto.awsmfrw.models.db.sql.pg.MAIN.PUBLIC.MlPg_ContentStatus;
import io.delphi.qa.auto.awsmfrw.models.db.sql.pg.MAIN.PUBLIC.MlPg_UnitOfWork;
import io.delphi.qa.auto.awsmfrw.models.ui.decibel.MlAdsPerformance;
import io.delphi.qa.auto.awsmfrw.tests.BsTest;
import io.restassured.mapper.ObjectMapperType;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import org.apache.http.annotation.Experimental;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.testng.collections.Lists;

import java.io.File;
import java.sql.Date;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public abstract class DelphiDataContainer extends BsTest
    implements IsaCommonMethods,
        ICanFetchAwsSecret,
        ICanFetchStartDate,
        ICanFetchEndDate,
        ICanFetchDecibelLabelIds,
        ICanFetchPlatforms,
        ICanFetchProviderIds {

  public static ConcurrentHashMap<String, InterfaceEnvHealth> MAP_ENV_HEALTH =
      new ConcurrentHashMap<>();

  public static final Map<String, String> MAP_DELPHI_API_DB_SECRET;

  private static final Map<String, Object> CNT_DELPHI_TESTDATA = new ConcurrentHashMap<>();
  private static final Map<ICanProvideHelpers, Object> CNT_DELPHI_HELPERS =
      new ConcurrentHashMap<>();
  public static final int INT_DISTINCT_LIMIT = 2000;
  private static final ThreadLocal<HashMap<String, Object>> TEST_RESOURCES = new ThreadLocal<>();

  public static MlAdsPerformance genMlAdsPerformance(String platform) {
    return new MlAdsPerformance()
        .withUser(IsaConstants.fetchDefaultUserId())
        .withDecibelLabel(IsaConstants.fetchDefaultDecibelLabelId())
        .withPlatform(platform)
        .withProvider(IsaConstants.fetchDefaultProviderId())
        .withStartDate(IsaConstants.fetchDefaultStartDate())
        .withEndDate(IsaConstants.fetchDefaultEndDate());
  }

  // TODO by alexeyisakov @6/3/21#1:35 PM: Rename -> saveTestResource
  public static void saveResource(String locator, Object obj) {
    HashMap<String, Object> currentTestData =
        TEST_RESOURCES.get() == null ? new HashMap<>() : TEST_RESOURCES.get();
    currentTestData.put(locator, obj);
    TEST_RESOURCES.set(currentTestData);
  }

  public static void resetThreadLocal() {
    TEST_RESOURCES.set(null);
  }

  static {
    MAP_DELPHI_API_DB_SECRET =
        new HlAwsSecretManager().getSecretMap(EnAwsSecrets.DELPHI_API_DB_PARAMS.resourceLocator());
    {
      if (envConfig.delphiApiHostPattern().contains("perf")) {
        IsaLogging.log(DelphiDataContainer.class, "\n\t- env: perftest");
        MAP_DELPHI_API_DB_SECRET.put(
            "host", "stage-delphi-main-perftest.cluster-cic9zqsxd2y6.us-east-1.rds.amazonaws.com");
      }
    }
    IsaLogging.log(DelphiDataContainer.class, "\n" + MAP_DELPHI_API_DB_SECRET.toString());
  }

  private static String STR_BASE_QUERY_PATTERN =
      "SELECT DISTINCT"
          + "\n  {column_name}"
          + "\nFROM"
          + "\n  {table_name}"
          + "\nWHERE"
          + "\n {column_name} IS NOT null";
  private static String STR_DELPHI_ORDER_BY_PATTERN = "\nORDER BY" + "\n  updated_at" + "\n DESC";
  private static String STR_LIMIT_PATTERN = "\nLIMIT {limit}";

  public static Object fetchApiRestHeader(
      ICanProvideResources resourceItem, final Map<String, String> secretOAuth) {
    if (CNT_DELPHI_TESTDATA.get(resourceItem.resourceLocator()) == null) {
      HashMap<String, Object> authorization =
          new HashMap<String, Object>() {
            {
              PlCredentials authToken = get0AuthToken(secretOAuth);
              String bearerToken = IsaCommonMethods.buildBearerToken(authToken);
              put("Authorization", bearerToken);
            }
          };
      CNT_DELPHI_TESTDATA.put(resourceItem.resourceLocator(), authorization);
    }
    return CNT_DELPHI_TESTDATA.get(resourceItem.resourceLocator());
  }

  public static Object fetchHelperItem(ICanProvideHelpers helpersItem) {
    if (CNT_DELPHI_HELPERS.get(helpersItem) == null) {
      CNT_DELPHI_HELPERS.put(helpersItem, helpersItem.init());
    }
    return CNT_DELPHI_HELPERS.get(helpersItem);
  }

  public static PlCredentials get0AuthToken(Map<String, String> secretOAuth) {
    final PlCredentials[] plCredentials = new PlCredentials[1];
    IsaCommonMethods.doWithWait(
        () -> {
          ExtractableResponse<Response> extractableResponse =
              delphi
                  .api()
                  .rest()
                  .javaApi()
                  .withBodies(IsaCommonMethods.buildOAuthTokenBodies(secretOAuth))
                  .execPost(EnDelphiApiV3_EndPointItems.OAUTH_TOKEN.get())
                  .withThen()
                  .withValidatableRs()
                  .extract();
          boolean isSuccess = extractableResponse.statusCode() == 200;
          if (isSuccess)
            plCredentials[0] = extractableResponse.as(PlCredentials.class, ObjectMapperType.GSON);
          return isSuccess;
        },
        10,
        127,
        TimeUnit.MILLISECONDS);
    if (plCredentials[0] != null) {
      return plCredentials[0];
    } else {
      throw new RuntimeException(
          String.format(
              IsaCommonMethods.getJiraIssuePrinter(
                  delphi.api().rest().jira().getJiraIssue("DAE-1768")),
              "KNOWN_ISSUE: Could not get access token. 0auth service not respond."));
    }
  }

  public static Object fetchResources(ICanProvideResources resourceItem) {
    String key = resourceItem.resourceLocator() + resourceItem.resourceName()  + "=" + resourceItem.resourceValue();
    synchronized (String.class) {
      if (CNT_DELPHI_TESTDATA.get(key) == null) {
        List<?> fetchedData = null;
        if (resourceItem.resourceClient().name().contains("_DB")) {
          fetchedData = fetchFromDb(resourceItem);
        } else if (resourceItem.resourceClient().name().contains("_CUSTOM")) {
          fetchedData = fetchFromDbCustom(resourceItem);
        } else if (resourceItem.resourceClient().name().contains("_MAP")) {
          fetchedData = fetchMapFromDb(resourceItem);
        } else if (resourceItem.resourceClient().name().contains("_SECRET_MANAGER")) {
          fetchedData = fetchAws(resourceItem);
        } else if (resourceItem.resourceClient().name().contains("_TMS_JIRA")) {
          fetchedData = fetchTms(resourceItem);
        } else if (resourceItem.resourceClient().name().contains("_FS")) {
          try {
            fetchedData =
                DelphiDataContainer.fetchFromFs(
                    resourceItem.resourceLocator() + resourceItem.resourceName());
          } catch (Throwable e) {
            fetchedData = Collections.singletonList(resourceItem.resourceValue());
          }
        }
        //        Object o = DelphiDataSupplier.getRandomSlice(Objects.requireNonNull(fetchedData));
        CNT_DELPHI_TESTDATA.put(key, fetchedData);
      }
    }
    return CNT_DELPHI_TESTDATA.get(key);
  }

  private static List<?> fetchTms(ICanProvideResources resourceItem) {
    Issue jiraIssue = new HlJira().getJiraIssue(resourceItem.resourceValue());
    return Collections.singletonList(jiraIssue);
  }

  private static List<?> fetchAws(ICanProvideResources resourceItem) {
    Map<String, String> secretMap = delphi.aws().sm().getSecretMap(resourceItem.resourceLocator());
    return Collections.singletonList(secretMap);
  }

  public static List<String> fetchFromDb(ICanProvideResources resourceItem) {
    String queryPattern = STR_BASE_QUERY_PATTERN + STR_LIMIT_PATTERN;
    String queryFormatted =
        IsaCommonMethods.resolvePattern(
            queryPattern,
            new HashMap() {
              {
                put("table_name", resourceItem.resourceLocator() + resourceItem.resourceName());
                put("column_name", resourceItem.resourceValue());
                put("limit", INT_DISTINCT_LIMIT);
              }
            });
    List<String> strings = delphi.db().postgres(MAP_DELPHI_API_DB_SECRET).awsmQuery(queryFormatted);
    Collections.shuffle(strings);
    return strings.stream().parallel().filter(Objects::nonNull).collect(Collectors.toList());
  }

  public static List<Map<String,String>> fetchMapFromDb(ICanProvideResources resourceItem) {
    return delphi.db().postgres(MAP_DELPHI_API_DB_SECRET).awsmQueryNew(resourceItem.resourceValue());
  }

  public static List<String> fetchFromDbCustom(ICanProvideResources resourceItem) {
    return delphi.db().postgres(MAP_DELPHI_API_DB_SECRET).awsmQuery(resourceItem.resourceValue());
  }

  public static AppleReportBody genPlAppleApiReport(
      Map<String, String> secret, RqSlzJobManager job, final DateTime xDateTime) {
    return new AppleReportBody()
        .withUserId(secret.get("user_id"))
        .withAccessToken(secret.get("access_token"))
        .withAccount(job.getContexts().get(0).split("::")[0])
        .withQueryInput(
            IsaCommonMethods.resolvePattern(
                "[p=Reporter.properties, Sales.getReport, {vendor_number},{report_type},{subtype},{report_frequency},{report_date},{report_version}]",
                new HashMap<String, String>() {
                  {
                    put("vendor_number", job.getContexts().get(0).split("::")[1]);
                    put("report_type", job.getReportType());
                    put("subtype", job.getSubtype());
                    put("report_frequency", "Daily");
                    put("report_date", IsaCommonMethods.printDateTimeText(xDateTime, "yyyyMMdd"));
                    put("report_version", job.getVersion().toLowerCase().replace("v", ""));
                  }
                }));
  }

  @Deprecated
  public static WpSlzUnitOfWork genMlWpUnitOfWork(DateTime xReportDate, MlFw_ReportWrapper mlFwReportWrapper) {
    return new WpSlzUnitOfWork()
        .withReportDate(xReportDate)
        .withDsp(mlFwReportWrapper.getDsp().get())
        .withLicensor(mlFwReportWrapper.getLicensor().get())
        .withLicensorId(mlFwReportWrapper.getLicensor().getId())
        .withReportName(mlFwReportWrapper.getReport().getReportName())
        .withReportId(mlFwReportWrapper.getReport().getId())
        .withVersion(mlFwReportWrapper.getVersion().name());
  }

  @Experimental
  public static WpSlzUnitOfWork genNewMlWpUnitOfWork(MlFw_ReportWrapper mlFwReportWrapper) {
    return new WpSlzUnitOfWork()
        .withReportDate(mlFwReportWrapper.getDate())
        .withDsp(mlFwReportWrapper.getDsp().get())
        .withLicensor(mlFwReportWrapper.getLicensor().get())
        .withLicensorId(mlFwReportWrapper.getLicensor().getId())
        .withReportName(mlFwReportWrapper.getReport().getReportName())
        .withReportId(mlFwReportWrapper.getReport().getId())
        .withVersion(mlFwReportWrapper.getVersion().name());
  }

  public static MlCwEventRegular genMlCwEventRegular(
      OptionalConfig optionalConfig, DateTime uowReportDate) {
    return new MlCwEventRegular()
        .withTime(uowReportDate != null ? uowReportDate.plusDays(1) : DT_NOW.plusDays(1))
        .withOptionalConfig(optionalConfig);
  }

  public static MlCwEventReprocessing genMlCwEventReprocessing(
      WpSlzUnitOfWork wpSlzUnitOfWork, OptionalConfig optionalConfig) {
    return new MlCwEventReprocessing()
        .withTime(
            DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss")
                    .print(wpSlzUnitOfWork.getReportDate().plusDays(1))
                + "Z")
        .withReprocessing(
            Lists.newArrayList(
                new Reprocessing()
                    .withUowId(wpSlzUnitOfWork.getUowCode())
                    .withContext(
                        wpSlzUnitOfWork.getCompleteCriteriaSet().iterator().next().getContext())))
        .withOptionalConfig(optionalConfig);
  }

  public static MlPgEventRetrigger genMlCwEventRetrigger(EventRetriggerBody eventRetriggerBody) {
    return new MlPgEventRetrigger()
        .withRecords(
            Lists.newArrayList(
                new Record()
                    .withMessageId(UUID.randomUUID().toString())
                    .withBody(gson.toJson(eventRetriggerBody))));
  }

  public static MlPg_ContentStatus genMlPgContentStatus(MlPg_UnitOfWork mlPgUnitOfWork) {
    return new MlPg_ContentStatus()
        .withUnitOfWorkId(mlPgUnitOfWork.getId())
        .withContentName(mlPgUnitOfWork.getUnitOfWorkCode().replace("-", "_") + ".gz")
        .withContentStatus(EnPgContentStatus.MISSING.name())
        .withLatestJobId(
            String.format(
                "%s_%s_%s",
                mlPgUnitOfWork.getUnitOfWorkCode(),
                mlPgUnitOfWork.getId() != null ? mlPgUnitOfWork.getId() : 0,
                IsaCommonMethods.printDateTimeText(
                    mlPgUnitOfWork.getCreatedAt(), "yyyyMMdd'T'hh.mm.ss")))
        .withCreatedAt(mlPgUnitOfWork.getCreatedAt().plusMinutes(1))
        .withLastCheckedAt(mlPgUnitOfWork.getCreatedAt().plusMinutes(1))
        .withMetadataProcessStatus(EnPgMetadataProcessStatuses.NOT_QUEUED.name());
  }

  public static MlPg_UnitOfWork genMlPgUnitOfWork(WpSlzUnitOfWork wpSlzUnitOfWork, int hoursShift) {
    return new MlPg_UnitOfWork()
        .withUnitOfWorkCode(wpSlzUnitOfWork.getUowCode())
        .withReportDate(new Date(wpSlzUnitOfWork.getReportDate().getMillis()))
        .withReportId(
            Arrays.stream(EnDelphiLicensorItems.values())
                .filter(i -> i.get().equalsIgnoreCase(wpSlzUnitOfWork.getLicensor()))
                .collect(Collectors.toList())
                .get(0)
                .getId())
        .withLicensorId(
            Arrays.stream(EnDelphiLicensorItems.values())
                .filter(o -> o.name().equalsIgnoreCase(wpSlzUnitOfWork.getLicensor()))
                .collect(Collectors.toList())
                .get(0)
                .getId())
        .withVersion(wpSlzUnitOfWork.getVersion())
        .withActivityStatus(EnPgActivityStatuses.NOT_IN_PROGRESS.name())
        .withCompletenessStatus(EnPgCompletenessStatuses.ACTIVE.name())
        .withCreatedAt(wpSlzUnitOfWork.getReportDate().minusHours(hoursShift))
        .withNextRunAt(
            wpSlzUnitOfWork
                .getReportDate()
                .minusHours(hoursShift)
                .plusMinutes(
                    10)) // TODO by alexeyisakov @3/24/21#2:59 PM: Add scheduling time from config
        .withLastUpdatedAt(wpSlzUnitOfWork.getReportDate().minusHours(hoursShift))
        .withIsForceComplete(false)
        .withPriority(EnPgPriorities._5.get());
  }

  public static Object getResource(String locator) {
    return TEST_RESOURCES.get().get(locator);
  }

  public static List<String> fetchFromFs(String xResource) {
    return getRandomSlice(IsaCommonMethods.loadCsv2List(new File(xResource)));
  }

  public static List<String> getRandomSlice(List<String> list) {
    int size = list.size();
    int nextInt = size > 2 ? random.nextInt(size) : 0;
    if (nextInt > 2) {
      return list.subList(size - nextInt, size);
    } else {
      return list;
    }
  }
}
