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

package io.opentelemetry.javaagent.instrumentation.spring.gateway.v2_0;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
import io.opentelemetry.javaagent.bootstrap.internal.InstrumentationConfig;
import java.util.regex.Pattern;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.web.server.ServerWebExchange;

public final class ServerWebExchangeHelper {

  /** Route ID attribute key. */
  private static final AttributeKey<String> ROUTE_ID_ATTRIBUTE =
      AttributeKey.stringKey("spring-cloud-gateway.route.id");

  /** Route URI attribute key. */
  private static final AttributeKey<String> ROUTE_URI_ATTRIBUTE =
      AttributeKey.stringKey("spring-cloud-gateway.route.uri");

  /** Route order attribute key. */
  private static final AttributeKey<Long> ROUTE_ORDER_ATTRIBUTE =
      AttributeKey.longKey("spring-cloud-gateway.route.order");

  /** Route filter size attribute key. */
  private static final AttributeKey<Long> ROUTE_FILTER_SIZE_ATTRIBUTE =
      AttributeKey.longKey("spring-cloud-gateway.route.filter.size");

  private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES;

  static {
    CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
        InstrumentationConfig.get()
            .getBoolean(
                "otel.instrumentation.spring-cloud-gateway.experimental-span-attributes", false);
  }

  /* Regex for UUID */
  private static final Pattern UUID_REGEX =
      Pattern.compile(
          "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");

  private static final String INVALID_RANDOM_ROUTE_ID =
      "org.springframework.util.AlternativeJdkIdGenerator@";

  private ServerWebExchangeHelper() {}

  public static void extractAttributes(ServerWebExchange exchange, Context context) {
    // Record route info
    Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
    if (route != null && CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
      Span serverSpan = LocalRootSpan.fromContextOrNull(context);
      if (serverSpan == null) {
        return;
      }
      serverSpan.setAttribute(ROUTE_ID_ATTRIBUTE, route.getId());
      serverSpan.setAttribute(ROUTE_URI_ATTRIBUTE, route.getUri().toASCIIString());
      serverSpan.setAttribute(ROUTE_ORDER_ATTRIBUTE, route.getOrder());
      serverSpan.setAttribute(ROUTE_FILTER_SIZE_ATTRIBUTE, route.getFilters().size());
    }
  }

  public static String extractServerRoute(ServerWebExchange exchange) {
    Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
    if (route != null) {
      return convergeRouteId(route);
    }
    return null;
  }

  /**
   * To avoid high cardinality, we ignore random UUID generated by Spring Cloud Gateway. Spring
   * Cloud Gateway generate invalid random routeID, and it is fixed until 3.1.x
   *
   * @see <a
   *     href="https://github.com/spring-cloud/spring-cloud-gateway/commit/5002fe2e0a2825ef47dd667cade37b844c276cf6"/>
   */
  private static String convergeRouteId(Route route) {
    String routeId = route.getId();
    if (routeId == null || routeId.isEmpty()) {
      return null;
    }
    if (UUID_REGEX.matcher(routeId).matches()) {
      return null;
    }
    if (routeId.startsWith(INVALID_RANDOM_ROUTE_ID)) {
      return null;
    }
    return routeId;
  }
}
