package handlers

import (
	"context"
	stderrors "errors"
	"fmt"
	"math"
	"net"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"unicode"

	"github.com/gin-gonic/gin"
	gozap "go.uber.org/zap"

	clinic_internal_api "github.com/pingcap-inc/clinic/clinic_data_service/pkg/clinic-internal-api"
	apierrors "github.com/pingcap-inc/clinic/clinic_data_service/pkg/errors"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/observability"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/service"
)

const (
	maxBYOCSlowQueryQueryBytes       = 16 << 10
	maxBYOCSlowQueryTimeRangeSeconds = 3*24*60*60 + 5*60
	maxBYOCSlowQueryLimit            = 1000
	defaultBYOCSlowQueryLimit        = 100
	maxBYOCSlowQueryFieldsBytes      = 4096
	maxBYOCSlowQueryTextBytes        = 4096
	maxBYOCSlowQueryOpaqueBytes      = 256
	maxBYOCSlowQueryCollectionValues = 64
	defaultBYOCSlowQueryFields       = "query,timestamp,query_time,memory_max"
)

var (
	byocSlowQueryFieldNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]{0,127}$`)
	byocSlowQueryUintPattern      = regexp.MustCompile(`^[0-9]{1,20}$`)

	byocSlowQueryListKeys = map[string]struct{}{
		"begin_time": {}, "end_time": {}, "fields": {}, "db": {}, "limit": {},
		"text": {}, "orderBy": {}, "desc": {}, "plans": {}, "digest": {}, "show_internal": {},
	}
	byocSlowQueryDetailKeys = map[string]struct{}{
		"digest": {}, "timestamp": {}, "connect_id": {}, "show_internal": {},
	}
	byocSlowQueryOrderFields = map[string]struct{}{
		"timestamp": {}, "query_time": {}, "memory_max": {}, "total_keys": {}, "process_keys": {},
	}
)

type byocSlowQueryQuerier interface {
	Query(context.Context, uint64, string, service.BYOCSlowQueryOperation, url.Values) (*service.BYOCSlowQueryResult, error)
}

// BYOCSlowQueryHandler exposes BYOC slow-query data read directly from O11y.
// It never uses Data Proxy SQL, object storage, or warehouse table managers.
type BYOCSlowQueryHandler struct {
	service byocSlowQueryQuerier
}

func NewBYOCSlowQueryHandler(svc byocSlowQueryQuerier) *BYOCSlowQueryHandler {
	return &BYOCSlowQueryHandler{service: svc}
}

// AvailableFields returns the fields supported by the resolved BYOC O11y target.
//
//	@Summary		List BYOC slow-query fields
//	@ID			canonical-byoc-slow-query-available-fields
//	@Tags			data-proxy-slow-queries
//	@Produce		json
//	@Param			orgId		path		string	true	"TiDB Cloud/DBAAS org ID"
//	@Param			clusterId	path		string	true	"Logical BYOC or physical byoc-host cluster ID"
//	@Success		200			{array}		string
//	@Failure		400			{object}	apierrors.APIError
//	@Failure		401			{object}	apierrors.APIError
//	@Failure		403			{object}	apierrors.APIError
//	@Failure		404			{object}	apierrors.APIError
//	@Failure		413			{object}	apierrors.APIError
//	@Failure		429			{object}	apierrors.APIError
//	@Failure		502			{object}	apierrors.APIError
//	@Failure		503			{object}	apierrors.APIError
//	@Failure		504			{object}	apierrors.APIError
//	@Security		BearerAuth
//	@Router			/data-proxy/api/v1/orgs/{orgId}/clusters/{clusterId}/slow-queries/available-fields [get]
func (h *BYOCSlowQueryHandler) AvailableFields(c *gin.Context) {
	h.handle(c, "canonical_byoc_slow_query_available_fields", service.BYOCSlowQueryOperationAvailableFields, noBYOCSlowQueryParams)
}

// List returns BYOC slow-query records from O11y.
//
//	@Summary		List BYOC slow queries
//	@ID			canonical-byoc-slow-query-list
//	@Tags			data-proxy-slow-queries
//	@Produce		json
//	@Param			orgId			path		string		true	"TiDB Cloud/DBAAS org ID"
//	@Param			clusterId		path		string		true	"Logical BYOC or physical byoc-host cluster ID"
//	@Param			begin_time		query		int			true	"Window start in Unix seconds"	minimum(0)
//	@Param			end_time		query		int			true	"Window end in Unix seconds"	minimum(0)
//	@Param			fields			query		string		false	"Comma-separated result fields"
//	@Param			db				query		[]string	false	"Database filters; repeat the parameter"	collectionFormat(multi)
//	@Param			limit			query		int			false	"Result limit, 1-1000"	minimum(1)	maximum(1000)	default(100)
//	@Param			text			query		string		false	"SQL text filter"
//	@Param			orderBy			query		string		false	"Sort field"	Enums(timestamp,query_time,memory_max,total_keys,process_keys)	default(timestamp)
//	@Param			desc			query		bool		false	"Sort descending"	default(false)
//	@Param			plans			query		[]string	false	"Plan digest filters; repeat the parameter"	collectionFormat(multi)
//	@Param			digest			query		string		false	"SQL digest filter"
//	@Param			show_internal	query		bool		false	"Include internal SQL"	default(false)
//	@Success		200				{array}		map[string]interface{}
//	@Failure		400				{object}	apierrors.APIError
//	@Failure		401				{object}	apierrors.APIError
//	@Failure		403				{object}	apierrors.APIError
//	@Failure		404				{object}	apierrors.APIError
//	@Failure		413				{object}	apierrors.APIError
//	@Failure		429				{object}	apierrors.APIError
//	@Failure		502				{object}	apierrors.APIError
//	@Failure		503				{object}	apierrors.APIError
//	@Failure		504				{object}	apierrors.APIError
//	@Security		BearerAuth
//	@Router			/data-proxy/api/v1/orgs/{orgId}/clusters/{clusterId}/slow-queries [get]
func (h *BYOCSlowQueryHandler) List(c *gin.Context) {
	h.handle(c, "canonical_byoc_slow_query_list", service.BYOCSlowQueryOperationList, canonicalBYOCSlowQueryList)
}

// Detail returns one BYOC slow-query record identified by its composite key.
//
//	@Summary		Get BYOC slow-query detail
//	@ID			canonical-byoc-slow-query-detail
//	@Tags			data-proxy-slow-queries
//	@Produce		json
//	@Param			orgId			path		string	true	"TiDB Cloud/DBAAS org ID"
//	@Param			clusterId		path		string	true	"Logical BYOC or physical byoc-host cluster ID"
//	@Param			digest			query		string	true	"SQL digest"
//	@Param			timestamp		query		number	true	"Execution timestamp in Unix seconds"
//	@Param			connect_id		query		string	true	"Unsigned connection ID"
//	@Param			show_internal	query		bool	false	"Include internal SQL"	default(false)
//	@Success		200				{object}	map[string]interface{}
//	@Failure		400				{object}	apierrors.APIError
//	@Failure		401				{object}	apierrors.APIError
//	@Failure		403				{object}	apierrors.APIError
//	@Failure		404				{object}	apierrors.APIError
//	@Failure		413				{object}	apierrors.APIError
//	@Failure		429				{object}	apierrors.APIError
//	@Failure		502				{object}	apierrors.APIError
//	@Failure		503				{object}	apierrors.APIError
//	@Failure		504				{object}	apierrors.APIError
//	@Security		BearerAuth
//	@Router			/data-proxy/api/v1/orgs/{orgId}/clusters/{clusterId}/slow-queries/detail [get]
func (h *BYOCSlowQueryHandler) Detail(c *gin.Context) {
	h.handle(c, "canonical_byoc_slow_query_detail", service.BYOCSlowQueryOperationDetail, canonicalBYOCSlowQueryDetail)
}

type byocSlowQueryParamsParser func(*gin.Context) (url.Values, error)

func (h *BYOCSlowQueryHandler) handle(
	c *gin.Context,
	endpoint string,
	operation service.BYOCSlowQueryOperation,
	parseParams byocSlowQueryParamsParser,
) {
	start, rec := beginCanonicalDataProxyRequest(c, endpoint)
	rec.QueryType = "slowquery"
	rec.Engine = "o11y"
	defer func() { observability.RecordDataProxy(c, start, rec) }()

	// Reject oversized query strings before the shared placement guard parses
	// them, so an untrusted URL cannot cause duplicate large allocations.
	if len(c.Request.URL.RawQuery) > maxBYOCSlowQueryQueryBytes {
		rec.ErrCode = "INVALID_SLOW_QUERY_PARAMETERS"
		writeResourceError(c, http.StatusRequestEntityTooLarge, "PayloadTooLarge", errBYOCSlowQueryParametersTooLarge.Error(), nil)
		return
	}
	if !requireNoCanonicalBody(c, rec) || !rejectCanonicalPlacement(c, rec) {
		return
	}
	orgID, clusterID, ok := parseCanonicalTelemetryScope(c, rec)
	if !ok {
		return
	}
	query, err := parseParams(c)
	if err != nil {
		rec.ErrCode = "INVALID_SLOW_QUERY_PARAMETERS"
		status := http.StatusBadRequest
		code := "InvalidArgument"
		if stderrors.Is(err, errBYOCSlowQueryParametersTooLarge) {
			status = http.StatusRequestEntityTooLarge
			code = "PayloadTooLarge"
		}
		writeResourceError(c, status, code, err.Error(), nil)
		return
	}
	if h == nil || h.service == nil {
		rec.ErrCode = "SLOW_QUERY_UNAVAILABLE"
		writeResourceError(c, http.StatusServiceUnavailable, "Unavailable", "BYOC Slow Query service is temporarily unavailable", nil)
		return
	}

	result, err := h.service.Query(c.Request.Context(), orgID, clusterID, operation, query)
	if err != nil {
		h.writeError(c, rec, err, operation, clusterID)
		return
	}
	if result == nil {
		rec.ErrCode = "SLOW_QUERY_INVALID_RESPONSE"
		writeResourceError(c, http.StatusBadGateway, "BadGateway", "BYOC Slow Query returned an invalid response", nil)
		return
	}
	rec.Provider = result.Provider
	rec.Region = result.Region
	rec.DeployTypeV2 = result.DeployTypeV2
	c.Data(http.StatusOK, "application/json; charset=utf-8", result.Body)
}

func (h *BYOCSlowQueryHandler) RegisterRoutes(r *gin.Engine) {
	group := r.Group("/data-proxy/api/v1/orgs/:orgId/clusters/:clusterId/slow-queries")
	group.GET("", h.List)
	group.GET("/available-fields", h.AvailableFields)
	group.GET("/detail", h.Detail)
}

var errBYOCSlowQueryParametersTooLarge = stderrors.New("query parameters exceed the allowed size")

func parseBYOCSlowQueryValues(c *gin.Context, allowed map[string]struct{}) (url.Values, error) {
	if len(c.Request.URL.RawQuery) > maxBYOCSlowQueryQueryBytes {
		return nil, errBYOCSlowQueryParametersTooLarge
	}
	values, err := url.ParseQuery(c.Request.URL.RawQuery)
	if err != nil {
		return nil, fmt.Errorf("query parameters are malformed")
	}
	for key := range values {
		if _, ok := allowed[key]; !ok {
			return nil, fmt.Errorf("unsupported Slow Query parameter %q", key)
		}
	}
	return values, nil
}

func noBYOCSlowQueryParams(c *gin.Context) (url.Values, error) {
	values, err := parseBYOCSlowQueryValues(c, map[string]struct{}{})
	if err != nil {
		return nil, err
	}
	if len(values) != 0 {
		return nil, fmt.Errorf("query parameters are not supported")
	}
	return make(url.Values), nil
}

func canonicalBYOCSlowQueryList(c *gin.Context) (url.Values, error) {
	values, err := parseBYOCSlowQueryValues(c, byocSlowQueryListKeys)
	if err != nil {
		return nil, err
	}
	if err := requireSingleBYOCSlowQueryValues(values, "begin_time", "end_time", "fields", "limit", "text", "orderBy", "desc", "digest", "show_internal"); err != nil {
		return nil, err
	}
	if err := validateBYOCSlowQueryCollections(values); err != nil {
		return nil, err
	}

	begin, err := parseRequiredBYOCSlowQueryNonNegativeInt64(values, "begin_time")
	if err != nil {
		return nil, err
	}
	end, err := parseRequiredBYOCSlowQueryNonNegativeInt64(values, "end_time")
	if err != nil {
		return nil, err
	}
	if end < begin {
		return nil, fmt.Errorf("end_time must be greater than or equal to begin_time")
	}
	if end-begin > maxBYOCSlowQueryTimeRangeSeconds {
		return nil, fmt.Errorf("time range must not exceed %d seconds", maxBYOCSlowQueryTimeRangeSeconds)
	}

	result := cloneURLValues(values)
	fields := defaultBYOCSlowQueryFields
	if values.Has("fields") {
		fields, err = canonicalBYOCSlowQueryFields(values.Get("fields"))
		if err != nil {
			return nil, err
		}
	}
	result.Set("fields", fields)

	limit := defaultBYOCSlowQueryLimit
	if values.Has("limit") {
		limit, err = strconv.Atoi(values.Get("limit"))
		if err != nil || limit < 1 || limit > maxBYOCSlowQueryLimit {
			return nil, fmt.Errorf("limit must be between 1 and %d", maxBYOCSlowQueryLimit)
		}
	}
	result.Set("limit", strconv.Itoa(limit))

	orderBy := "timestamp"
	if values.Has("orderBy") {
		orderBy = strings.ToLower(values.Get("orderBy"))
		if _, ok := byocSlowQueryOrderFields[orderBy]; !ok {
			return nil, fmt.Errorf("orderBy is not supported")
		}
	}
	result.Set("orderBy", orderBy)

	for _, key := range []string{"desc", "show_internal"} {
		if values.Has(key) {
			if err := validateStrictBYOCSlowQueryBool(key, values.Get(key)); err != nil {
				return nil, err
			}
		}
	}
	if values.Has("text") {
		if err := validateBYOCSlowQueryText("text", values.Get("text"), maxBYOCSlowQueryTextBytes, true); err != nil {
			return nil, err
		}
	}
	if values.Has("digest") {
		if err := validateBYOCSlowQueryText("digest", values.Get("digest"), maxBYOCSlowQueryOpaqueBytes, true); err != nil {
			return nil, err
		}
	}
	return result, nil
}

func canonicalBYOCSlowQueryDetail(c *gin.Context) (url.Values, error) {
	values, err := parseBYOCSlowQueryValues(c, byocSlowQueryDetailKeys)
	if err != nil {
		return nil, err
	}
	if err := requireSingleBYOCSlowQueryValues(values, "digest", "timestamp", "connect_id", "show_internal"); err != nil {
		return nil, err
	}
	for _, key := range []string{"digest", "timestamp", "connect_id"} {
		if !values.Has(key) || values.Get(key) == "" {
			return nil, fmt.Errorf("%s is required", key)
		}
	}
	if err := validateBYOCSlowQueryText("digest", values.Get("digest"), maxBYOCSlowQueryOpaqueBytes, false); err != nil {
		return nil, err
	}
	timestamp, err := strconv.ParseFloat(values.Get("timestamp"), 64)
	if err != nil || timestamp <= 0 || math.IsNaN(timestamp) || math.IsInf(timestamp, 0) {
		return nil, fmt.Errorf("timestamp must be a finite positive Unix timestamp")
	}
	connectID := values.Get("connect_id")
	if !byocSlowQueryUintPattern.MatchString(connectID) {
		return nil, fmt.Errorf("connect_id must be an unsigned decimal integer")
	}
	if _, err := strconv.ParseUint(connectID, 10, 64); err != nil {
		return nil, fmt.Errorf("connect_id must be an unsigned decimal integer")
	}
	if values.Has("show_internal") {
		if err := validateStrictBYOCSlowQueryBool("show_internal", values.Get("show_internal")); err != nil {
			return nil, err
		}
	}
	return cloneURLValues(values), nil
}

func requireSingleBYOCSlowQueryValues(values url.Values, keys ...string) error {
	for _, key := range keys {
		if len(values[key]) > 1 {
			return fmt.Errorf("%s must not be repeated", key)
		}
	}
	return nil
}

func validateBYOCSlowQueryCollections(values url.Values) error {
	for _, key := range []string{"db", "plans"} {
		items := values[key]
		if len(items) > maxBYOCSlowQueryCollectionValues {
			return fmt.Errorf("%s must not contain more than %d values", key, maxBYOCSlowQueryCollectionValues)
		}
		for _, item := range items {
			if err := validateBYOCSlowQueryText(key, item, maxBYOCSlowQueryOpaqueBytes, false); err != nil {
				return err
			}
		}
	}
	return nil
}

func parseRequiredBYOCSlowQueryNonNegativeInt64(values url.Values, key string) (int64, error) {
	if !values.Has(key) || values.Get(key) == "" {
		return 0, fmt.Errorf("%s is required", key)
	}
	value, err := strconv.ParseInt(values.Get(key), 10, 64)
	if err != nil || value < 0 {
		return 0, fmt.Errorf("%s must be a non-negative Unix timestamp", key)
	}
	return value, nil
}

func canonicalBYOCSlowQueryFields(raw string) (string, error) {
	if raw == "" {
		return "", fmt.Errorf("fields must not be empty")
	}
	if len(raw) > maxBYOCSlowQueryFieldsBytes {
		return "", fmt.Errorf("fields must not exceed %d bytes", maxBYOCSlowQueryFieldsBytes)
	}
	parts := strings.Split(raw, ",")
	seen := make(map[string]struct{}, len(parts))
	result := make([]string, 0, len(parts))
	for _, part := range parts {
		part = strings.TrimSpace(part)
		if !byocSlowQueryFieldNamePattern.MatchString(part) {
			return "", fmt.Errorf("fields contains an invalid field name")
		}
		key := strings.ToLower(part)
		if _, ok := seen[key]; ok {
			return "", fmt.Errorf("fields contains a duplicate field name")
		}
		seen[key] = struct{}{}
		result = append(result, part)
	}
	return strings.Join(result, ","), nil
}

func validateStrictBYOCSlowQueryBool(key, value string) error {
	if value != "true" && value != "false" {
		return fmt.Errorf("%s must be true or false", key)
	}
	return nil
}

func validateBYOCSlowQueryText(key, value string, maxBytes int, allowEmpty bool) error {
	if !allowEmpty && value == "" {
		return fmt.Errorf("%s must not be empty", key)
	}
	if len(value) > maxBytes {
		return fmt.Errorf("%s must not exceed %d bytes", key, maxBytes)
	}
	for _, r := range value {
		if unicode.IsControl(r) {
			return fmt.Errorf("%s must not contain control characters", key)
		}
	}
	return nil
}

func cloneURLValues(values url.Values) url.Values {
	result := make(url.Values, len(values))
	for key, items := range values {
		result[key] = append([]string(nil), items...)
	}
	return result
}

func (h *BYOCSlowQueryHandler) writeError(
	c *gin.Context,
	rec *observability.DataProxyRecord,
	err error,
	operation service.BYOCSlowQueryOperation,
	clusterID string,
) {
	switch {
	case stderrors.Is(err, context.Canceled):
		rec.ErrCode = "SLOW_QUERY_CANCELED"
		writeResourceError(c, clientClosedStatus, "Canceled", "BYOC Slow Query request was canceled", nil)
		return
	case stderrors.Is(err, context.DeadlineExceeded):
		rec.ErrCode = "SLOW_QUERY_TIMEOUT"
		writeResourceError(c, http.StatusGatewayTimeout, "DeadlineExceeded", "BYOC Slow Query request timed out", nil)
		return
	case stderrors.Is(err, service.ErrBYOCSlowQueryUnavailable):
		rec.ErrCode = "SLOW_QUERY_UNAVAILABLE"
		writeResourceError(c, http.StatusServiceUnavailable, "Unavailable", "BYOC Slow Query service is temporarily unavailable", nil)
		return
	case stderrors.Is(err, service.ErrInvalidBYOCSlowQueryTarget), stderrors.Is(err, service.ErrInvalidBYOCSlowQueryResponse):
		rec.ErrCode = "SLOW_QUERY_INVALID_UPSTREAM"
		writeResourceError(c, http.StatusBadGateway, "BadGateway", "BYOC Slow Query returned an invalid response", nil)
		return
	}

	var networkError net.Error
	if stderrors.As(err, &networkError) {
		if networkError.Timeout() {
			rec.ErrCode = "SLOW_QUERY_TIMEOUT"
			writeResourceError(c, http.StatusGatewayTimeout, "DeadlineExceeded", "BYOC Slow Query request timed out", nil)
			return
		}
		rec.ErrCode = "SLOW_QUERY_UNAVAILABLE"
		writeResourceError(c, http.StatusServiceUnavailable, "Unavailable", "BYOC Slow Query service is temporarily unavailable", nil)
		return
	}

	var targetError *clinic_internal_api.UpstreamError
	if stderrors.As(err, &targetError) {
		switch targetError.StatusCode {
		case http.StatusBadRequest:
			rec.ErrCode = "SLOW_QUERY_UNSUPPORTED"
			writeResourceError(c, http.StatusBadRequest, "InvalidArgument", "BYOC Slow Query is not supported for this cluster", nil)
		case http.StatusNotFound:
			rec.ErrCode = "CLUSTER_NOT_FOUND"
			writeResourceError(c, http.StatusNotFound, "NotFound", "cluster not found", nil)
		case http.StatusRequestTimeout, http.StatusGatewayTimeout:
			rec.ErrCode = "SLOW_QUERY_TARGET_TIMEOUT"
			writeResourceError(c, http.StatusGatewayTimeout, "DeadlineExceeded", "BYOC Slow Query target resolution timed out", nil)
		case http.StatusTooManyRequests:
			rec.ErrCode = "SLOW_QUERY_TARGET_RATE_LIMITED"
			writeResourceError(c, http.StatusTooManyRequests, "ResourceExhausted", "BYOC Slow Query target rate limit exceeded", nil)
		case http.StatusServiceUnavailable:
			rec.ErrCode = "SLOW_QUERY_TARGET_UNAVAILABLE"
			writeResourceError(c, http.StatusServiceUnavailable, "Unavailable", "BYOC Slow Query target is temporarily unavailable", nil)
		default:
			rec.ErrCode = "SLOW_QUERY_TARGET_ERROR"
			writeResourceError(c, http.StatusBadGateway, "BadGateway", "failed to resolve BYOC Slow Query target", nil)
		}
		return
	}

	var upstreamError *service.BYOCSlowQueryUpstreamError
	if stderrors.As(err, &upstreamError) {
		switch upstreamError.StatusCode {
		case http.StatusBadRequest:
			rec.ErrCode = "INVALID_SLOW_QUERY_PARAMETERS"
			writeResourceError(c, http.StatusBadRequest, "InvalidArgument", "invalid BYOC Slow Query request", nil)
		case http.StatusNotFound:
			rec.ErrCode = "SLOW_QUERY_NOT_FOUND"
			message := "BYOC Slow Query data is not available for this cluster"
			if operation == service.BYOCSlowQueryOperationDetail {
				message = "slow query detail not found"
			}
			writeResourceError(c, http.StatusNotFound, "NotFound", message, nil)
		case http.StatusTooManyRequests:
			rec.ErrCode = "SLOW_QUERY_RATE_LIMITED"
			writeResourceError(c, http.StatusTooManyRequests, "ResourceExhausted", "BYOC Slow Query rate limit exceeded", nil)
		case http.StatusRequestTimeout, http.StatusGatewayTimeout:
			rec.ErrCode = "SLOW_QUERY_TIMEOUT"
			writeResourceError(c, http.StatusGatewayTimeout, "DeadlineExceeded", "BYOC Slow Query request timed out", nil)
		case http.StatusServiceUnavailable:
			rec.ErrCode = "SLOW_QUERY_UNAVAILABLE"
			writeResourceError(c, http.StatusServiceUnavailable, "Unavailable", "BYOC Slow Query service is temporarily unavailable", nil)
		default:
			rec.ErrCode = "SLOW_QUERY_UPSTREAM_ERROR"
			writeResourceError(c, http.StatusBadGateway, "BadGateway", "BYOC Slow Query upstream request failed", nil)
		}
		return
	}

	rec.ErrCode = "SLOW_QUERY_INTERNAL_ERROR"
	log.Error(c.Request.Context(), "BYOC Slow Query request failed",
		gozap.String("cluster_id", clusterID), gozap.String("error_type", fmt.Sprintf("%T", err)))
	writeResourceError(c, http.StatusBadGateway, "BadGateway", "BYOC Slow Query request failed", nil)
}

// Keep the canonical OpenAPI error type reachable by the annotation parser.
var _ apierrors.APIError
