package form_urlencoded

import (
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/metric"
	"github.com/influxdata/telegraf/testutil"
)

const (
	validFormData              = "tag1=foo&tag2=bar&tag3=baz&field1=42&field2=69"
	encodedFormData            = "tag1=%24%24%24&field1=1e%2B3"
	notEscapedProperlyFormData = "invalid=%Y5"
	blankKeyFormData           = "=42&field2=69"
	emptyFormData              = ""
)

func TestParseValidFormData(t *testing.T) {
	parser := &Parser{
		MetricName: "form_urlencoded_test",
	}
	require.NoError(t, parser.Init())

	metrics, err := parser.Parse([]byte(validFormData))
	require.NoError(t, err)
	require.Len(t, metrics, 1)
	require.Equal(t, "form_urlencoded_test", metrics[0].Name())
	require.Equal(t, map[string]string{}, metrics[0].Tags())
	require.Equal(t, map[string]interface{}{
		"field1": float64(42),
		"field2": float64(69),
	}, metrics[0].Fields())
}

func TestParseLineValidFormData(t *testing.T) {
	parser := &Parser{
		MetricName: "form_urlencoded_test",
	}
	require.NoError(t, parser.Init())

	metrics, err := parser.ParseLine(validFormData)
	require.NoError(t, err)
	require.Equal(t, "form_urlencoded_test", metrics.Name())
	require.Equal(t, map[string]string{}, metrics.Tags())
	require.Equal(t, map[string]interface{}{
		"field1": float64(42),
		"field2": float64(69),
	}, metrics.Fields())
}

func TestParseValidFormDataWithTags(t *testing.T) {
	parser := &Parser{
		MetricName: "form_urlencoded_test",
		TagKeys:    []string{"tag1", "tag2"},
	}
	require.NoError(t, parser.Init())

	metrics, err := parser.Parse([]byte(validFormData))
	require.NoError(t, err)
	require.Len(t, metrics, 1)
	require.Equal(t, "form_urlencoded_test", metrics[0].Name())
	require.Equal(t, map[string]string{
		"tag1": "foo",
		"tag2": "bar",
	}, metrics[0].Tags())
	require.Equal(t, map[string]interface{}{
		"field1": float64(42),
		"field2": float64(69),
	}, metrics[0].Fields())
}

func TestParseValidFormDataDefaultTags(t *testing.T) {
	parser := &Parser{
		MetricName:  "form_urlencoded_test",
		TagKeys:     []string{"tag1", "tag2"},
		DefaultTags: map[string]string{"tag4": "default"},
	}
	require.NoError(t, parser.Init())

	metrics, err := parser.Parse([]byte(validFormData))
	require.NoError(t, err)
	require.Len(t, metrics, 1)
	require.Equal(t, "form_urlencoded_test", metrics[0].Name())
	require.Equal(t, map[string]string{
		"tag1": "foo",
		"tag2": "bar",
		"tag4": "default",
	}, metrics[0].Tags())
	require.Equal(t, map[string]interface{}{
		"field1": float64(42),
		"field2": float64(69),
	}, metrics[0].Fields())
}

func TestParseValidFormDataDefaultTagsOverride(t *testing.T) {
	parser := &Parser{
		MetricName:  "form_urlencoded_test",
		TagKeys:     []string{"tag1", "tag2"},
		DefaultTags: map[string]string{"tag1": "default"},
	}
	require.NoError(t, parser.Init())

	metrics, err := parser.Parse([]byte(validFormData))
	require.NoError(t, err)
	require.Len(t, metrics, 1)
	require.Equal(t, "form_urlencoded_test", metrics[0].Name())
	require.Equal(t, map[string]string{
		"tag1": "default",
		"tag2": "bar",
	}, metrics[0].Tags())
	require.Equal(t, map[string]interface{}{
		"field1": float64(42),
		"field2": float64(69),
	}, metrics[0].Fields())
}

func TestParseEncodedFormData(t *testing.T) {
	parser := &Parser{
		MetricName: "form_urlencoded_test",
		TagKeys:    []string{"tag1"},
	}
	require.NoError(t, parser.Init())

	metrics, err := parser.Parse([]byte(encodedFormData))
	require.NoError(t, err)
	require.Len(t, metrics, 1)
	require.Equal(t, "form_urlencoded_test", metrics[0].Name())
	require.Equal(t, map[string]string{
		"tag1": "$$$",
	}, metrics[0].Tags())
	require.Equal(t, map[string]interface{}{
		"field1": float64(1000),
	}, metrics[0].Fields())
}

func TestParseInvalidFormDataError(t *testing.T) {
	parser := &Parser{
		MetricName: "form_urlencoded_test",
	}
	require.NoError(t, parser.Init())

	metrics, err := parser.Parse([]byte(notEscapedProperlyFormData))
	require.Error(t, err)
	require.Empty(t, metrics)
}

func TestParseInvalidFormDataEmptyKey(t *testing.T) {
	parser := &Parser{
		MetricName: "form_urlencoded_test",
	}
	require.NoError(t, parser.Init())

	// Empty key for field
	metrics, err := parser.Parse([]byte(blankKeyFormData))
	require.NoError(t, err)
	require.Len(t, metrics, 1)
	require.Equal(t, map[string]string{}, metrics[0].Tags())
	require.Equal(t, map[string]interface{}{
		"field2": float64(69),
	}, metrics[0].Fields())

	// Empty key for tag
	parser.TagKeys = []string{""}
	metrics, err = parser.Parse([]byte(blankKeyFormData))
	require.NoError(t, err)
	require.Len(t, metrics, 1)
	require.Equal(t, map[string]string{}, metrics[0].Tags())
	require.Equal(t, map[string]interface{}{
		"field2": float64(69),
	}, metrics[0].Fields())
}

func TestParseInvalidFormDataEmptyString(t *testing.T) {
	parser := &Parser{
		MetricName: "form_urlencoded_test",
	}
	require.NoError(t, parser.Init())

	metrics, err := parser.Parse([]byte(emptyFormData))
	require.NoError(t, err)
	require.Empty(t, metrics)
}

const benchmarkData = `tags_host=myhost&tags_platform=python&tags_sdkver=3.11.5&value=5`

func TestBenchmarkData(t *testing.T) {
	plugin := &Parser{
		MetricName: "benchmark",
		TagKeys:    []string{"tags_host", "tags_platform", "tags_sdkver"},
	}
	require.NoError(t, plugin.Init())

	expected := []telegraf.Metric{
		metric.New(
			"benchmark",
			map[string]string{
				"tags_host":     "myhost",
				"tags_platform": "python",
				"tags_sdkver":   "3.11.5",
			},
			map[string]interface{}{
				"value": 5.0,
			},
			time.Unix(0, 0),
		),
	}

	actual, err := plugin.Parse([]byte(benchmarkData))
	require.NoError(t, err)
	testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime(), testutil.SortMetrics())
}

func BenchmarkParsing(b *testing.B) {
	plugin := &Parser{
		MetricName: "benchmark",
		TagKeys:    []string{"source", "tags_platform", "tags_sdkver"},
	}
	require.NoError(b, plugin.Init())

	for n := 0; n < b.N; n++ {
		//nolint:errcheck // Benchmarking so skip the error check to avoid the unnecessary operations
		plugin.Parse([]byte(benchmarkData))
	}
}
