// Copyright 2025 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package schema

import (
	"fmt"
	"testing"

	"github.com/prometheus/common/model"
	"github.com/stretchr/testify/require"

	"github.com/prometheus/prometheus/model/labels"
	"github.com/prometheus/prometheus/util/testutil"
)

func TestMetadata(t *testing.T) {
	testMeta := Metadata{
		Name: "metric_total",
		Type: model.MetricTypeCounter,
		Unit: "seconds",
	}

	for _, tcase := range []struct {
		emptyName, emptyType, emptyUnit bool
	}{
		{},
		{emptyName: true},
		{emptyType: true},
		{emptyUnit: true},
		{emptyName: true, emptyType: true, emptyUnit: true},
	} {
		var (
			expectedMeta   Metadata
			expectedLabels labels.Labels
		)
		{
			// Setup expectations.
			lb := labels.NewScratchBuilder(0)
			lb.Add("foo", "bar")

			if !tcase.emptyName {
				lb.Add(metricName, testMeta.Name)
				expectedMeta.Name = testMeta.Name
			}
			if !tcase.emptyType {
				lb.Add(metricType, string(testMeta.Type))
				expectedMeta.Type = testMeta.Type
			} else {
				expectedMeta.Type = model.MetricTypeUnknown
			}
			if !tcase.emptyUnit {
				lb.Add(metricUnit, testMeta.Unit)
				expectedMeta.Unit = testMeta.Unit
			}
			lb.Sort()
			expectedLabels = lb.Labels()
		}

		t.Run(fmt.Sprintf("meta=%#v", expectedMeta), func(t *testing.T) {
			{
				// From labels to Metadata.
				got := NewMetadataFromLabels(expectedLabels)
				require.Equal(t, expectedMeta, got)
			}
			{
				// Empty methods.
				require.Equal(t, tcase.emptyName, expectedMeta.IsEmptyFor(metricName))
				require.Equal(t, tcase.emptyType, expectedMeta.IsEmptyFor(metricType))
				require.Equal(t, tcase.emptyType, expectedMeta.IsTypeEmpty())
				require.Equal(t, tcase.emptyUnit, expectedMeta.IsEmptyFor(metricUnit))
			}
			{
				// From Metadata to labels for various builders.
				slb := labels.NewScratchBuilder(0)
				slb.Add("foo", "bar")
				expectedMeta.AddToLabels(&slb)
				slb.Sort()
				testutil.RequireEqual(t, expectedLabels, slb.Labels())

				lb := labels.NewBuilder(labels.FromStrings("foo", "bar"))
				expectedMeta.SetToLabels(lb)
				testutil.RequireEqual(t, expectedLabels, lb.Labels())
			}
		})
	}
}

func TestIgnoreOverriddenMetadataLabelsScratchBuilder(t *testing.T) {
	// PROM-39 specifies that metadata labels should be sourced primarily from the metadata structures.
	// However, the original labels should be preserved IF the metadata structure does not set or support certain information.
	// Test those cases with common label interactions.
	incomingLabels := labels.FromStrings(metricName, "different_name", metricType, string(model.MetricTypeSummary), metricUnit, "MB", "foo", "bar")
	for _, tcase := range []struct {
		highPrioMeta   Metadata
		expectedLabels labels.Labels
	}{
		{
			expectedLabels: incomingLabels,
		},
		{
			highPrioMeta: Metadata{
				Name: "metric_total",
				Type: model.MetricTypeCounter,
				Unit: "seconds",
			},
			expectedLabels: labels.FromStrings(metricName, "metric_total", metricType, string(model.MetricTypeCounter), metricUnit, "seconds", "foo", "bar"),
		},
		{
			highPrioMeta: Metadata{
				Name: "metric_total",
				Type: model.MetricTypeCounter,
			},
			expectedLabels: labels.FromStrings(metricName, "metric_total", metricType, string(model.MetricTypeCounter), metricUnit, "MB", "foo", "bar"),
		},
		{
			highPrioMeta: Metadata{
				Type: model.MetricTypeCounter,
				Unit: "seconds",
			},
			expectedLabels: labels.FromStrings(metricName, "different_name", metricType, string(model.MetricTypeCounter), metricUnit, "seconds", "foo", "bar"),
		},
		{
			highPrioMeta: Metadata{
				Name: "metric_total",
				Type: model.MetricTypeUnknown,
				Unit: "seconds",
			},
			expectedLabels: labels.FromStrings(metricName, "metric_total", metricType, string(model.MetricTypeSummary), metricUnit, "seconds", "foo", "bar"),
		},
	} {
		t.Run(fmt.Sprintf("meta=%#v", tcase.highPrioMeta), func(t *testing.T) {
			lb := labels.NewScratchBuilder(0)
			tcase.highPrioMeta.AddToLabels(&lb)
			wrapped := &IgnoreOverriddenMetadataLabelsScratchBuilder{ScratchBuilder: &lb, Overwrite: tcase.highPrioMeta}
			incomingLabels.Range(func(l labels.Label) {
				wrapped.Add(l.Name, l.Value)
			})
			lb.Sort()
			require.Equal(t, tcase.expectedLabels, lb.Labels())
		})
	}
}
