package jwk_test

import (
	"testing"

	"github.com/lestrrat-go/jwx/v3/internal/json"
	"github.com/lestrrat-go/jwx/v3/jwa"
	"github.com/lestrrat-go/jwx/v3/jwk"
	"github.com/stretchr/testify/require"
)

func TestHeader(t *testing.T) {
	t.Parallel()

	t.Run("Roundtrip", func(t *testing.T) {
		t.Parallel()

		values := map[string]any{
			jwk.KeyIDKey:                  "helloworld01",
			jwk.KeyOpsKey:                 jwk.KeyOperationList{jwk.KeyOpSign},
			jwk.KeyUsageKey:               "sig",
			jwk.X509CertThumbprintKey:     "thumbprint",
			jwk.X509CertThumbprintS256Key: "thumbprint256",
			jwk.X509URLKey:                "cert1",
			"private":                     "boofoo",
		}

		h, err := jwk.Import([]byte("dummy"))
		require.NoError(t, err, `jwk.New should succeed`)

		for k, v := range values {
			require.NoError(t, h.Set(k, v), "Set works for '%s'", k)

			var got any
			require.NoError(t, h.Get(k, &got), "Get works for '%s'", k)
			require.Equal(t, v, got, "values match '%s'", k)
			require.NoError(t, h.Set(k, v), "Set works for '%s'", k)
		}

		t.Run("Private params", func(t *testing.T) {
			t.Parallel()
			var v string
			require.NoError(t, h.Get(`private`, &v), `h.Get should succeed`)
			require.Equal(t, v, "boofoo", "value for 'private' should match")
		})
	})
	t.Run("RoundtripError", func(t *testing.T) {
		t.Parallel()
		type dummyStruct struct {
			dummy1 int
			dummy2 float64
		}
		dummy := &dummyStruct{1, 3.4}
		values := map[string]any{
			jwk.AlgorithmKey:              dummy,
			jwk.KeyIDKey:                  dummy,
			jwk.KeyUsageKey:               dummy,
			jwk.KeyOpsKey:                 dummy,
			jwk.X509CertChainKey:          dummy,
			jwk.X509CertThumbprintKey:     dummy,
			jwk.X509CertThumbprintS256Key: dummy,
			jwk.X509URLKey:                dummy,
		}

		h, err := jwk.Import([]byte("dummy"))
		require.NoError(t, err, `jwk.New should succeed`)
		for k, v := range values {
			err := h.Set(k, v)
			if err == nil {
				t.Fatalf("Setting %s value should have failed", k)
			}
		}
		require.NoError(t, h.Set("Default", dummy), `Setting "Default" should succeed`)

		alg, ok := h.Algorithm()
		require.True(t, !ok, `Algorithm should not be set`)
		require.Nil(t, alg, "Algorithm should be nil")

		kid, ok := h.KeyID()
		require.False(t, ok, `KeyID should not be set`)
		require.Empty(t, kid, "KeyID should be empty")

		use, ok := h.KeyUsage()
		require.False(t, ok, `KeyUsage should not be set`)
		require.Empty(t, use, "KeyUsage should be empty")

		ops, ok := h.KeyOps()
		require.False(t, ok, `KeyOps should not be set`)
		require.Nil(t, ops, "KeyOps should be nil")
	})

	t.Run("Algorithm", func(t *testing.T) {
		t.Parallel()
		h, err := jwk.Import([]byte("dummy"))
		require.NoError(t, err, `jwk.New should succeed`)
		for _, value := range []any{jwa.RS256(), jwa.RSA1_5()} {
			require.NoError(t, h.Set(jwk.AlgorithmKey, value), "Set for alg should succeed")

			var got jwa.KeyAlgorithm
			require.NoError(t, h.Get("alg", &got), "Get for alg should succeed")
			require.Equal(t, value, got, "values match")
		}
	})
}

// A custom field name must never be able to introduce members of its own.
// See GHSA-4cf7-xm37-g63h.
func TestFieldNameCannotInjectMembers(t *testing.T) {
	t.Parallel()

	const name = `x":0,"use`

	key, err := jwk.Import([]byte("0123456789abcdef"))
	require.NoError(t, err, `jwk.Import should succeed`)
	require.NoError(t, key.Set(name, "sig"), `Set should succeed`)

	buf, err := json.Marshal(key)
	require.NoError(t, err, `json.Marshal should succeed`)

	var got map[string]any
	require.NoError(t, json.Unmarshal(buf, &got), `serialized key should be valid JSON`)
	require.Contains(t, got, name, `field name should round-trip unchanged: %s`, buf)
	require.NotContains(t, got, "use", `no "use" field should have been injected: %s`, buf)

	roundtrip, err := jwk.ParseKey(buf)
	require.NoError(t, err, `jwk.ParseKey should succeed`)
	_, ok := roundtrip.KeyUsage()
	require.False(t, ok, `no "use" field should have been injected`)
}
