package auth

import (
	"context"
	"fmt"
	"sync"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"google.golang.org/grpc"
)

var (
	onceGwChecker        PermissionChecker
	onceGwCheckerInit    sync.Once
	onceGwCheckerInitErr error
)

func GrpcGatewayPermissionCheckUnaryServerInterceptor(commonParam *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, err := GetGrpcGatewayPermissionChecker(commonParam)
		if err != nil {
			tilog.Warn(ctx, "GetGrpcGatewayPermissionChecker error", zap.Any("err", err))
			return nil, status.Error(codes.PermissionDenied, fmt.Sprintf("Access denied: %s", err.Error()))
		}
		var accessParam *AccessParam
		if commonParam.GenAccessParam4GrpcGatewayF != nil {
			accessParam, err = commonParam.GenAccessParam4GrpcGatewayF(ctx)
			if err != nil {
				tilog.Warn(ctx, "custom GenAccessParam4GrpcGatewayF exec error", zap.Any("err", err))
				return nil, status.Error(codes.PermissionDenied, fmt.Sprintf("Access denied: %s", err.Error()))
			}
		} else {
			accessParam = GenAccessParam4GrpcGateway(ctx)
		}
		accessParam.Metadata[AccessMetadataResourceMapKey] = commonParam.PathPattern2ResourceMap
		accessParam.Metadata[AccessMetadataRequestWhitelistKey] = commonParam.PathWhitelist
		ret, err := checker.Handle(ctx, accessParam)
		if err != nil {
			return nil, status.Error(codes.PermissionDenied, fmt.Sprintf("Access denied: %s", err.Error()))
		}
		if ret.Code != AccessRetRelease {
			return nil, status.Error(codes.PermissionDenied, fmt.Sprintf("Access denied: %s", ret.Msg))
		}
		return handler(ctx, req)
	}
}

func GrpcGatewayPermissionCheckStreamServerInterceptor(commonParam *PermissionCheckerCommonParam) grpc.StreamServerInterceptor {
	return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
		ctx := ss.Context()
		if !WhetherRequestComeFromGrpcGateway(ctx) {
			return handler(srv, ss)
		}
		checker, err := GetGrpcGatewayPermissionChecker(commonParam)
		if err != nil {
			tilog.Warn(ctx, "GetGrpcGatewayPermissionChecker error", zap.Any("err", err))
			return status.Error(codes.PermissionDenied, fmt.Sprintf("Access denied: %s", err.Error()))
		}
		var accessParam *AccessParam
		if commonParam.GenAccessParam4GrpcGatewayF != nil {
			accessParam, err = commonParam.GenAccessParam4GrpcGatewayF(ctx)
			if err != nil {
				tilog.Warn(ctx, "custom GenAccessParam4GrpcGatewayF exec error", zap.Any("err", err))
				return status.Error(codes.PermissionDenied, err.Error())
			}
		} else {
			accessParam = GenAccessParam4GrpcGateway(ctx)
		}
		accessParam.Metadata[AccessMetadataResourceMapKey] = commonParam.PathPattern2ResourceMap
		accessParam.Metadata[AccessMetadataRequestWhitelistKey] = commonParam.PathWhitelist
		result, err := checker.Handle(ctx, accessParam)
		if err != nil {
			return status.Error(codes.PermissionDenied, err.Error())
		}
		if result.Code != AccessRetRelease {
			return status.Error(codes.PermissionDenied, fmt.Sprintf("Access denied: %s", err.Error()))
		}
		return handler(srv, ss)
	}
}

func GetGrpcGatewayPermissionChecker(commonParam *PermissionCheckerCommonParam) (PermissionChecker, error) {
	onceGwCheckerInit.Do(func() {
		onceGwChecker, onceGwCheckerInitErr = NewPermissionCheckerWithCommonParam(commonParam)
	})
	return onceGwChecker, onceGwCheckerInitErr
}
