package v0alpha1_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"testing"

	"github.com/stretchr/testify/require"
	"go.yaml.in/yaml/v3"
	"k8s.io/kube-openapi/pkg/validation/strfmt"
	"k8s.io/kube-openapi/pkg/validation/validate"

	common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
)

func TestSecureValues(t *testing.T) {
	t.Run("redaction", func(t *testing.T) {
		expected := "[REDACTED]"

		rawValue := "a-password"
		esv := common.NewSecretValue(rawValue)

		// String must not return the exposed secure value.
		require.Equal(t, expected, esv.String())
		require.Equal(t, expected, esv.GoString())

		// Format/GoString must not return the exposed secure value.
		require.Equal(t, expected, fmt.Sprintf("%+#v", esv))
		require.Equal(t, expected, fmt.Sprintf("%v", esv))
		require.Equal(t, expected, fmt.Sprintf("%s", esv))

		buf := new(bytes.Buffer)
		_, err := fmt.Fprintf(buf, "%#v", esv)
		require.NoError(t, err)
		require.Equal(t, expected, buf.String())

		// MarshalJSON must not return the exposed secure value.
		bytes, err := json.Marshal(esv)
		require.NoError(t, err)
		require.Equal(t, `"`+expected+`"`, string(bytes))

		// MarshalYAML must not return the exposed secure value.
		bytes, err = yaml.Marshal(esv)
		require.NoError(t, err)
		require.Equal(t, "'"+expected+"'\n", string(bytes))

		// DangerouslyExposeAndConsumeValue returns the raw value.
		require.Equal(t, rawValue, esv.DangerouslyExposeAndConsumeValue())

		// Further calls to DangerouslyExposeAndConsumeValue will panic.
		require.Panics(t, func() { esv.DangerouslyExposeAndConsumeValue() })
	})

	t.Run("Inline", func(t *testing.T) {
		t.Run("IsZero", func(t *testing.T) {
			require.True(t, common.InlineSecureValue{}.IsZero())
			require.True(t, common.NewSecretValue("").IsZero())
			require.True(t, common.InlineSecureValue{Remove: false}.IsZero())

			require.False(t, common.NewSecretValue("X").IsZero())
			require.False(t, common.InlineSecureValue{Name: "X"}.IsZero())
			require.False(t, common.InlineSecureValue{Remove: true}.IsZero())
		})

		t.Run("Validate OneOf", func(t *testing.T) {
			def := common.InlineSecureValue{}.OpenAPIDefinition()
			// jj, _ := json.MarshalIndent(def.Schema, "", "  ")
			// fmt.Printf("%s", string(jj))
			// t.FailNow()

			validator := validate.NewSchemaValidator(&def.Schema, nil, "", strfmt.Default)

			tests := []struct {
				name  string
				input map[string]any
				valid bool
				err   string
			}{
				{
					name:  "with name",
					input: map[string]any{"name": "x"},
					valid: true,
				},
				{
					name:  "with create",
					input: map[string]any{"create": "x"},
					valid: true,
				},
				{
					name:  "with remove",
					input: map[string]any{"remove": true},
					valid: true,
				},
				{
					name:  "empty",
					input: map[string]any{},
					valid: false,
					err:   "must validate one and only one",
				},
				{
					name:  "with unknown property",
					input: map[string]any{"unknown": "property"},
					valid: false,
					err:   "unknown",
				},
			}
			for _, tt := range tests {
				t.Run(tt.name, func(t *testing.T) {
					result := validator.Validate(tt.input)
					require.Equal(t, tt.valid, result.IsValid())
					if tt.err == "" {
						require.NoError(t, result.AsError())
					} else {
						require.ErrorContains(t, result.AsError(), tt.err)
					}
				})
			}
		})
	})
}
