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

package metric_test

import (
	"context"
	"fmt"
	"log"
	"regexp"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/sdk/instrumentation"
	"go.opentelemetry.io/otel/sdk/metric"
	"go.opentelemetry.io/otel/sdk/metric/exemplar"
	"go.opentelemetry.io/otel/sdk/resource"
	semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
)

// To enable metrics in your application using the SDK,
// you'll need to have an initialized [MeterProvider]
// that will let you create a [go.opentelemetry.io/otel/metric.Meter].
//
// Here's how you might initialize a metrics provider.
func Example() {
	// Create resource.
	res, err := resource.Merge(resource.Default(),
		resource.NewWithAttributes(semconv.SchemaURL,
			semconv.ServiceName("my-service"),
			semconv.ServiceVersion("0.1.0"),
		))
	if err != nil {
		log.Fatalln(err)
	}

	// This reader is used as a stand-in for a reader that will actually export
	// data. See https://pkg.go.dev/go.opentelemetry.io/otel/exporters for
	// exporters that can be used as or with readers.
	reader := metric.NewManualReader()

	// Create a meter provider.
	// You can pass this instance directly to your instrumented code if it
	// accepts a MeterProvider instance.
	meterProvider := metric.NewMeterProvider(
		metric.WithResource(res),
		metric.WithReader(reader),
		metric.WithCardinalityLimit(2000),
	)

	// Handle shutdown properly so that nothing leaks.
	defer func() {
		err := meterProvider.Shutdown(context.Background())
		if err != nil {
			log.Fatalln(err)
		}
	}()

	// Register as global meter provider so that it can be used via otel.Meter
	// and accessed using otel.GetMeterProvider.
	// Most instrumentation libraries use the global meter provider as default.
	// If the global meter provider is not set then a no-op implementation
	// is used, which fails to generate data.
	otel.SetMeterProvider(meterProvider)
}

func ExampleView() {
	// The NewView function provides convenient creation of common Views
	// construction. However, it is limited in what it can create.
	//
	// When NewView is not able to provide the functionally needed, a custom
	// View can be constructed directly. Here a custom View is constructed that
	// uses Go's regular expression matching to ensure all data stream names
	// have a suffix of the units it uses.

	re := regexp.MustCompile(`[._](ms|byte)$`)
	var view metric.View = func(i metric.Instrument) (metric.Stream, bool) {
		// In a custom View function, you need to explicitly copy
		// the name, description, and unit.
		s := metric.Stream{Name: i.Name, Description: i.Description, Unit: i.Unit}
		// Any instrument that does not have a unit suffix defined, but has a
		// dimensional unit defined, update the name with a unit suffix.
		if re.MatchString(i.Name) {
			return s, false
		}
		switch i.Unit {
		case "ms":
			s.Name += ".ms"
		case "By":
			s.Name += ".byte"
		default:
			return s, false
		}
		return s, true
	}

	// The created view can then be registered with the OpenTelemetry metric
	// SDK using the WithView option.
	_ = metric.NewMeterProvider(
		metric.WithView(view),
	)

	// Below is an example of how the view will
	// function in the SDK for certain instruments.
	stream, _ := view(metric.Instrument{
		Name: "computation.time.ms",
		Unit: "ms",
	})
	fmt.Println("name:", stream.Name)

	stream, _ = view(metric.Instrument{
		Name: "heap.size",
		Unit: "By",
	})
	fmt.Println("name:", stream.Name)
	// Output:
	// name: computation.time.ms
	// name: heap.size.byte
}

func ExampleNewView() {
	// Create a view that renames the "latency" instrument from the v0.34.0
	// version of the "http" instrumentation library as "request.latency".
	view := metric.NewView(metric.Instrument{
		Name: "latency",
		Scope: instrumentation.Scope{
			Name:    "http",
			Version: "0.34.0",
		},
	}, metric.Stream{Name: "request.latency"})

	// The created view can then be registered with the OpenTelemetry metric
	// SDK using the WithView option.
	_ = metric.NewMeterProvider(
		metric.WithView(view),
	)

	// Below is an example of how the view will
	// function in the SDK for certain instruments.
	stream, _ := view(metric.Instrument{
		Name:        "latency",
		Description: "request latency",
		Unit:        "ms",
		Kind:        metric.InstrumentKindCounter,
		Scope: instrumentation.Scope{
			Name:      "http",
			Version:   "0.34.0",
			SchemaURL: "https://opentelemetry.io/schemas/1.37.0",
		},
	})
	fmt.Println("name:", stream.Name)
	fmt.Println("description:", stream.Description)
	fmt.Println("unit:", stream.Unit)
	// Output:
	// name: request.latency
	// description: request latency
	// unit: ms
}

func ExampleNewView_wildcard() {
	// Create a view that sets unit to milliseconds for any instrument with a
	// name suffix of ".ms".
	view := metric.NewView(
		metric.Instrument{Name: "*.ms"},
		metric.Stream{Unit: "ms"},
	)

	// The created view can then be registered with the OpenTelemetry metric
	// SDK using the WithView option.
	_ = metric.NewMeterProvider(
		metric.WithView(view),
	)

	// Below is an example of how the view will
	// function in the SDK for certain instruments.
	stream, _ := view(metric.Instrument{
		Name: "computation.time.ms",
		Unit: "1",
	})
	fmt.Println("name:", stream.Name)
	fmt.Println("unit:", stream.Unit)
	// Output:
	// name: computation.time.ms
	// unit: ms
}

func ExampleNewView_drop() {
	// Create a view that drops the "latency" instrument from the "http"
	// instrumentation library.
	view := metric.NewView(
		metric.Instrument{
			Name:  "latency",
			Scope: instrumentation.Scope{Name: "http"},
		},
		metric.Stream{Aggregation: metric.AggregationDrop{}},
	)

	// The created view can then be registered with the OpenTelemetry metric
	// SDK using the WithView option.
	_ = metric.NewMeterProvider(
		metric.WithView(view),
	)
}

func ExampleNewView_attributeFilter() {
	// Create a view that removes the "http.request.method" attribute recorded
	// by the "latency" instrument from the "http" instrumentation library.
	view := metric.NewView(
		metric.Instrument{
			Name:  "latency",
			Scope: instrumentation.Scope{Name: "http"},
		},
		metric.Stream{AttributeFilter: attribute.NewDenyKeysFilter("http.request.method")},
	)

	// The created view can then be registered with the OpenTelemetry metric
	// SDK using the WithView option.
	_ = metric.NewMeterProvider(
		metric.WithView(view),
	)
}

func ExampleNewView_exponentialHistogram() {
	// Create a view that makes the "latency" instrument from the "http"
	// instrumentation library to be reported as an exponential histogram.
	view := metric.NewView(
		metric.Instrument{
			Name:  "latency",
			Scope: instrumentation.Scope{Name: "http"},
		},
		metric.Stream{
			Aggregation: metric.AggregationBase2ExponentialHistogram{
				MaxSize:  160,
				MaxScale: 20,
			},
		},
	)

	// The created view can then be registered with the OpenTelemetry metric
	// SDK using the WithView option.
	_ = metric.NewMeterProvider(
		metric.WithView(view),
	)
}

func ExampleNewView_exemplarreservoirproviderselector() {
	// Create a view that makes all metrics use a different exemplar reservoir.
	view := metric.NewView(
		metric.Instrument{Name: "*"},
		metric.Stream{
			ExemplarReservoirProviderSelector: func(agg metric.Aggregation) exemplar.ReservoirProvider {
				// This example uses a fixed-size reservoir with a size of 10
				// for explicit bucket histograms instead of the default
				// bucket-aligned reservoir.
				if _, ok := agg.(metric.AggregationExplicitBucketHistogram); ok {
					return exemplar.FixedSizeReservoirProvider(10)
				}
				// Fall back to the default reservoir otherwise.
				return metric.DefaultExemplarReservoirProviderSelector(agg)
			},
		},
	)

	// The created view can then be registered with the OpenTelemetry metric
	// SDK using the WithView option.
	_ = metric.NewMeterProvider(
		metric.WithView(view),
	)
}

func ExampleWithExemplarFilter_disabled() {
	// Use exemplar.AlwaysOffFilter to disable exemplar collection.
	_ = metric.NewMeterProvider(
		metric.WithExemplarFilter(exemplar.AlwaysOffFilter),
	)
}

func ExampleWithExemplarFilter_custom() {
	// Create a custom filter function that only offers measurements if the
	// context has an error.
	customFilter := func(ctx context.Context) bool {
		return ctx.Err() != nil
	}
	_ = metric.NewMeterProvider(
		metric.WithExemplarFilter(customFilter),
	)
}
