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

package io.opentelemetry.javaagent.instrumentation.gwt;

import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;

import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.test.utils.PortUtils;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.time.Duration;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.Testcontainers;
import org.testcontainers.containers.BrowserWebDriverContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;

class GwtTest {

  @RegisterExtension
  private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();

  private static final Logger logger = LoggerFactory.getLogger(GwtTest.class);
  static int port;
  static Server server;
  static BrowserWebDriverContainer<?> browser;
  static URI address;

  static void startServer() throws Exception {
    port = PortUtils.findOpenPort();
    server = new Server(port);
    for (Connector connector : server.getConnectors()) {
      ((ServerConnector) connector).setHost("localhost");
    }

    WebAppContext webAppContext = new WebAppContext();
    webAppContext.setContextPath(getContextPath());
    webAppContext.setBaseResource(Resource.newResource(new File("build/testapp/web")));

    server.setHandler(webAppContext);
    server.start();
  }

  static void stopServer() throws Exception {
    server.stop();
    server.destroy();
  }

  @BeforeAll
  static void setup() throws Exception {
    startServer();

    Testcontainers.exposeHostPorts(port);

    browser =
        new BrowserWebDriverContainer<>()
            .withCapabilities(new ChromeOptions())
            .withLogConsumer(new Slf4jLogConsumer(logger));
    browser.start();

    address = new URI("http://host.testcontainers.internal:" + port + getContextPath() + "/");
  }

  @AfterAll
  static void cleanup() throws Exception {
    stopServer();
    browser.stop();
  }

  static final String getContextPath() {
    return "/xyz";
  }

  RemoteWebDriver getDriver() {
    RemoteWebDriver driver =
        new RemoteWebDriver(browser.getSeleniumAddress(), new ChromeOptions(), false);
    driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30));
    return driver;
  }

  @Test
  void testGwt() {
    RemoteWebDriver driver = getDriver();

    // fetch the test page
    driver.get(address.resolve("greeting.html").toString());

    driver.findElement(By.className("greeting.button"));
    testing.waitAndAssertSortedTraces(
        orderByRootSpanName("GET " + getContextPath() + "/*", "GET"),
        trace -> {
          // /xyz/greeting.html
          trace.hasSpansSatisfyingExactly(
              span ->
                  span.hasName("GET " + getContextPath() + "/*")
                      .hasKind(SpanKind.SERVER)
                      .hasNoParent());
        },
        trace -> {
          // /xyz/greeting/greeting.nocache.js
          trace.hasSpansSatisfyingExactly(
              span ->
                  span.hasName("GET " + getContextPath() + "/*")
                      .hasKind(SpanKind.SERVER)
                      .hasNoParent());
        },
        trace -> {
          // /xyz/greeting/1B105441581A8F41E49D5DF3FB5B55BA.cache.html
          trace.hasSpansSatisfyingExactly(
              span ->
                  span.hasName("GET " + getContextPath() + "/*")
                      .hasKind(SpanKind.SERVER)
                      .hasNoParent());
        },
        trace -> {
          // /favicon.ico
          trace.hasSpansSatisfyingExactly(
              span -> span.hasName("GET").hasKind(SpanKind.SERVER).hasNoParent());
        });

    testing.clearData();

    // click a button to trigger calling java code
    driver.findElement(By.className("greeting.button")).click();
    assertEquals(driver.findElement(By.className("message.received")).getText(), "Hello, Otel");

    testing.waitAndAssertTraces(
        trace ->
            trace
                .hasSize(2)
                .hasSpansSatisfyingExactly(
                    span ->
                        span.hasName("POST " + getContextPath() + "/greeting/greet")
                            .hasKind(SpanKind.SERVER)
                            .hasNoParent(),
                    span ->
                        span.hasName("test.gwt.shared.MessageService/sendMessage")
                            .hasKind(SpanKind.SERVER)
                            .hasParent(trace.getSpan(0))
                            .hasAttributesSatisfyingExactly(
                                equalTo(SemanticAttributes.RPC_SYSTEM, "gwt"),
                                equalTo(
                                    SemanticAttributes.RPC_SERVICE,
                                    "test.gwt.shared.MessageService"),
                                equalTo(SemanticAttributes.RPC_METHOD, "sendMessage"))));

    testing.clearData();

    // click a button to trigger calling java code
    driver.findElement(By.className("error.button")).click();
    assertEquals(driver.findElement(By.className("error.received")).getText(), "Error");

    testing.waitAndAssertTraces(
        trace ->
            trace
                .hasSize(2)
                .hasSpansSatisfyingExactly(
                    span ->
                        span.hasName("POST " + getContextPath() + "/greeting/greet")
                            .hasKind(SpanKind.SERVER)
                            .hasNoParent(),
                    span ->
                        span.hasName("test.gwt.shared.MessageService/sendMessage")
                            .hasKind(SpanKind.SERVER)
                            .hasParent(trace.getSpan(0))
                            .hasException(new IOException())
                            .hasAttributesSatisfyingExactly(
                                equalTo(SemanticAttributes.RPC_SYSTEM, "gwt"),
                                equalTo(
                                    SemanticAttributes.RPC_SERVICE,
                                    "test.gwt.shared.MessageService"),
                                equalTo(SemanticAttributes.RPC_METHOD, "sendMessage"))));

    driver.close();
  }
}
