// Code generated by gotmpl. DO NOT MODIFY.
// source: internal/shared/semconv/env.go.tmpl

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

package semconv // import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv"

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"strings"
	"sync"

	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/metric"
)

// OTelSemConvStabilityOptIn is an environment variable.
// That can be set to "http/dup" to keep getting the old HTTP semantic conventions.
const OTelSemConvStabilityOptIn = "OTEL_SEMCONV_STABILITY_OPT_IN"

type ResponseTelemetry struct {
	StatusCode int
	ReadBytes  int64
	ReadError  error
	WriteBytes int64
	WriteError error
}

type HTTPServer struct {
	duplicate bool

	// Old metrics
	requestBytesCounter  metric.Int64Counter
	responseBytesCounter metric.Int64Counter
	serverLatencyMeasure metric.Float64Histogram

	// New metrics
	requestBodySizeHistogram  metric.Int64Histogram
	responseBodySizeHistogram metric.Int64Histogram
	requestDurationHistogram  metric.Float64Histogram
}

// RequestTraceAttrs returns trace attributes for an HTTP request received by a
// server.
//
// The server must be the primary server name if it is known. For example this
// would be the ServerName directive
// (https://httpd.apache.org/docs/2.4/mod/core.html#servername) for an Apache
// server, and the server_name directive
// (http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name) for an
// nginx server. More generically, the primary server name would be the host
// header value that matches the default virtual host of an HTTP server. It
// should include the host identifier and if a port is used to route to the
// server that port identifier should be included as an appropriate port
// suffix.
//
// If the primary server name is not known, server should be an empty string.
// The req Host will be used to determine the server instead.
func (s HTTPServer) RequestTraceAttrs(server string, req *http.Request, opts RequestTraceAttrsOpts) []attribute.KeyValue {
	attrs := CurrentHTTPServer{}.RequestTraceAttrs(server, req, opts)
	if s.duplicate {
		return OldHTTPServer{}.RequestTraceAttrs(server, req, attrs)
	}
	return attrs
}

func (s HTTPServer) NetworkTransportAttr(network string) []attribute.KeyValue {
	if s.duplicate {
		return []attribute.KeyValue{
			OldHTTPServer{}.NetworkTransportAttr(network),
			CurrentHTTPServer{}.NetworkTransportAttr(network),
		}
	}
	return []attribute.KeyValue{
		CurrentHTTPServer{}.NetworkTransportAttr(network),
	}
}

// ResponseTraceAttrs returns trace attributes for telemetry from an HTTP response.
//
// If any of the fields in the ResponseTelemetry are not set the attribute will be omitted.
func (s HTTPServer) ResponseTraceAttrs(resp ResponseTelemetry) []attribute.KeyValue {
	attrs := CurrentHTTPServer{}.ResponseTraceAttrs(resp)
	if s.duplicate {
		return OldHTTPServer{}.ResponseTraceAttrs(resp, attrs)
	}
	return attrs
}

// Route returns the attribute for the route.
func (s HTTPServer) Route(route string) attribute.KeyValue {
	return CurrentHTTPServer{}.Route(route)
}

// Status returns a span status code and message for an HTTP status code
// value returned by a server. Status codes in the 400-499 range are not
// returned as errors.
func (s HTTPServer) Status(code int) (codes.Code, string) {
	if code < 100 || code >= 600 {
		return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
	}
	if code >= 500 {
		return codes.Error, ""
	}
	return codes.Unset, ""
}

type ServerMetricData struct {
	ServerName   string
	ResponseSize int64

	MetricData
	MetricAttributes
}

type MetricAttributes struct {
	Req                  *http.Request
	StatusCode           int
	AdditionalAttributes []attribute.KeyValue
}

type MetricData struct {
	RequestSize int64

	// The request duration, in milliseconds
	ElapsedTime float64
}

var (
	metricAddOptionPool = &sync.Pool{
		New: func() interface{} {
			return &[]metric.AddOption{}
		},
	}

	metricRecordOptionPool = &sync.Pool{
		New: func() interface{} {
			return &[]metric.RecordOption{}
		},
	}
)

func (s HTTPServer) RecordMetrics(ctx context.Context, md ServerMetricData) {
	if s.requestDurationHistogram != nil && s.requestBodySizeHistogram != nil && s.responseBodySizeHistogram != nil {
		attributes := CurrentHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
		o := metric.WithAttributeSet(attribute.NewSet(attributes...))
		recordOpts := metricRecordOptionPool.Get().(*[]metric.RecordOption)
		*recordOpts = append(*recordOpts, o)
		s.requestBodySizeHistogram.Record(ctx, md.RequestSize, *recordOpts...)
		s.responseBodySizeHistogram.Record(ctx, md.ResponseSize, *recordOpts...)
		s.requestDurationHistogram.Record(ctx, md.ElapsedTime/1000.0, o)
		*recordOpts = (*recordOpts)[:0]
		metricRecordOptionPool.Put(recordOpts)
	}

	if s.duplicate && s.requestBytesCounter != nil && s.responseBytesCounter != nil && s.serverLatencyMeasure != nil {
		attributes := OldHTTPServer{}.MetricAttributes(md.ServerName, md.Req, md.StatusCode, md.AdditionalAttributes)
		o := metric.WithAttributeSet(attribute.NewSet(attributes...))
		addOpts := metricAddOptionPool.Get().(*[]metric.AddOption)
		*addOpts = append(*addOpts, o)
		s.requestBytesCounter.Add(ctx, md.RequestSize, *addOpts...)
		s.responseBytesCounter.Add(ctx, md.ResponseSize, *addOpts...)
		s.serverLatencyMeasure.Record(ctx, md.ElapsedTime, o)
		*addOpts = (*addOpts)[:0]
		metricAddOptionPool.Put(addOpts)
	}
}

// hasOptIn returns true if the comma-separated version string contains the
// exact optIn value.
func hasOptIn(version, optIn string) bool {
	for _, v := range strings.Split(version, ",") {
		if strings.TrimSpace(v) == optIn {
			return true
		}
	}
	return false
}

func NewHTTPServer(meter metric.Meter) HTTPServer {
	env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn))
	duplicate := hasOptIn(env, "http/dup")
	server := HTTPServer{
		duplicate: duplicate,
	}
	server.requestBodySizeHistogram, server.responseBodySizeHistogram, server.requestDurationHistogram = CurrentHTTPServer{}.createMeasures(meter)
	if duplicate {
		server.requestBytesCounter, server.responseBytesCounter, server.serverLatencyMeasure = OldHTTPServer{}.createMeasures(meter)
	}
	return server
}

type HTTPClient struct {
	duplicate bool

	// old metrics
	requestBytesCounter  metric.Int64Counter
	responseBytesCounter metric.Int64Counter
	latencyMeasure       metric.Float64Histogram

	// new metrics
	requestBodySize metric.Int64Histogram
	requestDuration metric.Float64Histogram
}

func NewHTTPClient(meter metric.Meter) HTTPClient {
	env := strings.ToLower(os.Getenv(OTelSemConvStabilityOptIn))
	duplicate := hasOptIn(env, "http/dup")
	client := HTTPClient{
		duplicate: duplicate,
	}
	client.requestBodySize, client.requestDuration = CurrentHTTPClient{}.createMeasures(meter)
	if duplicate {
		client.requestBytesCounter, client.responseBytesCounter, client.latencyMeasure = OldHTTPClient{}.createMeasures(meter)
	}

	return client
}

// RequestTraceAttrs returns attributes for an HTTP request made by a client.
func (c HTTPClient) RequestTraceAttrs(req *http.Request) []attribute.KeyValue {
	attrs := CurrentHTTPClient{}.RequestTraceAttrs(req)
	if c.duplicate {
		return OldHTTPClient{}.RequestTraceAttrs(req, attrs)
	}
	return attrs
}

// ResponseTraceAttrs returns metric attributes for an HTTP request made by a client.
func (c HTTPClient) ResponseTraceAttrs(resp *http.Response) []attribute.KeyValue {
	attrs := CurrentHTTPClient{}.ResponseTraceAttrs(resp)
	if c.duplicate {
		return OldHTTPClient{}.ResponseTraceAttrs(resp, attrs)
	}
	return attrs
}

func (c HTTPClient) Status(code int) (codes.Code, string) {
	if code < 100 || code >= 600 {
		return codes.Error, fmt.Sprintf("Invalid HTTP status code %d", code)
	}
	if code >= 400 {
		return codes.Error, ""
	}
	return codes.Unset, ""
}

func (c HTTPClient) ErrorType(err error) attribute.KeyValue {
	return CurrentHTTPClient{}.ErrorType(err)
}

type MetricOpts struct {
	measurement metric.MeasurementOption
	addOptions  metric.AddOption
}

func (o MetricOpts) MeasurementOption() metric.MeasurementOption {
	return o.measurement
}

func (o MetricOpts) AddOptions() metric.AddOption {
	return o.addOptions
}

func (c HTTPClient) MetricOptions(ma MetricAttributes) map[string]MetricOpts {
	opts := map[string]MetricOpts{}

	attributes := CurrentHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
	set := metric.WithAttributeSet(attribute.NewSet(attributes...))
	opts["new"] = MetricOpts{
		measurement: set,
		addOptions:  set,
	}

	if c.duplicate {
		attributes := OldHTTPClient{}.MetricAttributes(ma.Req, ma.StatusCode, ma.AdditionalAttributes)
		set := metric.WithAttributeSet(attribute.NewSet(attributes...))
		opts["old"] = MetricOpts{
			measurement: set,
			addOptions:  set,
		}
	}

	return opts
}

func (s HTTPClient) RecordMetrics(ctx context.Context, md MetricData, opts map[string]MetricOpts) {
	if s.requestBodySize == nil || s.requestDuration == nil {
		// This will happen if an HTTPClient{} is used instead of NewHTTPClient().
		return
	}

	s.requestBodySize.Record(ctx, md.RequestSize, opts["new"].MeasurementOption())
	s.requestDuration.Record(ctx, md.ElapsedTime/1000, opts["new"].MeasurementOption())

	if s.duplicate {
		s.requestBytesCounter.Add(ctx, md.RequestSize, opts["old"].AddOptions())
		s.latencyMeasure.Record(ctx, md.ElapsedTime, opts["old"].MeasurementOption())
	}
}

func (s HTTPClient) RecordResponseSize(ctx context.Context, responseData int64, opts map[string]MetricOpts) {
	if s.responseBytesCounter == nil {
		// This will happen if an HTTPClient{} is used instead of NewHTTPClient().
		return
	}

	s.responseBytesCounter.Add(ctx, responseData, opts["old"].AddOptions())
}

func (s HTTPClient) TraceAttributes(host string) []attribute.KeyValue {
	attrs := CurrentHTTPClient{}.TraceAttributes(host)
	if s.duplicate {
		return OldHTTPClient{}.TraceAttributes(host, attrs)
	}

	return attrs
}
