package gotoprom

import (
	"testing"

	"github.com/cabify/gotoprom/prometheusvanilla"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/assert"
)

func TestInitializer_MustAddBuilder(t *testing.T) {
	t.Run("adds builder", func(t *testing.T) {
		initializer := NewInitializer(prometheus.NewRegistry())
		initializer.MustAddBuilder(prometheusvanilla.GaugeType, prometheusvanilla.BuildGauge)

		err := initializer.Init(&struct {
			Metric func() prometheus.Gauge `name:"gauge" help:"help"`
		}{}, "namespace")
		assert.NoError(t, err)
	})
	t.Run("same builder twice panics", func(t *testing.T) {
		initializer := NewInitializer(prometheus.NewRegistry())
		initializer.MustAddBuilder(prometheusvanilla.GaugeType, prometheusvanilla.BuildGauge)

		assert.Panics(t, func() {
			initializer.MustAddBuilder(prometheusvanilla.GaugeType, prometheusvanilla.BuildGauge)
		})
	})
}

func TestInitializer_AddBuilder(t *testing.T) {
	t.Run("same builder twice fails", func(t *testing.T) {
		initializer := NewInitializer(prometheus.NewRegistry())
		err := initializer.AddBuilder(prometheusvanilla.GaugeType, prometheusvanilla.BuildGauge)
		assert.NoError(t, err)

		err = initializer.AddBuilder(prometheusvanilla.GaugeType, prometheusvanilla.BuildGauge)
		assert.Error(t, err)
	})
}

func TestInitializer_MustInit(t *testing.T) {
	t.Run("happy case", func(t *testing.T) {
		initializer := NewInitializer(prometheus.NewRegistry())

		structPtr := &struct{}{}
		assert.NotPanics(t, func() {
			initializer.MustInit(structPtr, "namespace")
		})
	})

	t.Run("panics when can't init", func(t *testing.T) {
		initializer := NewInitializer(prometheus.NewRegistry())

		notAPointer := struct{}{}
		assert.Panics(t, func() {
			initializer.MustInit(notAPointer, "namespace")
		})
	})
}

func TestInitializer_Init(t *testing.T) {
	someInt := 3
	type someLabels struct {
		Label string `label:"label"`
	}
	type someLabelsWithoutLabelTag struct {
		LabelWithoutLabelTag string
	}
	t.Run("fails", func(t *testing.T) {
		for _, tc := range []struct {
			desc    string
			metrics interface{}
		}{
			{
				desc:    "not a pointer to struct",
				metrics: struct{}{},
			},
			{
				desc:    "pointer to a non struct",
				metrics: &someInt,
			},
			{
				desc: "contains a non-supported field, like a string",
				metrics: &struct {
					Something string
				}{},
			},
			{
				desc: "sub-struct doesn't define its namespace",
				metrics: &struct {
					Inner struct{}
				}{},
			},
			{
				desc: "sub-struct can't be initialized",
				metrics: &struct {
					Inner struct {
						Deeper struct{} `thisdoesnothaveanamespace:"nothing"`
					} `namespace:"thishasanamespace"`
				}{},
			},
			{
				desc: "non-exported field",
				metrics: &struct {
					foo func() prometheus.Gauge `name:"unexported" help:"this can't be set"`
				}{},
			},
			{
				desc: "missing name",
				metrics: &struct {
					Foo func() prometheus.Gauge `help:"missing name tag"`
				}{},
			},
			{
				desc: "missing help",
				metrics: &struct {
					Foo func() prometheus.Gauge `name:"nohelp"`
				}{},
			},
			{
				desc: "more than one return argument",
				metrics: &struct {
					Foo func() (prometheus.Gauge, error) `name:"toomanyreturnargs" help:"what is this?"`
				}{},
			},
			{
				desc: "no builders defined",
				metrics: &struct {
					Foo func() prometheus.Counter `name:"unknowntype" help:"Counter is not registered"`
				}{},
			},
			{
				desc: "builder fails",
				metrics: &struct {
					Foo func() prometheus.Summary `name:"unknowntype" help:"max_age is malformed" max_age:"bar"`
				}{},
			},
			{
				desc: "builder fails",
				metrics: &struct {
					Foo func() prometheus.Summary `name:"unknowntype" help:"max_age is malformed" max_age:"bar"`
				}{},
			},
			{
				desc: "too many params",
				metrics: &struct {
					Foo func(someLabels, string) prometheus.Gauge `name:"too many input params" help:"only labels are accepted"`
				}{},
			},
			{
				desc: "labels without label tag",
				metrics: &struct {
					Foo func(someLabelsWithoutLabelTag) prometheus.Gauge `name:"nolabeltag" help:"This labels are missing the label tag"`
				}{},
			},
			{
				desc: "metric registration fails",
				metrics: &struct {
					Foo func() prometheus.Gauge `name:"metric" help:"first gauge"`
					Bar func() prometheus.Gauge `name:"metric" help:"name duplicates the previous one"`
				}{},
			},
		} {
			t.Run(tc.desc, func(t *testing.T) {
				initializer := NewInitializer(prometheus.NewRegistry())
				initializer.MustAddBuilder(prometheusvanilla.GaugeType, prometheusvanilla.BuildGauge)
				initializer.MustAddBuilder(prometheusvanilla.SummaryType, prometheusvanilla.BuildSummary)

				err := initializer.Init(tc.metrics, "namespace")
				assert.Error(t, err)
			})
		}
	})
}
