package checker

import (
	"context"
	"errors"
	"testing"

	. "github.com/onsi/gomega"
	"google.golang.org/grpc"
	grpcmetadata "google.golang.org/grpc/metadata"

	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	openapiserverlessv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/serverless/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func TestCreateClusterPermissionChecker(t *testing.T) {
	g := NewWithT(t)
	errUnexpectedRequestType := errors.New("unexpected request type")

	type testCase struct {
		name          string
		extractLabels func(req any) (map[string]string, error)
		inputsFn      func() (context.Context, CheckContext, any, *grpc.UnaryServerInfo)
		outputsFn     func(context.Context, any, error)
	}

	serverlessLabelExtractor := func(req any) (map[string]string, error) {
		r, ok := req.(*openapiserverlessv1beta1.CreateClusterRequest)
		if !ok {
			return nil, errUnexpectedRequestType
		}
		return r.GetCluster().GetLabels(), nil
	}
	dedicatedLabelExtractor := func(req any) (map[string]string, error) {
		r, ok := req.(*openapidedicatedv1beta1.CreateClusterRequest)
		if !ok {
			return nil, errUnexpectedRequestType
		}
		return r.GetCluster().GetLabels(), nil
	}

	cases := []testCase{
		{
			name:          "Permission check serverless v1 should pass",
			extractLabels: serverlessLabelExtractor,
			inputsFn: func() (context.Context, CheckContext, any, *grpc.UnaryServerInfo) {
				ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
					metadata.UserIDHeaderKey:           "1",
					metadata.OrgIDHeaderKey:            "1",
					metadata.DefaultProjectIDHeaderKey: "1",
					metadata.ProjectListHeaderKey:      "1,2,3",
					metadata.UserEmailHeaderKey:        "foo@bar.com",
				}))
				checkCtx := CheckContext{}
				req := &openapiserverlessv1beta1.CreateClusterRequest{
					Cluster: &openapiserverlessv1beta1.Cluster{
						Labels: map[string]string{
							common.ProjectIDLabelKey: "2",
						},
					},
				}
				info := &grpc.UnaryServerInfo{
					FullMethod: openapiserverlessv1beta1.ClusterService_CreateCluster_FullMethodName,
				}
				return ctx, checkCtx, req, info
			},
			outputsFn: func(ctx context.Context, req any, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(req.(*openapiserverlessv1beta1.CreateClusterRequest).Cluster.Labels[common.ProjectIDLabelKey]).
					Should(Equal("2"))
				ctxMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(ctxMeta.DefaultProjectID).Should(Equal(uint64(2)))
				g.Expect(ctxMeta.GetIsCheckPermission()).Should(BeTrue())
			},
		},
		{
			name:          "Permission check dedicated v1 should pass",
			extractLabels: dedicatedLabelExtractor,
			inputsFn: func() (context.Context, CheckContext, any, *grpc.UnaryServerInfo) {
				ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
					metadata.UserIDHeaderKey:           "1",
					metadata.OrgIDHeaderKey:            "1",
					metadata.DefaultProjectIDHeaderKey: "1",
					metadata.ProjectListHeaderKey:      "1,2,3",
					metadata.UserEmailHeaderKey:        "foo@bar.com",
				}))
				checkCtx := CheckContext{}
				req := &openapidedicatedv1beta1.CreateClusterRequest{
					Cluster: &openapidedicatedv1beta1.Cluster{
						Labels: map[string]string{
							common.ProjectIDLabelKey: "1",
						},
					},
				}
				info := &grpc.UnaryServerInfo{
					FullMethod: openapidedicatedv1beta1.ClusterService_CreateCluster_FullMethodName,
				}
				return ctx, checkCtx, req, info
			},
			outputsFn: func(ctx context.Context, req any, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(req.(*openapidedicatedv1beta1.CreateClusterRequest).Cluster.Labels[common.ProjectIDLabelKey]).
					Should(Equal("1"))
				ctxMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(ctxMeta.DefaultProjectID).Should(Equal(uint64(1)))
				g.Expect(ctxMeta.GetIsCheckPermission()).Should(BeTrue())
			},
		},
		{
			name:          "Permission check dedicated v1 no label should pass",
			extractLabels: dedicatedLabelExtractor,
			inputsFn: func() (context.Context, CheckContext, any, *grpc.UnaryServerInfo) {
				ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
					metadata.UserIDHeaderKey:           "1",
					metadata.OrgIDHeaderKey:            "1",
					metadata.DefaultProjectIDHeaderKey: "1",
					metadata.ProjectListHeaderKey:      "1,2,3",
					metadata.UserEmailHeaderKey:        "foo@bar.com",
				}))
				checkCtx := CheckContext{}
				req := &openapidedicatedv1beta1.CreateClusterRequest{
					Cluster: &openapidedicatedv1beta1.Cluster{},
				}
				info := &grpc.UnaryServerInfo{
					FullMethod: openapidedicatedv1beta1.ClusterService_CreateCluster_FullMethodName,
				}
				return ctx, checkCtx, req, info
			},
			outputsFn: func(ctx context.Context, req any, err error) {
				g.Expect(err).ShouldNot(HaveOccurred())
				ctxMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
				g.Expect(err).ShouldNot(HaveOccurred())
				g.Expect(ctxMeta.DefaultProjectID).Should(Equal(uint64(1)))
				g.Expect(ctxMeta.GetIsCheckPermission()).Should(BeTrue())
			},
		},
		{
			name:          "Permission check should fail",
			extractLabels: serverlessLabelExtractor,
			inputsFn: func() (context.Context, CheckContext, any, *grpc.UnaryServerInfo) {
				ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.New(map[string]string{
					metadata.UserIDHeaderKey:           "1",
					metadata.OrgIDHeaderKey:            "1",
					metadata.DefaultProjectIDHeaderKey: "1",
					metadata.ProjectListHeaderKey:      "1,2,3",
					metadata.UserEmailHeaderKey:        "foo@bar.com",
				}))
				checkCtx := CheckContext{}
				req := &openapiserverlessv1beta1.CreateClusterRequest{
					Cluster: &openapiserverlessv1beta1.Cluster{
						Labels: map[string]string{
							common.ProjectIDLabelKey: "4",
						},
					},
				}
				info := &grpc.UnaryServerInfo{
					FullMethod: openapiserverlessv1beta1.ClusterService_CreateCluster_FullMethodName,
				}
				return ctx, checkCtx, req, info
			},
			outputsFn: func(ctx context.Context, req any, err error) {
				g.Expect(err).Should(HaveOccurred())
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			ctx, checkCtx, req, info := tc.inputsFn()
			checker := NewCreateClusterPermissionChecker(tc.extractLabels)
			ctx, req, err := checker(ctx, &checkCtx, req, info)
			tc.outputsFn(ctx, req, err)
		})
	}
}
