package middlewares

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	larkcard "github.com/larksuite/oapi-sdk-go/v3/card"
	larkevent "github.com/larksuite/oapi-sdk-go/v3/event"

	"github.com/tidbcloud/aisupport/packages/sdk-go/infra/apiserver/utils"
)

const (
	// CtxLarkEvent is the event info of the lark event callback.
	// Value Type: *larkevent.EventV2Body
	CtxLarkEvent = "LARK_EVENT" // type:  &larkevent.EventV2Body{}
	// CtxLarkCardAction is the action info of the lark card callback.
	// Value Type: *larkcard.CardAction
	CtxLarkCardAction = "LARK_CARD_ACTION" // type: *larkcard.CardAction
)

type LarkMiddlewareBuiler struct {
	EncryptKey        string
	VerificationToken string
}

func (b *LarkMiddlewareBuiler) EventMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		rawBody, err := utils.FetchBody(c)
		if err != nil {
			utils.Fail(c, err)
			return
		}

		// decrypt
		plain, err := b.larkDecrypt(rawBody)
		if err != nil {
			utils.Fail(c, err)
			return
		}

		// parse
		body := &larkevent.EventV2Body{}
		err = json.Unmarshal(plain, body)
		if err != nil {
			utils.Fail(c, err)
			return
		}

		// handle challenge
		if larkevent.ReqType(body.Type) == larkevent.ReqTypeChallenge {
			c.JSON(http.StatusOK, gin.H{"challenge": body.Challenge})
			c.Abort()
			return
		}

		// verify sign
		err = b.verifySignV2(c, rawBody)
		if err != nil {
			utils.Fail(c, err)
			return
		}

		// set cardAction to context
		c.Set(CtxLarkEvent, body)
		c.Next()
	}
}

// CardMiddleware parse and verify lark card event.
//
// It will set cardAction to context.
func (b *LarkMiddlewareBuiler) CardMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		rawBody, err := utils.FetchBody(c)
		if err != nil {
			utils.Fail(c, err)
			return
		}

		// decrypt
		plain, err := b.larkDecrypt(rawBody)
		if err != nil {
			utils.Fail(c, err)
			return
		}

		// parse
		cardAction := &larkcard.CardAction{}
		err = json.Unmarshal(plain, cardAction)
		if err != nil {
			utils.Fail(c, err)
			return
		}

		// handle challenge
		if larkevent.ReqType(cardAction.Type) == larkevent.ReqTypeChallenge {
			c.JSON(http.StatusOK, gin.H{"challenge": cardAction.Challenge})
			c.Abort()
			return
		}

		// verify sign
		err = b.verifySign(c, rawBody)
		if err != nil {
			utils.Fail(c, err)
			return
		}

		// set cardAction to context
		c.Set(CtxLarkCardAction, cardAction)
		c.Next()
	}
}

func (b *LarkMiddlewareBuiler) larkDecrypt(bs []byte) ([]byte, error) {
	var plain []byte
	var encrypt larkevent.EventEncryptMsg
	err := json.Unmarshal(bs, &encrypt)
	if err != nil {
		return nil, err
	}
	if encrypt.Encrypt != "" {
		if b.EncryptKey == "" {
			return nil, fmt.Errorf("encrypt_key not found")
		}
		plain, err = larkevent.EventDecrypt(encrypt.Encrypt, b.EncryptKey)
		if err != nil {
			return nil, err
		}
	} else {
		plain = bs
	}

	return plain, nil
}

// For lark event, we use the v2 signature verification.
// https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/encrypt-key-encryption-configuration-case
func (b *LarkMiddlewareBuiler) verifySignV2(c *gin.Context, rawBody []byte) error {
	if b.EncryptKey == "" {
		return nil
	}

	requestTimestamp := c.GetHeader(larkevent.EventRequestTimestamp)
	requestNonce := c.GetHeader(larkevent.EventRequestNonce)
	sourceSign := c.GetHeader(larkevent.EventSignature)

	targetSign := larkevent.Signature(requestTimestamp, requestNonce, b.EncryptKey, string(rawBody))

	if targetSign == sourceSign {
		return nil
	}
	return fmt.Errorf("the result of signature verification failed")
}

// For lark card event, we use the v1 signature verification.
// Ref: https://open.feishu.cn/document/common-capabilities/message-card/add-card-interaction/message-card-security-verification
func (b *LarkMiddlewareBuiler) verifySign(c *gin.Context, rawBody []byte) error {
	if b.VerificationToken == "" {
		return nil
	}

	requestTimestamp := c.GetHeader(larkevent.EventRequestTimestamp)
	requestNonce := c.GetHeader(larkevent.EventRequestNonce)
	sourceSign := c.GetHeader(larkevent.EventSignature)

	targetSign := larkcard.Signature(requestTimestamp, requestNonce, b.VerificationToken, string(rawBody))

	if targetSign == sourceSign {
		return nil
	}
	return fmt.Errorf("the result of signature verification failed")
}
