package errorcode

import (
	"context"
	"errors"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/log"
	"go.uber.org/zap"
)

// PrepareErrorMessage converts an error into the response-safe BizError used by
// public service gateways. Known user-facing errors keep their specific
// messages, while unknown and internal errors are replaced with the standard
// non-revealing internal error message.
func PrepareErrorMessage(
	ctx context.Context,
	err error,
	messages map[bizerr.ErrCode]string,
) bizerr.BizError {
	if err == nil {
		return nil
	}
	if be, ok := FormatClusterSQLError(err); ok {
		return be
	}

	logger := log.WithContextL(ctx)
	be, ok := AsResponseError(err)
	if !ok {
		logger.Error("Unknown error", zap.Error(err))
		return bizerr.New(ErrCodeInternalError, InternalErrorMessage)
	}
	if bizerr.IsInternalErr(be.RawErrCode()) {
		logger.Error("InternalErr error", zap.Error(err))
		return bizerr.New(ErrCodeInternalError, InternalErrorMessage)
	}

	if be.ErrMsg() != "" {
		return be
	}
	if message, ok := messages[be.RawErrCode()]; ok {
		return bizerr.Restore(be.ErrCode(), message)
	}
	return be
}

// AsResponseError finds errors that already have a response-safe contract.
// It is shared by the domain-specific Prepare functions and the transport
// middlewares so services which do not have a Prepare function behave the same
// way. Ordinary Go errors are not considered safe to expose.
func AsResponseError(err error) (bizerr.BizError, bool) {
	if err == nil {
		return nil, false
	}
	if be, ok := FormatClusterSQLError(err); ok {
		return be, true
	}
	var be bizerr.BizError
	if !errors.As(err, &be) {
		return nil, false
	}
	return be, true
}
