package lib

import (
	"reflect"
	"testing"
	"time"

	"github.com/armon/go-metrics/prometheus"

	"github.com/stretchr/testify/require"
)

func makeFullTelemetryConfig(t *testing.T) TelemetryConfig {
	var (
		promOpts    = prometheus.PrometheusOpts{}
		strSliceVal = []string{"foo"}
		strVal      = "foo"
		intVal      = int64(1 * time.Second)
	)

	cfg := TelemetryConfig{}
	cfgP := reflect.ValueOf(&cfg)
	cfgV := cfgP.Elem()
	for i := 0; i < cfgV.NumField(); i++ {
		f := cfgV.Field(i)
		if !f.IsValid() || !f.CanSet() {
			continue
		}
		// Set non-zero values for all fields. We only implement kinds that exist
		// now for brevity but will fail the test if a new field type is added since
		// this is likely not implemented in MergeDefaults either.
		switch f.Kind() {
		case reflect.Struct:
			if f.Type() != reflect.TypeOf(promOpts) {
				t.Fatalf("unknown struct type in TelemetryConfig: actual %v, expected: %v", f.Type(), reflect.TypeOf(promOpts))
			}
			// TODO(kit): This should delve into the fields and set them individually rather than using an empty struct
			f.Set(reflect.ValueOf(promOpts))
		case reflect.Slice:
			if f.Type() != reflect.TypeOf(strSliceVal) {
				t.Fatalf("unknown slice type in TelemetryConfig." +
					" You need to update MergeDefaults and this test code.")
			}
			f.Set(reflect.ValueOf(strSliceVal))
		case reflect.Int, reflect.Int64: // time.Duration == int64
			f.SetInt(intVal)
		case reflect.String:
			f.SetString(strVal)
		case reflect.Bool:
			f.SetBool(true)
		default:
			t.Fatalf("unknown field type in TelemetryConfig" +
				" You need to update MergeDefaults and this test code.")
		}
	}
	return cfg
}

func TestTelemetryConfig_MergeDefaults(t *testing.T) {
	tests := []struct {
		name     string
		cfg      TelemetryConfig
		defaults TelemetryConfig
		want     TelemetryConfig
	}{
		{
			name: "basic merge",
			cfg: TelemetryConfig{
				StatsiteAddr: "stats.it:4321",
			},
			defaults: TelemetryConfig{
				StatsdAddr:   "localhost:5678",
				StatsiteAddr: "localhost:1234",
			},
			want: TelemetryConfig{
				StatsdAddr:   "localhost:5678",
				StatsiteAddr: "stats.it:4321",
			},
		},
		{
			// This test uses reflect to build a TelemetryConfig with every value set
			// to ensure that we exercise every possible field type. This means that
			// if new fields are added that are not supported types in the code, this
			// test should either ensure they work or fail to build the test case and
			// fail the test.
			name:     "exhaustive",
			cfg:      TelemetryConfig{},
			defaults: makeFullTelemetryConfig(t),
			want:     makeFullTelemetryConfig(t),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c := tt.cfg
			c.MergeDefaults(&tt.defaults)
			require.Equal(t, tt.want, c)
		})
	}
}
