/*
 * Copyright © 2021 DataSQRL (contact@datasqrl.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.datasqrl.container.testing;

import static org.assertj.core.api.Assertions.assertThat;

import java.time.Duration;
import java.util.regex.Pattern;
import lombok.SneakyThrows;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

public class VertxContainerIT {

  @RegisterExtension static SqrlContainerExtension sqrl = new SqrlContainerExtension("udf");

  @Test
  @SneakyThrows
  void givenUdfScript_whenCompiledAndServerStarted_thenApiRespondsCorrectly() {
    sqrl.compileAndStartServer();

    try (var response = sqrl.executeGraphQLQuery("{\"query\":\"query { __typename }\"}")) {
      sqrl.validateBasicGraphQLResponse(response);
    }
  }

  @Test
  @SneakyThrows
  void givenTraceRequestsEnabled_whenGraphQLRequestSent_thenRequestBodyLoggedInServerLogs() {
    sqrl.compileAndStartServer();

    var testQuery = "{\"query\":\"query { __typename }\"}";
    try (var response = sqrl.executeGraphQLQuery(testQuery)) {
      sqrl.validateBasicGraphQLResponse(response);
    }

    assertTraceLogContains("ReqBody:");
  }

  @Test
  @SneakyThrows
  void givenTraceRequestsEnabled_whenHttpRequestsMade_thenLogPathAndBody() {
    sqrl.compileAndStartServer();

    var testQuery = "{\"query\":\"query { __typename }\"}";
    try (var response = sqrl.executeGraphQLQuery(testQuery)) {
      sqrl.validateBasicGraphQLResponse(response);
    }

    assertTraceLogContains("ReqBody:", testQuery, "/v1/graphql");

    var randomPath = "/random/test/path/12345";
    var testBody = "{\"test\":\"data\",\"random\":\"content\"}";

    var randomRequest = new HttpPost(sqrl.getBaseUrl() + randomPath);
    randomRequest.setEntity(new StringEntity(testBody, ContentType.APPLICATION_JSON));

    try (var randomResponse = sqrl.getHttpClient().execute(randomRequest)) {
      EntityUtils.consume(randomResponse.getEntity());
    }

    assertTraceLogContains("ReqBody:", testBody, randomPath);

    String graphiql;
    try (var graphiqlResponse =
        sqrl.getHttpClient().execute(new HttpGet(sqrl.getBaseUrl() + "/v1/graphiql/"))) {
      assertThat(graphiqlResponse.getStatusLine().getStatusCode()).isEqualTo(200);
      assertThat(graphiqlResponse.getFirstHeader("Content-Type").getValue()).contains("text/html");
      graphiql = EntityUtils.toString(graphiqlResponse.getEntity());
    }

    assertThat(graphiql)
        .containsIgnoringCase("<!DOCTYPE html>")
        .contains("<html lang=\"en\">")
        .contains("<title>GraphiQL</title>")
        .contains("window.VERTX_GRAPHIQL_CONFIG")
        .contains("\"httpEnabled\":true")
        .contains("\"graphQLUri\":\"/v1/graphql\"")
        .contains("\"graphQLWSEnabled\":true")
        .contains("\"graphQLWSUri\":\"/v1/graphql\"");

    assertTraceLogContains("GET /v1/graphiql/");
  }

  @Test
  @SneakyThrows
  void givenTraceRequestsEnabled_whenRequestsMade_thenLogsContainRequestPrefix() {
    sqrl.compileAndStartServer();

    var testQuery = "{\"query\":\"query { __typename }\"}";
    try (var response = sqrl.executeGraphQLQuery(testQuery)) {
      sqrl.validateBasicGraphQLResponse(response);
    }

    // Verify that log entries contain request ID prefix and all key elements in single line
    assertTraceLogMatchesPattern("\\[REQ-\\d+-\\d+\\] POST /v1/graphql \\| Status: \\d+");
    assertTraceLogMatchesPattern("ReqBody: \\d+ bytes");
    assertTraceLogMatchesPattern("RespBody: \\d+ bytes");
  }

  @SneakyThrows
  private void assertTraceLogContains(String... expectedPatterns) {
    // Wait for log file to be created and populated with expected content
    Awaitility.await()
        .atMost(Duration.ofSeconds(10))
        .pollInterval(Duration.ofMillis(500))
        .untilAsserted(
            () -> {
              assertThat(traceLogs()).contains(expectedPatterns);
            });
  }

  @SneakyThrows
  private void assertTraceLogMatchesPattern(String regexPattern) {
    // Wait for log file to be created and populated with expected pattern
    Awaitility.await()
        .atMost(Duration.ofSeconds(10))
        .pollInterval(Duration.ofMillis(500))
        .untilAsserted(
            () -> {
              var logs = traceLogs();
              var pattern = Pattern.compile(regexPattern);
              assertThat(pattern.matcher(logs).find())
                  .as("Expected log pattern '%s' not found in logs", regexPattern)
                  .isTrue();
            });
  }

  @SneakyThrows
  private String traceLogs() {
    // Get the log file from the container
    var logPath = "/opt/sqrl/logs/request-trace.log";

    // Copy log file from container to temp location and read it
    var result = sqrl.getServerContainer().execInContainer("cat", logPath);
    if (result.getExitCode() != 0) {
      throw new RuntimeException("Failed to read request trace log: " + result.getStderr());
    }

    return result.getStdout();
  }
}
