package checker

import (
	"context"

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

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

type ResourcePoolIDExtractor func(req any) (string, error)

func StdResourcePoolIDExtractor(req any) (string, error) {
	poolReq, ok := req.(interface{ GetResourcePoolId() string })
	if !ok {
		return "", bizerr.New(bizerr.ErrCodeParams, "invalid request: req=%v", req)
	}
	return poolReq.GetResourcePoolId(), nil
}

func NewResourcePoolPermissionChecker(extractResourcePoolID ResourcePoolIDExtractor) CheckFunc {
	return newResourcePoolPermissionChecker(extractResourcePoolID, isBYOCResourcePool)
}

func newResourcePoolPermissionChecker(
	extractResourcePoolID ResourcePoolIDExtractor,
	servicePlanAllowed func(globalcommonv1.ServicePlan) bool,
) CheckFunc {
	return func(
		ctx context.Context,
		checkCtx *CheckContext,
		req any,
		_ *grpc.UnaryServerInfo,
	) (context.Context, any, error) {
		reqMeta, err := metadata.ExtractRequestMeta(ctx)
		if err != nil {
			tilog.Error(ctx, "failed to extract request metadata", zap.Error(err))
			return ctx, req, bizerror.NewInternal(err, "failed to extract request metadata")
		}
		if checkCtx.NextGenCli == nil {
			return ctx, req, bizerr.New(bizerr.ErrCodeUnknown, "nextgen client is not configured")
		}

		poolIDStr, err := extractResourcePoolID(req)
		if err != nil {
			return ctx, req, err
		}
		poolID, err := common.ParseResourcePoolID(poolIDStr)
		if err != nil {
			return ctx, req, bizerr.WrapErrorWithCode(
				err,
				bizerr.ErrCodeParams,
				"invalid resource pool id %s",
				poolIDStr,
			)
		}

		resp, err := checkCtx.NextGenCli.GetResourcePool(ctx, &globalngv1.GetResourcePoolRequest{
			ResourcePoolId: poolID,
		})
		if err != nil {
			return ctx, req, bizerr.WrapGrpcError(err, "failed to get resource pool")
		}
		if resp.GetResourcePool().GetOwnerType() != globalngv1.ResourcePool_ORGANIZATION ||
			resp.GetResourcePool().GetOwnerId() != reqMeta.OrgID ||
			!servicePlanAllowed(resp.GetResourcePool().GetServicePlan()) {
			return ctx, req, bizerr.New(bizerr.ErrCodeRecordNotFound, "resource pool not found")
		}

		reqMeta.ResourceID = poolIDStr
		reqMeta.IsCheckPermission = lo.ToPtr(true)
		return metadata.AppendToIncomingMeta(ctx, reqMeta), req, nil
	}
}

func isBYOCResourcePool(servicePlan globalcommonv1.ServicePlan) bool {
	return servicePlan == globalcommonv1.ServicePlan_BYOC
}

func ResourcePoolIdChecker(
	ctx context.Context,
	checkCtx *CheckContext,
	req any,
	info *grpc.UnaryServerInfo,
) (context.Context, any, error) {
	return NewResourcePoolPermissionChecker(StdResourcePoolIDExtractor)(ctx, checkCtx, req, info)
}
