package useadapter

import (
	"context"
	"net/http"
	"strings"

	"github.com/tidbcloud/account/idl/pbgen/proto/account"

	"github.com/tidbcloud/tiinf/sdk/rbac"
	"github.com/tidbcloud/tiinf/sdk/rbac/common"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

// -------- for grpc gateway interceptor -----------------------

func GrpcGatewayPermissionCheckUnaryServerInterceptorWithCommonParam(commonParam *rbac.PermissionCheckerCommonParam) grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		// do not inject permission check logic when req is not come from grpc gateway
		if !WhetherRequestComeFromGrpcGateway(ctx) {
			return handler(ctx, req)
		}
		checker := rbac.GetPermissionCheckerWithCommonParam(commonParam)
		result, err := checker.Handle(ctx, GenAccessParam4GrpcGateway(ctx))
		if err != nil {
			return nil, status.Error(codes.PermissionDenied, err.Error())
		}
		if result.Code != common.RSP_CODE_RELEASE {
			return nil, status.Error(codes.PermissionDenied, "permission denied")
		}
		return handler(ctx, req)
	}
}

func GrpcGatewayPermissionCheckStreamServerInterceptorWithCommonParam(commonParam *rbac.PermissionCheckerCommonParam) grpc.StreamServerInterceptor {
	return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
		if !WhetherRequestComeFromGrpcGateway(ss.Context()) {
			return handler(srv, ss)
		}
		checker := rbac.GetPermissionCheckerWithCommonParam(commonParam)
		result, err := checker.Handle(ss.Context(), GenAccessParam4GrpcGateway(ss.Context()))
		if err != nil {
			return status.Error(codes.PermissionDenied, err.Error())
		}
		if result.Code != common.RSP_CODE_RELEASE {
			return status.Error(codes.PermissionDenied, "permission denied")
		}
		return handler(srv, ss)
	}
}

// Deprecated
func GrpcGatewayPermissionCheckUnaryServerInterceptor(
	identityF common.GetIdentityFunc,
	resourceMap map[string]string,
	pathWhitelist ...string) grpc.UnaryServerInterceptor {

	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		// do not inject permission check logic when req is not come from grpc gateway
		if !WhetherRequestComeFromGrpcGateway(ctx) {
			return handler(ctx, req)
		}
		checker := rbac.GetPermissionCheckerWithDefaultHelper(
			identityF,
			resourceMap,
			pathWhitelist...)
		result, err := checker.Handle(ctx, GenAccessParam4GrpcGateway(ctx))
		if err != nil {
			return nil, status.Error(codes.PermissionDenied, err.Error())
		}
		if result.Code != common.RSP_CODE_RELEASE {
			return nil, status.Error(codes.PermissionDenied, "permission denied")
		}
		return handler(ctx, req)
	}
}

// Deprecated
func GrpcGatewayPermissionCheckUnaryServerInterceptorWithAccountClient(
	accountClient *account.AccountAPIServiceClient,
	identityF common.GetIdentityFunc,
	resourceMap map[string]string,
	pathWhitelist ...string) grpc.UnaryServerInterceptor {

	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		// do not inject permission check logic when req is not come from grpc gateway
		if !WhetherRequestComeFromGrpcGateway(ctx) {
			return handler(ctx, req)
		}
		checker := rbac.GetPermissionCheckerWithAccountClient(
			*accountClient,
			identityF,
			resourceMap,
			pathWhitelist...)
		result, err := checker.Handle(ctx, GenAccessParam4GrpcGateway(ctx))
		if err != nil {
			return nil, status.Error(codes.PermissionDenied, err.Error())
		}
		if result.Code != common.RSP_CODE_RELEASE {
			return nil, status.Error(codes.PermissionDenied, "permission denied")
		}
		return handler(ctx, req)
	}
}

// Deprecated
func GrpcGatewayPermissionCheckStreamServerInterceptor(
	identityF common.GetIdentityFunc,
	resourceMap map[string]string,
	pathWhitelist ...string) grpc.StreamServerInterceptor {
	return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
		if !WhetherRequestComeFromGrpcGateway(ss.Context()) {
			return handler(srv, ss)
		}
		checker := rbac.GetPermissionCheckerWithDefaultHelper(
			identityF,
			resourceMap,
			pathWhitelist...)
		result, err := checker.Handle(ss.Context(), GenAccessParam4GrpcGateway(ss.Context()))
		if err != nil {
			return status.Error(codes.PermissionDenied, err.Error())
		}
		if result.Code != common.RSP_CODE_RELEASE {
			return status.Error(codes.PermissionDenied, "permission denied")
		}
		return handler(srv, ss)
	}
}

// Deprecated
func GrpcGatewayPermissionCheckStreamServerInterceptorWithAccountClient(
	accountClient *account.AccountAPIServiceClient,
	identityF common.GetIdentityFunc,
	resourceMap map[string]string,
	pathWhitelist ...string) grpc.StreamServerInterceptor {
	return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
		if !WhetherRequestComeFromGrpcGateway(ss.Context()) {
			return handler(srv, ss)
		}
		checker := rbac.GetPermissionCheckerWithAccountClient(
			*accountClient,
			identityF,
			resourceMap,
			pathWhitelist...)
		result, err := checker.Handle(ss.Context(), GenAccessParam4GrpcGateway(ss.Context()))
		if err != nil {
			return status.Error(codes.PermissionDenied, err.Error())
		}
		if result.Code != common.RSP_CODE_RELEASE {
			return status.Error(codes.PermissionDenied, "permission denied")
		}
		return handler(srv, ss)
	}
}

func GenAccessParam4GrpcGateway(ctx context.Context) *common.AccessParam {
	param := common.AccessParam{
		Metadata: map[string]interface{}{
			common.METADATA_PATH:   GetHttpMetadataFromGrpcContext(ctx, common.METADATA_PATH),
			common.METADATA_METHOD: GetHttpMetadataFromGrpcContext(ctx, common.METADATA_METHOD),
		},
		Req: ctx,
	}
	return &param
}

func AddHttpMetadata2GrpcContext(c context.Context, r *http.Request) metadata.MD {
	m := make(map[string]string)
	m[common.METADATA_GRPC_GATEWAY_TOKEN] = common.METADATA_GRPC_GATEWAY_VALUE
	m[common.METADATA_PATH] = r.URL.Path
	m[common.METADATA_METHOD] = r.Method
	return metadata.New(m)
}

func GetHttpMetadataFromGrpcContext(ctx context.Context, header string) string {
	headers, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return ""
	}
	headerTexts := headers[strings.ToLower(header)]
	if len(headerTexts) < 1 {
		return ""
	}
	return headerTexts[0]
}

func WhetherRequestComeFromGrpcGateway(ctx context.Context) bool {
	token := GetHttpMetadataFromGrpcContext(ctx, common.METADATA_GRPC_GATEWAY_TOKEN)
	return token == common.METADATA_GRPC_GATEWAY_VALUE
}
