package logger

import (
	"context"
	"dataservices/commons/utils"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

type credential struct {
	Key  string
	Type int
}

type reqWithPtrCred struct {
	Name       string
	Credential *credential
}

type reqWithValCred struct {
	Name       string
	Credential credential
}

type reqNoCred struct {
	Name string
}

func hashKey(s string) string {
	sum := utils.Md5WithDefaultSalt(s)
	return "<<redacted>>" + sum
}

func TestRedact(t *testing.T) {
	t.Run("nil value", func(t *testing.T) {
		f := Redact("req", nil)
		assert.Equal(t, zapcore.ReflectType, f.Type)
		assert.Nil(t, f.Interface)
	})

	t.Run("no credential field", func(t *testing.T) {
		v := &reqNoCred{Name: "test"}
		f := Redact("req", v)
		tilog.Info(context.Background(), "none", f, zap.Any("orig", v))
		assert.Equal(t, zapcore.ReflectType, f.Type)
		assert.Equal(t, v, f.Interface)
	})

	t.Run("ptr outer, ptr credential, with key", func(t *testing.T) {
		orig := &reqWithPtrCred{
			Name:       "alice",
			Credential: &credential{Key: "secret", Type: 1},
		}

		f := Redact("req", orig)
		tilog.Info(context.Background(), "none", f, zap.Any("orig", orig))
		assert.Equal(t, zapcore.ReflectType, f.Type)

		got, ok := f.Interface.(*reqWithPtrCred)
		require.True(t, ok)
		assert.Equal(t, hashKey("secret"), got.Credential.Key)
		assert.Equal(t, 1, got.Credential.Type)
		assert.Equal(t, "alice", got.Name)
		assert.NotSame(t, orig.Credential, got.Credential)
		assert.Equal(t, "secret", orig.Credential.Key)
	})

	t.Run("ptr outer, ptr credential, empty key", func(t *testing.T) {
		orig := &reqWithPtrCred{
			Name:       "alice",
			Credential: &credential{Key: "", Type: 1},
		}
		f := Redact("req", orig)
		tilog.Info(context.Background(), "none", f, zap.Any("orig", orig))
		assert.Equal(t, orig, f.Interface)
	})

	t.Run("ptr outer, nil credential", func(t *testing.T) {
		orig := &reqWithPtrCred{Name: "alice", Credential: nil}
		f := Redact("req", orig)
		tilog.Info(context.Background(), "none", f, zap.Any("orig", orig))
		assert.Equal(t, orig, f.Interface)
	})

	t.Run("ptr outer, value credential, with key", func(t *testing.T) {
		orig := &reqWithValCred{
			Name:       "bob",
			Credential: credential{Key: "topsecret", Type: 2},
		}

		f := Redact("req", orig)
		tilog.Info(context.Background(), "none", f, zap.Any("orig", orig))
		got, ok := f.Interface.(*reqWithValCred)
		require.True(t, ok)
		assert.Equal(t, hashKey("topsecret"), got.Credential.Key)
		assert.Equal(t, 2, got.Credential.Type)
		assert.Equal(t, "bob", got.Name)
		assert.Equal(t, "topsecret", orig.Credential.Key)
	})

	t.Run("value outer, ptr credential, with key", func(t *testing.T) {
		cred := &credential{Key: "mysecret", Type: 3}
		orig := reqWithPtrCred{Name: "carol", Credential: cred}

		f := Redact("req", orig)
		tilog.Info(context.Background(), "none", f, zap.Any("orig", orig))
		got, ok := f.Interface.(reqWithPtrCred)
		require.True(t, ok)
		require.NotNil(t, got.Credential)
		assert.Equal(t, hashKey("mysecret"), got.Credential.Key)
		assert.Equal(t, 3, got.Credential.Type)
		assert.NotSame(t, cred, got.Credential)
		assert.Equal(t, "mysecret", orig.Credential.Key)
		assert.Equal(t, "mysecret", cred.Key)
	})

	t.Run("value outer, value credential, with key", func(t *testing.T) {
		orig := reqWithValCred{
			Name:       "dave",
			Credential: credential{Key: "pass", Type: 4},
		}

		f := Redact("req", orig)
		tilog.Info(context.Background(), "none", f, zap.Any("orig", orig))
		got, ok := f.Interface.(reqWithValCred)
		require.True(t, ok)
		assert.Equal(t, hashKey("pass"), got.Credential.Key)
		assert.Equal(t, 4, got.Credential.Type)
		assert.Equal(t, "pass", orig.Credential.Key)
	})

	t.Run("nil pointer", func(t *testing.T) {
		var v *reqWithPtrCred
		f := Redact("req", v)
		tilog.Info(context.Background(), "none", f, zap.Any("orig", v))
		assert.Equal(t, zapcore.ReflectType, f.Type)
		assert.Equal(t, v, f.Interface)
	})

	t.Run("non-struct value", func(t *testing.T) {
		orig := "hello"
		f := Redact("req", orig)
		tilog.Info(context.Background(), "none", f, zap.Any("orig", orig))
		assert.Equal(t, zapcore.StringType, f.Type)
		assert.Equal(t, orig, f.String)
	})

	t.Run("map value", func(t *testing.T) {
		orig := map[string]string{
			"auth0_token": "sometoken",
			"other":       "value",
		}
		f := Redact("req", orig)
		tilog.Info(context.Background(), "none", f, zap.Any("orig", orig))
		assert.Equal(t, zapcore.ReflectType, f.Type)
		got, ok := f.Interface.(map[string]string)
		require.True(t, ok)
		assert.Equal(t, hashKey("sometoken"), got["auth0_token"])
		assert.Equal(t, "value", got["other"])
		assert.Equal(t, "sometoken", orig["auth0_token"])
	})

}
