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

package io.opentelemetry.javaagent.instrumentation.play.v2_6;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteHolder;
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpRouteSource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.annotation.Nullable;
import play.api.mvc.Request;
import play.api.routing.HandlerDef;
import play.libs.typedmap.TypedKey;
import play.routing.Router;
import scala.Option;

public final class Play26Singletons {

  private static final String SPAN_NAME = "play.request";
  private static final Instrumenter<Void, Void> INSTRUMENTER =
      Instrumenter.<Void, Void>builder(
              GlobalOpenTelemetry.get(), "io.opentelemetry.play-mvc-2.6", s -> SPAN_NAME)
          .buildInstrumenter();

  @Nullable private static final Method typedKeyGetUnderlying;

  static {
    Method typedKeyGetUnderlyingCheck = null;
    try {
      // This method was added in Play 2.6.8
      typedKeyGetUnderlyingCheck = TypedKey.class.getMethod("asScala");
    } catch (NoSuchMethodException ignored) {
      // Ignore
    }
    // Fallback
    if (typedKeyGetUnderlyingCheck == null) {
      try {
        typedKeyGetUnderlyingCheck = TypedKey.class.getMethod("underlying");
      } catch (NoSuchMethodException ignored) {
        // Ignore
      }
    }
    typedKeyGetUnderlying = typedKeyGetUnderlyingCheck;
  }

  public static Instrumenter<Void, Void> instrumenter() {
    return INSTRUMENTER;
  }

  public static void updateSpan(Context context, Request<?> request) {
    String route = getRoute(request);
    if (route == null) {
      return;
    }

    Span.fromContext(context).updateName(route);
    HttpRouteHolder.updateHttpRoute(context, HttpRouteSource.CONTROLLER, route);
  }

  private static String getRoute(Request<?> request) {
    if (request != null) {
      // more about routes here:
      // https://github.com/playframework/playframework/blob/master/documentation/manual/releases/release26/migration26/Migration26.md
      Option<HandlerDef> defOption = null;
      if (typedKeyGetUnderlying != null) { // Should always be non-null but just to make sure
        try {
          @SuppressWarnings("unchecked")
          play.api.libs.typedmap.TypedKey<HandlerDef> handlerDef =
              (play.api.libs.typedmap.TypedKey<HandlerDef>)
                  typedKeyGetUnderlying.invoke(Router.Attrs.HANDLER_DEF);
          defOption = request.attrs().get(handlerDef);
        } catch (IllegalAccessException | InvocationTargetException ignored) {
          // Ignore
        }
      }
      if (defOption != null && !defOption.isEmpty()) {
        return defOption.get().path();
      }
    }
    return null;
  }

  private Play26Singletons() {}
}
