package checker

import (
	"context"
	"strconv"

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

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

// StdMaintenanceTaskIdExtractor extracts maintenance_task_id from requests that
// implement GetMaintenanceTaskId() directly.
func StdMaintenanceTaskIdExtractor(req any) (string, error) {
	type maintenanceTaskIdRequest interface{ GetMaintenanceTaskId() string }
	r, ok := req.(maintenanceTaskIdRequest)
	if !ok {
		return "", bizerr.New(bizerr.ErrCodeParams, "invalid request: req=%v", req)
	}
	return r.GetMaintenanceTaskId(), nil
}

// NewMaintenanceTaskIdChecker creates a permission checker that validates the requesting user
// has access to the maintenance task. The extractTaskId function extracts the task ID string
// from the request.
func NewMaintenanceTaskIdChecker(extractTaskId func(req any) (string, error)) CheckFunc {
	return func(
		ctx context.Context,
		checkCtx *CheckContext,
		req any,
		info *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")
		}

		maintenanceTaskIdStr, err := extractTaskId(req)
		if err != nil {
			return ctx, req, err
		}
		if maintenanceTaskIdStr == "" {
			return ctx, req, bizerr.New(bizerr.ErrCodeParams, "maintenance task id is empty")
		}

		maintenanceTaskId, err := strconv.ParseUint(maintenanceTaskIdStr, 10, 64)
		if err != nil {
			return ctx, req, bizerr.WrapErrorWithCode(
				err,
				bizerr.ErrCodeParams,
				"invalid maintenance task id %s",
				maintenanceTaskIdStr,
			)
		}

		task, err := checkCtx.MaintenanceTaskRepo.Load(ctx, maintenanceTaskId)
		if err != nil {
			return ctx, req, err
		}

		if err := reqMeta.CheckProjectIDsPermission(task.ProjectId); err != nil {
			return ctx, req, bizerr.WrapErrorWithCode(err, bizerr.ErrCodePermission, bizerr.ErrMsgResourceNoPermission)
		}

		reqMeta.IsCheckPermission = lo.ToPtr(true)
		reqMeta.DefaultProjectID = task.ProjectId
		return metadata.AppendToIncomingMeta(ctx, reqMeta), req, nil
	}
}
