package api

import (
	"crypto/subtle"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/alertstore/pkg/metrics"
)

// BearerToken is one accepted webhook credential. Name is for observability
// only; access is by Secret.
type BearerToken struct {
	Name   string
	Secret string
}

func requireBearerToken(expected []BearerToken) gin.HandlerFunc {
	return func(c *gin.Context) {
		if len(expected) == 0 {
			metrics.WebhookRequests.WithLabelValues("alertmanager", "not_configured").Inc()
			c.AbortWithStatusJSON(http.StatusServiceUnavailable, gin.H{
				"error": "alertmanager webhook authentication is not configured",
			})
			return
		}

		scheme, secret, ok := strings.Cut(c.GetHeader("Authorization"), " ")
		name, authorized := "", false
		if ok && strings.EqualFold(scheme, "Bearer") {
			name, authorized = matchToken(secret, expected)
		}
		if !authorized {
			metrics.WebhookRequests.WithLabelValues("alertmanager", "unauthorized").Inc()
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
			return
		}

		c.Set("webhook_token_name", name)
		metrics.WebhookAuthorized.WithLabelValues("alertmanager", name).Inc()
		c.Next()
	}
}

// matchToken returns the name of the expected token whose secret equals secret.
// Every candidate is compared in constant time without short-circuiting, so
// neither the match nor its position leaks through timing.
func matchToken(secret string, expected []BearerToken) (name string, matched bool) {
	for _, e := range expected {
		if len(secret) == len(e.Secret) && subtle.ConstantTimeCompare([]byte(secret), []byte(e.Secret)) == 1 {
			name, matched = e.Name, true
		}
	}
	return name, matched
}
