package event

import (
	"context"
	"encoding/json"
	"github.com/marmotedu/errors"
	"github.com/robfig/cron/v3"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	srvv1 "github.com/pingcap-inc/pinsights/internal/larkbot/service/v1"
	"github.com/pingcap-inc/pinsights/internal/larkbot/store"
	"github.com/pingcap-inc/pinsights/internal/pkg/util"
	"github.com/pingcap-inc/pinsights/pkg/log"
	"github.com/pingcap-inc/pinsights/pkg/storage"
	"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 EchoEncodeData struct {
	Encrypt string `json:"encrypt"`
}

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

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 {
		core.WriteRawResponse(c, err, nil)
		return
	}

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

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

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

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

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

	var evt srvv1.EventSchemaV2
	if err := json.Unmarshal(jsonc.ConvertJSON2Camel([]byte(s)), &evt); err != nil {
		core.WriteRawResponse(c, errors.New("Unknown event schema"), nil)
		return
	}

	// return first and refresh
	// TODO: check one event has been handled or not by event id
	core.WriteRawResponse(c, nil, "")

	var er error
	switch evt.Header.EventType {
	case "im.chat.member.user.added_v1":
		er = e.srv.WhiteList().RefreshByEvent(c, &evt)
	case "im.chat.member.user.withdrawn_v1":
		er = e.srv.WhiteList().RefreshByEvent(c, &evt)
	case "im.chat.member.user.deleted_v1":
		er = e.srv.WhiteList().RefreshByEvent(c, &evt)
	}

	if er != nil {
		log.Errorf("%#+v", err)
	}
}

func (e *LarkEventController) ManualTriggerWhiteList(c *gin.Context) {
	log.L(c).Info("ManualTriggerWhiteList function called.")
	er := e.srv.WhiteList().Refresh()
	if er != nil {
		core.WriteRawResponse(c, er, nil)
		return
	}

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

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

func (e *LarkEventController) initWhiteList(ctx context.Context) error {
	for {
		if storage.Connected() {
			log.Info("Init whitelist function called.")
			er := e.srv.WhiteList().Refresh()
			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) InitContactSyncCron() {
	if err := e.initContact(); err != nil {
		log.Error(err.Error())
	}
}

func (e *LarkEventController) initContact() error {
	log.Info("Init call sync contact(groups).")
	er := e.srv.Contact().Refresh()
	if er != nil {
		log.Error(errors.Wrap(er, "Error when exec contact refresh in cron task.").Error())
		return er
	}

	log.Info("Start cron task to sync contact(groups): every 6h.")

	c := cron.New()
	_, err := c.AddFunc("@every 6h", func() {
		log.Info("Cron task called to sync contact(groups).")
		er := e.srv.Contact().Refresh()
		if er != nil {
			log.Error(errors.Wrap(er, "Error when exec contact refresh in cron task.").Error())
		}
	})

	c.Start()

	return err
}

func (e *LarkEventController) ManualTriggerGroups(c *gin.Context) {
	log.L(c).Info("ManualTriggerGroups function called.")

	er := e.srv.Contact().Refresh()
	if er != nil {
		core.WriteRawResponse(c, er, nil)
		return
	}

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