package permission

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	"google.golang.org/grpc"
	grpcmetadata "google.golang.org/grpc/metadata"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	openapingv1beta2 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/nextgen/v1beta2"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	permissionckr "github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/authorization/permission/checker"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

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

	checker, ok := methodToCheckerMap[tidbmgmtservicev1.BackupService_OverrideFlexibleBackupRetention_FullMethodName]
	require.True(t, ok)

	info := &grpc.UnaryServerInfo{FullMethod: tidbmgmtservicev1.BackupService_OverrideFlexibleBackupRetention_FullMethodName}
	req := &globalngv1.OverrideFlexibleBackupRetentionRequest{ClusterId: "1", RetentionDays: 1, Reason: "test"}
	checkCtx := &permissionckr.CheckContext{ApiKey: "test-api-key"}

	_, _, err := checker.Check(t.Context(), checkCtx, req, info)
	require.ErrorContains(t, err, "no metadata in context")

	ctx := grpcmetadata.NewIncomingContext(t.Context(), grpcmetadata.Pairs("api-key", "test-api-key"))
	_, _, err = checker.Check(ctx, checkCtx, req, info)
	require.NoError(t, err)
}

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

	tests := []struct {
		name    string
		method  string
		request any
	}{
		{
			name:    "pause",
			method:  tidbmgmtservicev1.ResourcePoolService_PauseResourcePool_FullMethodName,
			request: &globalngv1.PauseResourcePoolRequest{},
		},
		{
			name:    "resume",
			method:  tidbmgmtservicev1.ResourcePoolService_ResumeResourcePool_FullMethodName,
			request: &globalngv1.ResumeResourcePoolRequest{},
		},
		{
			name:    "get operation",
			method:  tidbmgmtservicev1.ResourcePoolService_GetResourcePoolPauseResumeOperation_FullMethodName,
			request: &globalngv1.GetResourcePoolPauseResumeOperationRequest{},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			_, ok := methodToCheckerMap[tt.method]
			require.False(t, ok)

			interceptor := NewPermissionCheckInterceptor(&permissionckr.CheckContext{ApiKey: "test-api-key"})
			info := &grpc.UnaryServerInfo{FullMethod: tt.method}

			called := false
			response, err := interceptor(t.Context(), tt.request, info, func(ctx context.Context, req any) (any, error) {
				called = true
				md, ok := grpcmetadata.FromIncomingContext(ctx)
				require.True(t, ok)
				require.Equal(t, []string{"true"}, md[metadata.IsCheckPermissionKey])
				return req, nil
			})
			require.NoError(t, err)
			require.True(t, called)
			require.Same(t, tt.request, response)
		})
	}
}

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

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:            1,
		UserID:           "10086",
		ValidProjectList: []uint64{10},
		DefaultProjectID: 10,
	})
	req := &openapingv1beta2.RestoreTidbRequest{
		BackupId:     "2863074",
		RestoreMode:  openapingv1beta2.RestoreMode_DEDICATED_SNAPSHOT,
		SourceTidbId: "",
		Tidb: &openapingv1beta2.Tidb{
			RegionId: "aws-us-west-2",
		},
	}
	info := &grpc.UnaryServerInfo{
		FullMethod: openapingv1beta2.TidbService_RestoreTidb_FullMethodName,
	}

	checker, ok := methodToCheckerMap[openapingv1beta2.TidbService_RestoreTidb_FullMethodName]
	require.True(t, ok)

	checkCtx := &permissionckr.CheckContext{}
	ctx, _, err := checker.Check(ctx, checkCtx, req, info)
	require.NoError(t, err)

	reqMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	require.NoError(t, err)
	require.True(t, reqMeta.GetIsCheckPermission())
	require.Zero(t, reqMeta.ClusterID)
	require.Equal(t, uint64(10), reqMeta.DefaultProjectID)
}

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

	ctx := metadata.AppendToIncomingMeta(t.Context(), &metadata.RequestMeta{
		OrgID:            1,
		UserID:           "10086",
		ValidProjectList: []uint64{10, 11},
		DefaultProjectID: 10,
	})
	req := &openapingv1beta2.RestoreTidbRequest{
		BackupId:     "2863074",
		RestoreMode:  openapingv1beta2.RestoreMode_DEDICATED_SNAPSHOT,
		SourceTidbId: "",
		Tidb: &openapingv1beta2.Tidb{
			RegionId: "aws-us-west-2",
			Labels: map[string]string{
				common.ProjectIDLabelKey: "11",
			},
		},
	}
	info := &grpc.UnaryServerInfo{
		FullMethod: openapingv1beta2.TidbService_RestoreTidb_FullMethodName,
	}

	checker, ok := methodToCheckerMap[openapingv1beta2.TidbService_RestoreTidb_FullMethodName]
	require.True(t, ok)

	checkCtx := &permissionckr.CheckContext{}
	ctx, _, err := checker.Check(ctx, checkCtx, req, info)
	require.NoError(t, err)

	reqMeta, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	require.NoError(t, err)
	require.True(t, reqMeta.GetIsCheckPermission())
	require.Equal(t, uint64(11), reqMeta.DefaultProjectID)
}
