package scale

import (
	"testing"
	"time"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/testutil"
	"github.com/stretchr/testify/require"
)

func TestScaler(t *testing.T) {
	tests := []struct {
		name     string
		scale    *Scale
		inputs   []telegraf.Metric
		expected []telegraf.Metric
	}{
		{
			name: "Field Scaling",
			scale: &Scale{
				Scalings: []Scaling{
					{
						InMin:  -1,
						InMax:  1,
						OutMin: 0,
						OutMax: 100,
						Fields: []string{"test1", "test2"},
					},
					{
						InMin:  -5,
						InMax:  0,
						OutMin: 1,
						OutMax: 9,
						Fields: []string{"test3", "test4"},
					},
				},
			},
			inputs: []telegraf.Metric{
				testutil.MustMetric("Name1", map[string]string{},
					map[string]interface{}{
						"test1": int64(0),
						"test2": uint64(1),
					}, time.Unix(0, 0)),
				testutil.MustMetric("Name2", map[string]string{},
					map[string]interface{}{
						"test1": "0.5",
						"test2": float32(-0.5),
					}, time.Unix(0, 0)),
				testutil.MustMetric("Name3", map[string]string{},
					map[string]interface{}{
						"test3": int64(-3),
						"test4": uint64(0),
					}, time.Unix(0, 0)),
				testutil.MustMetric("Name4", map[string]string{},
					map[string]interface{}{
						"test3": int64(-5),
						"test4": float32(-0.5),
					}, time.Unix(0, 0)),
			},
			expected: []telegraf.Metric{
				testutil.MustMetric("Name1", map[string]string{},
					map[string]interface{}{
						"test1": float64(50),
						"test2": float64(100),
					}, time.Unix(0, 0)),
				testutil.MustMetric("Name2", map[string]string{},
					map[string]interface{}{
						"test1": float64(75),
						"test2": float32(25),
					}, time.Unix(0, 0)),
				testutil.MustMetric("Name3", map[string]string{},
					map[string]interface{}{
						"test3": float64(4.2),
						"test4": float64(9),
					}, time.Unix(0, 0)),
				testutil.MustMetric("Name4", map[string]string{},
					map[string]interface{}{
						"test3": float64(1),
						"test4": float64(8.2),
					}, time.Unix(0, 0)),
			},
		},
		{
			name: "Ignored Fileds",
			scale: &Scale{
				Scalings: []Scaling{
					{
						InMin:  -1,
						InMax:  1,
						OutMin: 0,
						OutMax: 100,
						Fields: []string{"test1", "test2"},
					},
				},
			},
			inputs: []telegraf.Metric{
				testutil.MustMetric("Name1", map[string]string{},
					map[string]interface{}{
						"test1": int64(0),
						"test2": uint64(1),
						"test3": int64(1),
					}, time.Unix(0, 0)),
			},
			expected: []telegraf.Metric{
				testutil.MustMetric("Name1", map[string]string{},
					map[string]interface{}{
						"test1": float64(50),
						"test2": float64(100),
						"test3": int64(1),
					}, time.Unix(0, 0)),
			},
		},
		{
			name: "Out of range tests",
			scale: &Scale{
				Scalings: []Scaling{
					{
						InMin:  -1,
						InMax:  1,
						OutMin: 0,
						OutMax: 100,
						Fields: []string{"test1", "test2"},
					},
				},
			},
			inputs: []telegraf.Metric{
				testutil.MustMetric("Name1", map[string]string{},
					map[string]interface{}{
						"test1": int64(-2),
						"test2": uint64(2),
					}, time.Unix(0, 0)),
			},
			expected: []telegraf.Metric{
				testutil.MustMetric("Name1", map[string]string{},
					map[string]interface{}{
						"test1": float64(-50),
						"test2": float64(150),
					}, time.Unix(0, 0)),
			},
		},
		{
			name: "Missing field Fileds",
			scale: &Scale{
				Scalings: []Scaling{
					{
						InMin:  -1,
						InMax:  1,
						OutMin: 0,
						OutMax: 100,
						Fields: []string{"test1", "test2"},
					},
				},
			},
			inputs: []telegraf.Metric{
				testutil.MustMetric("Name1", map[string]string{},
					map[string]interface{}{
						"test1": int64(0),
					}, time.Unix(0, 0)),
			},
			expected: []telegraf.Metric{
				testutil.MustMetric("Name1", map[string]string{},
					map[string]interface{}{
						"test1": float64(50),
					}, time.Unix(0, 0)),
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.scale.Log = testutil.Logger{}

			require.NoError(t, tt.scale.Init())
			actual := tt.scale.Apply(tt.inputs...)

			testutil.RequireMetricsEqual(t, tt.expected, actual)
		})
	}
}

func TestErrorCases(t *testing.T) {
	tests := []struct {
		name             string
		scale            *Scale
		expectedErrorMsg string
	}{
		{
			name: "Same input range values",
			scale: &Scale{
				Scalings: []Scaling{
					{
						InMin:  1,
						InMax:  1,
						OutMin: 0,
						OutMax: 100,
						Fields: []string{"test"},
					},
				},
			},
			expectedErrorMsg: "input minimum and maximum are equal for fields test",
		},
		{
			name: "Same input range values",
			scale: &Scale{
				Scalings: []Scaling{
					{
						InMin:  0,
						InMax:  1,
						OutMin: 100,
						OutMax: 100,
						Fields: []string{"test"},
					},
				},
			},
			expectedErrorMsg: "output minimum and maximum are equal for fields test",
		},
		{
			name:             "No scalings",
			scale:            &Scale{Log: testutil.Logger{}},
			expectedErrorMsg: "no valid scalings defined",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.scale.Log = testutil.Logger{}
			require.ErrorContains(t, tt.scale.Init(), tt.expectedErrorMsg)
		})
	}
}
