package utils

import (
	"strings"
	"testing"

	. "github.com/onsi/gomega"
	"github.com/tidbcloud/pkgv2/bizerr"
	"google.golang.org/grpc/codes"
	"google.golang.org/protobuf/types/known/structpb"
)

func TestGrpcCode2BizCode(t *testing.T) {
	g := NewGomegaWithT(t)
	testCases := []struct {
		code     codes.Code
		expected bizerr.ErrCode
	}{
		{codes.Code(bizerr.ErrCodeHTTP), bizerr.ErrCodeHTTP},
		{codes.Internal, bizerr.ErrCodeUnknown},
		{codes.InvalidArgument, bizerr.ErrCodeParams},
		{codes.Unauthenticated, bizerr.ErrCodeAuth},
		{codes.Code(1234), bizerr.ErrCodeUnknown},
	}
	for _, tc := range testCases {
		g.Expect(GrpcCode2BizCode(tc.code)).To(Equal(tc.expected))
	}
}

func TestBizCode2GrpcCode(t *testing.T) {
	g := NewGomegaWithT(t)
	testCases := []struct {
		code     bizerr.ErrCode
		expected codes.Code
	}{
		{bizerr.ErrCode(codes.InvalidArgument), codes.InvalidArgument},
		{bizerr.NewBusinessErrCode(1, 1, "memo"), codes.InvalidArgument},
		{bizerr.ErrCodeUnknown, codes.Internal},
		{bizerr.ErrCodeRecordNotFound, codes.NotFound},
		{bizerr.ErrCodePermission, codes.PermissionDenied},
		{bizerr.ErrCode(1234), codes.Internal},
	}
	for _, tc := range testCases {
		g.Expect(BizCode2GrpcCode(tc.code)).To(Equal(tc.expected))
	}
}

func TestListMap2StructpbValue(t *testing.T) {
	g := NewGomegaWithT(t)
	v := structpb.NewListValue(&structpb.ListValue{
		Values: []*structpb.Value{structpb.NewStringValue("bar")},
	})
	testCases := []struct {
		listMap      map[string][]any
		expected     *structpb.Value
		expectedErr  bool
		expectedJson string
	}{
		{
			listMap: map[string][]any{"foo": {"bar"}},
			expected: &structpb.Value{
				Kind: &structpb.Value_StructValue{
					StructValue: &structpb.Struct{
						Fields: map[string]*structpb.Value{
							"foo": v,
						},
					},
				},
			},
			expectedErr:  false,
			expectedJson: `{"foo":["bar"]}`,
		},
		{
			listMap: map[string][]any{"foo": {[]any{"bar"}}},
			expected: &structpb.Value{
				Kind: &structpb.Value_StructValue{
					StructValue: &structpb.Struct{
						Fields: map[string]*structpb.Value{
							"foo": structpb.NewListValue(&structpb.ListValue{
								Values: []*structpb.Value{v},
							}),
						},
					},
				},
			},
			expectedErr:  false,
			expectedJson: `{"foo":[["bar"]]}`,
		},
		{
			listMap: map[string][]any{"foo": nil},
			expected: &structpb.Value{
				Kind: &structpb.Value_StructValue{
					StructValue: &structpb.Struct{
						Fields: map[string]*structpb.Value{
							"foo": structpb.NewListValue(&structpb.ListValue{
								Values: []*structpb.Value{},
							}),
						},
					},
				},
			},
			expectedErr:  false,
			expectedJson: `{"foo":[]}`,
		},
		{
			listMap: map[string][]any{"foo": {nil}},
			expected: &structpb.Value{
				Kind: &structpb.Value_StructValue{
					StructValue: &structpb.Struct{
						Fields: map[string]*structpb.Value{
							"foo": structpb.NewListValue(&structpb.ListValue{
								Values: []*structpb.Value{structpb.NewNullValue()},
							}),
						},
					},
				},
			},
			expectedErr:  false,
			expectedJson: `{"foo":[null]}`,
		},
		{
			listMap: map[string][]any{"foo": {map[string]any{"bar": "baz"}}},
			expected: &structpb.Value{
				Kind: &structpb.Value_StructValue{
					StructValue: &structpb.Struct{
						Fields: map[string]*structpb.Value{
							"foo": structpb.NewListValue(&structpb.ListValue{
								Values: []*structpb.Value{
									structpb.NewStructValue(&structpb.Struct{
										Fields: map[string]*structpb.Value{
											"bar": structpb.NewStringValue("baz"),
										},
									}),
								},
							}),
						},
					},
				},
			},
			expectedErr:  false,
			expectedJson: `{"foo":[{"bar":"baz"}]}`,
		},
		{
			listMap:     map[string][]any{"foo": {g}},
			expected:    nil,
			expectedErr: true,
		},
	}

	for _, tc := range testCases {
		l, err := ListMap2StructpbValue(tc.listMap)
		if tc.expectedErr {
			g.Expect(err).To(HaveOccurred())
		} else {
			g.Expect(err).To(Succeed())
			g.Expect(l).To(Equal(tc.expected))
			json, e := l.MarshalJSON()
			g.Expect(e).To(Succeed())
			g.Expect(string(json)).To(Equal(tc.expectedJson))
		}
	}
}

func TestStringSlice2StructpbValue(t *testing.T) {
	g := NewGomegaWithT(t)
	testCases := []struct {
		slice        []string
		expected     *structpb.Value
		expectedJson string
	}{
		{
			slice: []string{},
			expected: &structpb.Value{
				Kind: &structpb.Value_ListValue{
					ListValue: &structpb.ListValue{
						Values: []*structpb.Value{},
					},
				},
			},
			expectedJson: `[]`,
		},
		{
			slice: nil,
			expected: &structpb.Value{
				Kind: &structpb.Value_ListValue{
					ListValue: &structpb.ListValue{
						Values: []*structpb.Value{},
					},
				},
			},
			expectedJson: `[]`,
		},
		{
			slice: []string{"foo", "bar", "baz"},
			expected: &structpb.Value{
				Kind: &structpb.Value_ListValue{
					ListValue: &structpb.ListValue{
						Values: []*structpb.Value{
							structpb.NewStringValue("foo"),
							structpb.NewStringValue("bar"),
							structpb.NewStringValue("baz"),
						},
					},
				},
			},
			expectedJson: `["foo", "bar", "baz"]`,
		},
	}

	for _, tc := range testCases {
		v := StringSlice2StructpbValue(tc.slice)
		g.Expect(v).To(Equal(tc.expected))
		obj, e := v.MarshalJSON()
		g.Expect(e).To(Succeed())
		if string(obj) != tc.expectedJson &&
			string(obj) != strings.ReplaceAll(tc.expectedJson, " ", "") {
			t.Errorf("expected %s, got %s", tc.expectedJson, string(obj))
		}
	}
}
