package lark

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/marmotedu/errors"
	srvv1 "github.com/pingcap-inc/pinsights/internal/apiserver/service/v1"
	"github.com/pingcap-inc/pinsights/internal/apiserver/store"
	"github.com/pingcap-inc/pinsights/pkg/log"
	"github.com/pingcap-inc/pinsights/pkg/util/core"
	"github.com/pingcap-inc/pinsights/pkg/util/jsonc"
	"github.com/spf13/viper"
)

type LarkEventController struct {
	srv srvv1.Service
}

type EchoRes struct {
	Challenge string `json:"challenge"`
}

type EchoData struct {
	Challenge string `json:"challenge"`
}

type EchoEncodeData struct {
	Encrypt string `json:"encrypt"`
}

func NewLarkEventController(store store.Factory) *LarkEventController {
	return &LarkEventController{
		srv: srvv1.NewService(store),
	}
}

func (e *LarkEventController) ValidateLarkEvent(c *gin.Context) {
	log.L(c).Info("validate lark event sub function called.")

	var ed EchoEncodeData
	if err := c.ShouldBindBodyWith(&ed, binding.JSON); err != nil {
		core.WriteResponse(c, err, nil)
		return
	}

	key := viper.GetString("lark.eventSecret")
	s, err := Decrypt(ed.Encrypt, key)
	if err != nil {
		log.Error(err.Error())
		core.WriteResponse(c, err, nil)
		return
	}

	var dd EchoData
	if err := json.Unmarshal(jsonc.ConvertJSON2Camel([]byte(s)), &dd); err != nil {
		core.WriteResponse(c, err, nil)
		return
	}

	core.WriteRawResponse(c, nil, &EchoRes{
		Challenge: dd.Challenge,
	})
}

func Decrypt(encrypt string, key string) (string, error) {
	buf, err := base64.StdEncoding.DecodeString(encrypt)
	if err != nil {
		return "", fmt.Errorf("base64StdEncode Error[%v]", err)
	}
	if len(buf) < aes.BlockSize {
		return "", errors.New("cipher  too short")
	}
	keyBs := sha256.Sum256([]byte(key))
	block, err := aes.NewCipher(keyBs[:sha256.Size])
	if err != nil {
		return "", fmt.Errorf("AESNewCipher Error[%v]", err)
	}
	iv := buf[:aes.BlockSize]
	buf = buf[aes.BlockSize:]
	// CBC mode always works in whole blocks.
	if len(buf)%aes.BlockSize != 0 {
		return "", errors.New("ciphertext is not a multiple of the block size")
	}
	mode := cipher.NewCBCDecrypter(block, iv)
	mode.CryptBlocks(buf, buf)
	n := strings.Index(string(buf), "{")
	if n == -1 {
		n = 0
	}
	m := strings.LastIndex(string(buf), "}")
	if m == -1 {
		m = len(buf) - 1
	}
	return string(buf[n : m+1]), nil
}
