package lark

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

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/spf13/viper"

	"github.com/pingcap-inc/pchub/pkg/rstorage"

	srvv1 "github.com/pingcap-inc/pchub/internal/apiserver/service/v1"
	"github.com/pingcap-inc/pchub/internal/apiserver/store"
	modelv1 "github.com/pingcap-inc/pchub/model/v1"
	"github.com/pingcap-inc/pchub/pkg/errors"
	"github.com/pingcap-inc/pchub/pkg/log"
	"github.com/pingcap-inc/pchub/pkg/util/ginres"
)

type LarkEventController struct {
	srv srvv1.Service
}

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

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

func (e *LarkEventController) InitAskPCHUserList() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := e.initAskPCHUserList(ctx); err != nil {
		log.Error(err.Error())
	}
}

func (e *LarkEventController) initAskPCHUserList(ctx context.Context) error {
	for {
		if rstorage.Connected() {
			log.Info("LarkEventController.initAskPCHUserList called.")
			er := e.srv.LarkChat().RefreshAskPCH(ctx)
			if er != nil {
				return er
			}

			return nil
		}

		// Sleep for a second to continue
		log.Info("Waiting for the redis connected, retry in 1 second.")
		time.Sleep(1 * time.Second)

		select {
		case <-ctx.Done():
			log.Error("Can not connect the redis within the specified time interval.")
		default:
		}
	}
}

func (e *LarkEventController) HandleMockEvent(c *gin.Context) {
	log.L(c).Info("handle lark mock event function called.")

	params := ginres.ParseQuery(c, "s")
	if params["s"] == "" {
		ginres.WriteRawResponse(c, errors.New("s is empty"), nil)

		return
	}

	s := params["s"]

	log.L(c).Info("Receive lark event: " + s)

	var psd interface{}
	if err := json.Unmarshal([]byte(s), &psd); err != nil {
		ginres.WriteRawResponse(c, err, nil)

		return
	}

	psdMap, ok := psd.(map[string]interface{})
	if !ok {
		ginres.WriteRawResponse(c, errors.New("Unknown event schema"), nil)

		return
	}

	// if a url_verification req
	eventType, hasType := psdMap["type"]
	eventTypeStr, isStr := eventType.(string)
	if hasType && isStr && eventTypeStr == "url_verification" {
		d, err := e.srv.LarkEvent().Validate(c, s)
		if err != nil {
			ginres.WriteRawResponse(c, err, nil)

			return
		}
		ginres.WriteRawResponse(c, nil, d)

		return
	}

	// TODO: check whether one event has been handled or not by event id
	if psdMap["schema"] == "2.0" {
		var evt modelv1.EventSchemaV2
		if err := json.Unmarshal([]byte(s), &evt); err != nil {
			ginres.WriteRawResponse(c, errors.New("Unknown event schema"), nil)

			return
		}

		// return first
		ginres.WriteRawResponse(c, nil, "")

		e.srv.LarkEvent().DispatcherV2(c, evt)

		return
	}

	// if v1
	var evt1 modelv1.EventSchemaV1
	if err := json.Unmarshal([]byte(s), &evt1); err != nil {
		ginres.WriteRawResponse(c, errors.New("Unknown event schema"), nil)

		return
	}

	// return first
	ginres.WriteRawResponse(c, nil, "")

	e.srv.LarkEvent().DispatcherV1(c, evt1)
}

func (e *LarkEventController) HandleEvent(c *gin.Context) {
	log.L(c).Info("handle lark event function called.")

	var ed EchoEncodeData
	if err := c.ShouldBindBodyWith(&ed, binding.JSON); err != nil {
		ginres.WriteRawResponse(c, err, nil)

		return
	}

	key := viper.GetString("lark.eventSecret")
	s, err := larkEventDecrypt(ed.Encrypt, key)
	if err != nil {
		log.Error(err.Error())
		ginres.WriteRawResponse(c, err, nil)

		return
	}

	log.L(c).Info("Receive lark event: " + s)

	var psd interface{}
	if err := json.Unmarshal([]byte(s), &psd); err != nil {
		ginres.WriteRawResponse(c, err, nil)

		return
	}

	psdMap, ok := psd.(map[string]interface{})
	if !ok {
		ginres.WriteRawResponse(c, errors.New("Unknown event schema"), nil)

		return
	}

	// if a url_verification req
	eventType, hasType := psdMap["type"]
	eventTypeStr, isStr := eventType.(string)
	if hasType && isStr && eventTypeStr == "url_verification" {
		d, err := e.srv.LarkEvent().Validate(c, s)
		if err != nil {
			ginres.WriteRawResponse(c, err, nil)

			return
		}
		ginres.WriteRawResponse(c, nil, d)

		return
	}

	// TODO: check whether one event has been handled or not by event id
	if psdMap["schema"] == "2.0" {
		var evt modelv1.EventSchemaV2
		if err := json.Unmarshal([]byte(s), &evt); err != nil {
			ginres.WriteRawResponse(c, errors.New("Unknown event schema"), nil)

			return
		}

		// return first
		ginres.WriteRawResponse(c, nil, "")

		e.srv.LarkEvent().DispatcherV2(c, evt)

		return
	}

	// if v1
	var evt1 modelv1.EventSchemaV1
	if err := json.Unmarshal([]byte(s), &evt1); err != nil {
		ginres.WriteRawResponse(c, errors.New("Unknown event schema"), nil)

		return
	}

	// return first
	ginres.WriteRawResponse(c, nil, "")

	e.srv.LarkEvent().DispatcherV1(c, evt1)
}

func larkEventDecrypt(encrypt, key string) (string, error) {
	buf, err := base64.StdEncoding.DecodeString(encrypt)
	if err != nil {
		return "", fmt.Errorf("base64StdEncode Error[%w]", 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[%w]", 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
}
