package middleware

import (
	"bytes"
	"io"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/pkg/result"
	"github.com/perfect-panel/server/pkg/signature"
	"github.com/perfect-panel/server/pkg/xerr"
)

var (
	publicSignaturePrefixes = []string{
		"/v1/auth",
		"/v1/auth/oauth",
		"/v1/common",
		"/v1/public",
	}
	defaultSignatureSkipPrefixes = []string{
		"/v1/notify/",
		"/v1/iap/notifications",
		"/v1/telegram/webhook",
		"/v1/subscribe/config",
		"/v1/common/log/report",
	}
)

func SignatureMiddleware(srvCtx *svc.ServiceContext) func(c *gin.Context) {
	skipPrefixes := collectSignatureSkipPrefixes(srvCtx)
	return func(c *gin.Context) {
		path := c.Request.URL.Path
		if !isPublicSignaturePath(path) {
			c.Next()
			return
		}

		for _, prefix := range skipPrefixes {
			if strings.HasPrefix(path, prefix) {
				c.Next()
				return
			}
		}
		if !srvCtx.Config.Signature.EnableSignature {
			c.Next()
			return
		}
		appId := c.GetHeader("X-App-Id")
		if appId == "" {
			c.Next()
			return
		}

		timestamp := c.GetHeader("X-Timestamp")
		nonce := c.GetHeader("X-Nonce")
		sig := c.GetHeader("X-Signature")
		if timestamp == "" || nonce == "" || sig == "" {
			result.HttpResult(c, nil, xerr.NewErrCode(xerr.SignatureMissing))
			c.Abort()
			return
		}

		var bodyBytes []byte
		if c.Request.Body != nil {
			bodyBytes, _ = io.ReadAll(c.Request.Body)
			c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
		}

		sts := signature.BuildStringToSign(
			c.Request.Method,
			path,
			c.Request.URL.RawQuery,
			bodyBytes,
			appId,
			timestamp,
			nonce,
		)
		if err := srvCtx.SignatureValidator.Validate(c.Request.Context(), appId, timestamp, nonce, sig, sts); err != nil {
			result.HttpResult(c, nil, xerr.NewErrCode(mapSignatureErr(err)))
			c.Abort()
			return
		}

		c.Next()
	}
}

func isPublicSignaturePath(path string) bool {
	for _, prefix := range publicSignaturePrefixes {
		if strings.HasPrefix(path, prefix) {
			return true
		}
	}
	return false
}

func collectSignatureSkipPrefixes(srvCtx *svc.ServiceContext) []string {
	prefixSet := make(map[string]struct{}, len(defaultSignatureSkipPrefixes)+len(srvCtx.Config.AppSignature.SkipPrefixes)+1)
	for _, prefix := range defaultSignatureSkipPrefixes {
		prefixSet[prefix] = struct{}{}
	}
	for _, prefix := range srvCtx.Config.AppSignature.SkipPrefixes {
		if strings.TrimSpace(prefix) == "" {
			continue
		}
		prefixSet[prefix] = struct{}{}
	}
	if path := strings.TrimSpace(srvCtx.Config.Subscribe.SubscribePath); path != "" {
		prefixSet[path] = struct{}{}
	}

	prefixes := make([]string, 0, len(prefixSet))
	for prefix := range prefixSet {
		prefixes = append(prefixes, prefix)
	}
	return prefixes
}

func mapSignatureErr(err error) uint32 {
	switch err {
	case signature.ErrSignatureMissing:
		return xerr.SignatureMissing
	case signature.ErrSignatureExpired:
		return xerr.SignatureExpired
	case signature.ErrSignatureReplay:
		return xerr.SignatureReplay
	default:
		return xerr.SignatureInvalid
	}
}
