package bizutils

import (
	"context"

	"github.com/golang/protobuf/proto"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/dataflow-service-ng/pkg/account/accountdb"
	"github.com/tidbcloud/dataflow-service-ng/pkg/log"
	"github.com/tidbcloud/pkgv2/auth/auth0"
	"go.uber.org/zap"
)

// GetUserFromContext gets the tenant.User from context if exists.
func GetUserFromContext(ctx context.Context) (u *accountdb.User, err error) {
	claimsIFace := ctx.Value(auth0.AuthTokenClaimKey)
	if claimsIFace == nil {
		return nil, errors.New("user not in context")
	}
	user, ok := claimsIFace.(*accountdb.User)
	if !ok {
		return nil, errors.New("expect type accountdb.User in context")
	}
	return user, nil
}

// GetUserEmailOrEmptyFromContext gets the caller's email from context if it is
// there, or returns an empty string. Callers that only want to record who acted
// should not fail because the claim is absent, which is why this mirrors
// GetUIDOrZeroFromContext rather than returning an error.
func GetUserEmailOrEmptyFromContext(ctx context.Context) string {
	user, err := GetUserFromContext(ctx)
	if err != nil || user == nil {
		return ""
	}
	return user.Email
}

// GetUIDOrZeroFromContext gets the current users' UID from context if exists, or returns 0.
func GetUIDOrZeroFromContext(ctx context.Context) uint64 {
	logger := log.GetLogger(ctx)

	u, err := GetUserFromContext(ctx)
	if err != nil {
		logger.Warn("get current user failed", zap.Error(err))
		return 0
	}

	return u.ID
}

type dataflowContextKey string

const (
	mutableErrorDetailsContextKey dataflowContextKey = "Dataflow/MutableErrorDetailsContextKey"
)

type mutableErrorDetails struct {
	// ErrorDetails stores all the detail information for errors.
	// TODO: currently the value is manipulated in serial,
	// if later we need to manipulate it in parallel, change it to an atomic.Value, or add a lock.
	ErrorDetails []proto.Message
}

func getMutableErrorDetailsFromContext(ctx context.Context) *mutableErrorDetails {
	val := ctx.Value(mutableErrorDetailsContextKey)
	if val == nil {
		return nil
	}
	mDetails, ok := val.(*mutableErrorDetails)
	if !ok {
		return nil
	}
	return mDetails
}

func WithInitMutableErrorDetails(ctx context.Context) context.Context {
	return context.WithValue(ctx, mutableErrorDetailsContextKey, &mutableErrorDetails{})
}

func AppendErrorDetails(ctx context.Context, details ...proto.Message) error {
	mDetails := getMutableErrorDetailsFromContext(ctx)
	if mDetails == nil {
		return errors.New("mutable error details are not initialized in the context")
	}
	mDetails.ErrorDetails = append(mDetails.ErrorDetails, details...)
	return nil
}

func GetErrorDetailsFromContext(ctx context.Context) []proto.Message {
	mDetails := getMutableErrorDetailsFromContext(ctx)
	if mDetails == nil {
		return nil
	}
	return mDetails.ErrorDetails
}
