package company

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/spf13/viper"

	"github.com/pingcap-inc/security/pkg/errors"
	"github.com/pingcap-inc/security/pkg/log"
	"github.com/pingcap-inc/security/pkg/util/ginres"

	srvv1 "github.com/pingcap-inc/security/internal/alert-server/service/v1"
	"github.com/pingcap-inc/security/internal/alert-server/store"
	v1 "github.com/pingcap-inc/security/model/v1"
)

type ContractScanController struct {
	srv srvv1.Service
}

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

func (g *ContractScanController) ContractScan(c *gin.Context) {
	log.L(c).Info("ContractScanController.Company function called.")

	ginres.WriteResponse(c, nil, "Start check contract scan.")

	g.srv.ContractScan().StartCheck(c)
}

func (g *ContractScanController) Fsc(c *gin.Context) {
	log.L(c).Info("Fsc function called.")

	ginres.WriteResponse(c, nil, "Start check fsc.")

	g.srv.Fsc().Fsc(c)
}

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

func (g *ContractScanController) 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("larkdata.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 := 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 v1.Event
		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, "")

		g.srv.CompanySearch().StartSearch(c, evt)

		return
	}

	ginres.WriteRawResponse(c, nil, "")
}

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
}

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

func validate(c *gin.Context, key string) (*EchoData, error) {
	var dd EchoData
	if err := json.Unmarshal([]byte(key), &dd); err != nil {
		return nil, err
	}

	return &EchoData{
		Challenge: dd.Challenge,
	}, nil
}

func (g *ContractScanController) DunCk(c *gin.Context) {
	log.L(c).Info("DunCk function called.")

	ginres.WriteResponse(c, nil, "Start check dun.")

	g.srv.Duns().DunCheck(c)
}

func (g *ContractScanController) TestBitbale(c *gin.Context) {
	log.L(c).Info("TestBitbale function called.")

	ginres.WriteResponse(c, nil, "Start test bitable.")

	g.srv.CompanySearch().TestBitable(c)
}
