package io.delphi.qa.auto.awsmfrw.managers.helpers.api.rest;

import io.delphi.qa.auto.awsmfrw.common_baby.IsaCommonMethods;
import io.delphi.qa.auto.awsmfrw.common_baby.IsaConstants;
import io.qameta.allure.Allure;
import io.qameta.allure.AllureLifecycle;
import io.qameta.allure.Step;
import io.qameta.allure.restassured.AllureRestAssured;
import io.restassured.RestAssured;
import io.restassured.filter.Filter;
import io.restassured.filter.log.LogDetail;
import io.restassured.http.ContentType;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import io.restassured.response.ValidatableResponse;
import io.restassured.specification.RequestSpecification;
import lombok.*;
import org.hamcrest.Matchers;

import java.util.*;
import java.util.stream.Collectors;

/*
Helper base class for API tests based on RestAssured
 * */
public class AbstractRestAssuredClient implements IsaConstants {

//  public RequestSpecification getBaseSpec() {
//    return baseSpec;
//  }

  public RequestSpecification baseSpec = null;
  private Response response;
  private ValidatableResponse validatableResponse;
  private final ThreadLocal<CustomRequestSpec> rqSpecs = new ThreadLocal<>();

  public CustomRequestSpec getRqSpec() {
    return rqSpecs.get();
  }

  public AbstractRestAssuredClient withRestDesc() {
    CustomRequestSpec bsRestAssured = rqSpecs.get();
    String path = bsRestAssured.getPath();
    Map<String, ?> pathParam = bsRestAssured.getPathParam();
    String method = bsRestAssured.getMethod();
    String queryParams =
        bsRestAssured.getQueryParams().size() > 0
            ? "?"
                + bsRestAssured.getQueryParams().entrySet().stream()
                    .map(entry -> String.format("%s=%s", entry.getKey(), entry.getValue()))
                    .collect(Collectors.joining("&"))
            : "";
    String url =
        pathParam != null
            ? IsaCommonMethods.resolvePattern(
                path,
                new HashMap<String, String>() {
                  {
                    put("path_param", (String) pathParam.get("path_param"));
                  }
                })
            : path;
    String description =
        String.format(
            "[%s] %s: %s%s",
            bsRestAssured.getStatusCode(), method.toUpperCase(Locale.ROOT), url, queryParams);
    AllureLifecycle lifecycle = Allure.getLifecycle();
    lifecycle.updateTestCase(
        testResult ->
            testResult.setLabels(
                testResult.getLabels().stream()
                    .filter(
                        o -> !o.getName().equals("testMethod") || o.setValue(description) != null)
                    .collect(Collectors.toList())));
    return this;
  }

  public AbstractRestAssuredClient withEncoding() {
    this.baseSpec.urlEncodingEnabled(true);
    return this;
  }

  @Getter
  @ToString
  @EqualsAndHashCode
  @AllArgsConstructor
  @NoArgsConstructor
  public static class CustomRequestSpec {
    private Map<String, ?> headers = new HashMap<>();
    private String statusCode;
    private String method;
    private Map<String, ?> cookies = new HashMap<>();
    private List bodies = new ArrayList<>();
    private String body;
    private String path;
    private Map<String, ?> pathParam = new HashMap<>();
    private Map<String, ?> queryParams = new HashMap<>();
    private List<Map<String, String>> expectedCount = new ArrayList<>();
    private Map<String, ?> formParams = new HashMap<>();
    private Map<String, ?> multiPart = new HashMap<>();
    private List filters = new ArrayList<>();

    public CustomRequestSpec withStatusCodes(String xStatusCodesText) {
      this.statusCode = xStatusCodesText;
      return this;
    }

    public CustomRequestSpec withBodies(List xBodies) {
      this.bodies = xBodies;
      return this;
    }

    public CustomRequestSpec withBody(String xBody) {
      this.body = xBody;
      return this;
    }

    public CustomRequestSpec withPath(String xPath) {
      this.path = xPath;
      return this;
    }

    public CustomRequestSpec withPathParam(Map<String, ?> xPathParam) {
      this.pathParam = xPathParam;
      return this;
    }

    public CustomRequestSpec withHeaders(Map<String, ?> xHeaders) {
      this.headers = xHeaders;
      return this;
    }

    public CustomRequestSpec withQueryParams(Map<String, ?> xQueryParams) {
      this.queryParams = xQueryParams;
      return this;
    }

    public CustomRequestSpec withMethod(String xMethod) {
      this.method = xMethod;
      return this;
    }
  }

  public AbstractRestAssuredClient withRequestSpecification(CustomRequestSpec rqSpec) {
    rqSpecs.set(rqSpec);
    return this;
  }

  public String getStatusCode() {
    return rqSpecs.get().getStatusCode();
  }

  public AbstractRestAssuredClient call() {
    return this.withHeaders(rqSpecs.get().getHeaders())
        .withPathParams(rqSpecs.get().getPathParam())
        .withQueryParams(rqSpecs.get().getQueryParams())
        .withBodies(rqSpecs.get().getBodies())
        .exec(rqSpecs.get().getMethod(), rqSpecs.get().getPath())
        .withThen();
  }

  public AbstractRestAssuredClient withExec(String xMethod, String xPath) {
    baseSpec.request(xMethod, xPath);
    return this;
  }

  public AbstractRestAssuredClient() {
    AllureRestAssured allureRestAssured = new AllureRestAssured();
    allureRestAssured.setRequestTemplate("http-request.ftl");
    allureRestAssured.setResponseTemplate("http-response.ftl");
    baseSpec = RestAssured.given().contentType(ContentType.JSON).filter(allureRestAssured);
  }

  protected void iSaidLogIt() {
    if (envConfig.isLogAll()) {
      baseSpec.log().all(envConfig.isLogPretty());
    }
    baseSpec.log().ifValidationFails(LogDetail.ALL);
  }

  @Step("API::REST: add bodies")
  public AbstractRestAssuredClient withBodies(List<?> bodies) {
    if (bodies != null) bodies.forEach(baseSpec::body);
    return this;
  }

  @Step("API::REST: add body")
  public AbstractRestAssuredClient withBody(String body) {
    if (body != null) baseSpec.body(body);
    return this;
  }

  @Step("API::REST: call DELETE method")
  public Response execDelete(String pth) {
    return baseSpec.delete(pth);
  }

  @Step("API::REST: add form params")
  public AbstractRestAssuredClient withFormParams(Map<String, ?> formParams) {
    formParams.forEach(baseSpec::formParam);
    return this;
  }

  @Step("API::REST: call GET method")
  public AbstractRestAssuredClient execGet(String path) {
    response = baseSpec.request().when().get(path);
    return this;
  }

  @Step("API::REST: call method {0}")
  public AbstractRestAssuredClient exec(String method, String path) {
    response = baseSpec.request(method, path);
    return this;
  }

  @Step("API::REST: call GET method")
  public AbstractRestAssuredClient execGet() {
    response = baseSpec.when().get();
    return this;
  }

  @Step("API::REST: add headers")
  public AbstractRestAssuredClient withHeaders(Map<String, ?> headers) {
    headers.forEach(baseSpec::header);
    return this;
  }

  @Step("API::REST: set logging on")
  public ValidatableResponse withValidatableRs() {
    return this.validatableResponse;
  }

  @Step("API::REST: set logging on")
  public AbstractRestAssuredClient withRsLog() {
    if (envConfig.isLogAll()) this.validatableResponse.log().all(envConfig.isLogPretty());
    return this;
  }

  @Step("API::REST: assert status code")
  public AbstractRestAssuredClient assertStatusCode() {
    String statusCodesText = rqSpecs.get().getStatusCode();
    String[] split = statusCodesText.split("Or");
    if (split.length > 1) {
      this.validatableResponse.statusCode(
          Matchers.anyOf(
              Matchers.is(Integer.parseInt(split[0])), Matchers.is(Integer.parseInt(split[1]))));
    } else {
      this.validatableResponse.statusCode(Matchers.is(Integer.parseInt(split[0])));
    }
    return this;
  }

  @Step("API::REST: assert count > 0")
  public AbstractRestAssuredClient assertCount() {
    if (envConfig.isDeepAssert()) {
      //      this.validatableResponse.body("count > 0", Matchers.is(true));
    }
    return this;
  }

  @Step("API::REST: assert count > 0")
  public AbstractRestAssuredClient assertCountFromDb() {
    this.validatableResponse.body("count", Matchers.equalTo(getRqSpec().getExpectedCount().size()));
    return this;
  }

  @Step("API::REST: assert SLA - rs_time")
  public AbstractRestAssuredClient assertSlaRsTime(Long lngSlaRsTime) {
    if (envConfig.isDeepAssert()) {
      this.validatableResponse.time(Matchers.lessThan(lngSlaRsTime));
    }
    return this;
  }

  @Step("API::REST: set logging off")
  public ValidatableResponse withOutLog() {
    return this.validatableResponse;
  }

  @Step("API::REST: add path params")
  public AbstractRestAssuredClient withPathParams(Map<String, ?> pathParams) {
    if (pathParams != null) baseSpec.pathParams(pathParams);
    return this;
  }

  @Step("API::REST: all POST method")
  public AbstractRestAssuredClient execPost(String path) {
    response = baseSpec.when().post(path);
    return this;
  }

  @Step("API::REST: validate response")
  public AbstractRestAssuredClient withThen() {
    validatableResponse = response.then();
    return this;
  }

  @Step("API::REST: validate response")
  public ExtractableResponse<Response> withExtract() {
    return response.then().extract();
  }

  @Step("API::REST: call PUT method")
  public Response execPut(String pth) {
    response = baseSpec.put(pth);
    return response;
  }

  @Step("API::REST: add query params")
  public AbstractRestAssuredClient withQueryParams(Map<String, ?> queryParams) {
    queryParams.forEach(baseSpec::queryParam);
    return this;
  }

  @Step("API::REST: add filters")
  public AbstractRestAssuredClient withFilters(List<Filter> filters) {
    filters.forEach(baseSpec::filter);
    return this;
  }

  @Step("API::REST: add multi-part")
  public AbstractRestAssuredClient withMultiPart(HashMap<String, ?> multiPart) {
    multiPart.forEach(baseSpec::multiPart);
    return this;
  }
}
