/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.javaagent.instrumentation.tomcat.v10_0;

import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.AUTH_REQUIRED;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT;
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS;
import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.api.internal.HttpConstants;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions;
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
import java.io.File;
import java.nio.file.Files;
import java.util.UUID;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.junit.jupiter.api.extension.RegisterExtension;

class TomcatAsyncTest extends AbstractHttpServerTest<Tomcat> {

  @RegisterExtension
  static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent();

  @Override
  public Tomcat setupServer() throws Exception {
    Tomcat tomcatServer = new Tomcat();
    File baseDir = Files.createTempDirectory("tomcat").toFile();
    baseDir.deleteOnExit();
    tomcatServer.setBaseDir(baseDir.getAbsolutePath());
    tomcatServer.setPort(port);
    tomcatServer.getConnector().setEnableLookups(true); // get localhost instead of 127.0.0.1

    File applicationDir = new File(baseDir, "/webapps/ROOT");
    if (!applicationDir.exists()) {
      applicationDir.mkdirs();
      applicationDir.deleteOnExit();
    }

    Context servletContext =
        tomcatServer.addWebapp(getContextPath(), applicationDir.getAbsolutePath());
    // Speed up startup by disabling jar scanning:
    servletContext.getJarScanner().setJarScanFilter((jarScanType, jarName) -> false);

    setupServlets(servletContext);
    tomcatServer.start();
    return tomcatServer;
  }

  protected void setupServlets(Context context) throws Exception {
    Class<? extends Servlet> servlet = servlet();

    addServlet(context, SUCCESS.getPath(), servlet);
    addServlet(context, QUERY_PARAM.getPath(), servlet);
    addServlet(context, ERROR.getPath(), servlet);
    addServlet(context, EXCEPTION.getPath(), servlet);
    addServlet(context, REDIRECT.getPath(), servlet);
    addServlet(context, AUTH_REQUIRED.getPath(), servlet);
    addServlet(context, CAPTURE_HEADERS.getPath(), servlet);
    addServlet(context, INDEXED_CHILD.getPath(), servlet);
  }

  void addServlet(Context servletContext, String path, Class<? extends Servlet> servlet)
      throws Exception {
    String name = UUID.randomUUID().toString();
    Tomcat.addServlet(servletContext, name, servlet.getDeclaredConstructor().newInstance());
    servletContext.addServletMappingDecoded(path, name);
  }

  Class<? extends Servlet> servlet() {
    return AsyncServlet.class;
  }

  @Override
  public void stopServer(Tomcat server) throws LifecycleException {
    server.stop();
    server.destroy();
  }

  @Override
  protected void configure(HttpServerTestOptions options) {
    options.setContextPath("/tomcat-context");

    options.setExpectedHttpRoute(
        (ServerEndpoint endpoint, String method) -> {
          if (method.equals(HttpConstants._OTHER)) {
            return getContextPath() + endpoint.getPath();
          }
          if (endpoint.equals(NOT_FOUND)) {
            return getContextPath() + "/*";
          }
          return super.expectedHttpRoute(endpoint, method);
        });

    options.setExpectedException(new ServletException(EXCEPTION.getBody()));

    options.setHasResponseSpan(endpoint -> endpoint == NOT_FOUND || endpoint == REDIRECT);

    // with async requests the span is started in one instrumentation (server instrumentation)
    // but ended from another (servlet instrumentation)
    options.setMetricsInstrumentationName(() -> "io.opentelemetry.servlet-5.0");
  }

  @Override
  protected SpanDataAssert assertResponseSpan(
      SpanDataAssert span, String method, ServerEndpoint endpoint) {
    if (endpoint.equals(REDIRECT)) {
      span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendRedirect"));
    } else if (endpoint.equals(NOT_FOUND)) {
      span.satisfies(spanData -> assertThat(spanData.getName()).endsWith(".sendError"));
    }
    span.hasKind(SpanKind.INTERNAL).hasAttributesSatisfying(Attributes::isEmpty);
    return span;
  }
}
