package adapter

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"
)

// dispatchSignatureHeader and the scheme below mirror alertstore's sender
// (services/alertstore/pkg/delivery/hmac_sign.go). The two live in separate
// modules, so the format is duplicated rather than shared; keep them in step.
// No "X-" prefix, per RFC 6648.
//
//	Alertstore-Signature: t=<unix-seconds>,v1=<hex(HMAC-SHA256(secret, "<t>." + body))>
const dispatchSignatureHeader = "Alertstore-Signature"

// signatureTolerance is how far the signed timestamp may be from now. It bounds
// the window in which a captured dispatch can be replayed; a few minutes absorbs
// ordinary clock skew between the two pods without leaving that window wide.
const signatureTolerance = 5 * time.Minute

// verifySignature refuses a dispatch that is not signed by alertstore. An empty
// secret disables the check and streams the body straight through, unchanged.
func (a *Adapter) verifySignature(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if a.cfg.DispatchHMACSecret == "" {
			next(w, r)
			return
		}
		// The signature covers the body, so it has to be read whole here and
		// handed to the next handler intact — the same 4 MiB bound handleDispatch
		// would apply on its own.
		body, err := io.ReadAll(io.LimitReader(r.Body, 4<<20))
		if err != nil {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "cannot read body"})
			return
		}
		r.Body = io.NopCloser(bytes.NewReader(body))

		if !validSignature(a.cfg.DispatchHMACSecret, r.Header.Get(dispatchSignatureHeader), body, time.Now()) {
			a.logger.Warn("dispatch signature rejected",
				"remote", r.RemoteAddr, "xff", r.Header.Get("X-Forwarded-For"))
			writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid signature"})
			return
		}
		next(w, r)
	}
}

// validSignature parses a `t=..,v1=..` header, rejects a stale timestamp, and
// compares the HMAC in constant time.
func validSignature(secret, header string, body []byte, now time.Time) bool {
	var tPart, v1 string
	for _, field := range strings.Split(header, ",") {
		k, v, ok := strings.Cut(strings.TrimSpace(field), "=")
		if !ok {
			continue
		}
		switch k {
		case "t":
			tPart = v
		case "v1":
			v1 = v
		}
	}
	if tPart == "" || v1 == "" {
		return false
	}
	ts, err := strconv.ParseInt(tPart, 10, 64)
	if err != nil {
		return false
	}
	if d := now.Sub(time.Unix(ts, 0)); d > signatureTolerance || d < -signatureTolerance {
		return false
	}

	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(tPart))
	mac.Write([]byte("."))
	mac.Write(body)
	want := mac.Sum(nil)

	got, err := hex.DecodeString(v1)
	if err != nil {
		return false
	}
	return hmac.Equal(got, want)
}
