package v1

import (
	"context"
	"encoding/json"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/pingcap-inc/pingcat/internal/pkg/rstore"

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

	"github.com/pingcap-inc/pingcat/internal/apiserver/store"
	modelv1 "github.com/pingcap-inc/pingcat/model/v1"

	"github.com/pingcap-inc/pchub/pkg/errors"
	"github.com/pingcap-inc/pchub/pkg/log"
)

type LarkEventSrv interface {
	Validate(c *gin.Context, eventStr string) (*EchoData, error)
	DispatcherV1(c *gin.Context, evt1 modelv1.EventSchemaV1)
	DispatcherV2(c *gin.Context, evt2 modelv1.EventSchemaV2)
	HandleMsgCard(c *gin.Context, msgCard modelv1.MsgCardBody) (interface{}, error)
}

type LarkEventService struct {
	store store.Factory
}

var _ LarkEventSrv = (*LarkEventService)(nil)

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

func NewLarkEvent(store store.Factory) *LarkEventService {
	return &LarkEventService{store: store}
}

func (u *LarkEventService) Validate(_ *gin.Context, eventStr string) (*EchoData, error) {
	var dd EchoData
	if err := json.Unmarshal([]byte(eventStr), &dd); err != nil {
		return nil, err
	}

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

func (u *LarkEventService) DispatcherV2(c *gin.Context, evt2 modelv1.EventSchemaV2) {
	log.L(c).Info("LarkEventSrv.DispatcherV2 called.")

	rStore := &rstorage.RedisCluster{
		KeyPrefix: rstore.LarkEventIDPrefix,
	}

	val, err := rStore.GetKey(evt2.Header.EventId)
	if err != nil {
		if !errors.As(err, &rstorage.ErrKeyNotFound) {
			log.L(c).Error(errors.Wrap(err, "error when retrieve key in redis").Error())
		}
	}

	if val != "" {
		log.L(c).Info("Lark Event " + evt2.Header.EventId + " has been handled before.")

		return
	}

	if err := rStore.SetKey(evt2.Header.EventId, "y", time.Minute*10); err != nil {
		log.L(c).Errorf("Error when store lark event_id into redis: %s", err.Error())
	}

	var er error
	switch evt2.Header.EventType {
	case "im.chat.member.user.added_v1", "im.chat.member.user.withdrawn_v1", "im.chat.member.user.deleted_v1":
		u.handleChatMemberEvent(c, evt2)
	case "im.message.receive_v1":
		u.handleChatMsgEvent(c, evt2)
	case "im.chat.member.bot.added_v1":
		u.handleChatMemberBotAddedEvent(c, evt2)
	}

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

func (u *LarkEventService) handleChatMemberBotAddedEvent(c context.Context, evt2 modelv1.EventSchemaV2) {
	log.L(c).Info("LarkEventSrv.handleChatMemberBotAddedEvent called.")

	e, err := json.Marshal(evt2.Event)
	if err != nil {
		log.L(c).Error(err.Error())

		return
	}

	var b modelv1.LarkBotAddedEvent
	if err := json.Unmarshal(e, &b); err != nil {
		log.L(c).Error(err.Error())

		return
	}
	if b.ChatId == "" {
		return
	}
	chatSrv := &ChatService{store: u.store}
	_, _ = chatSrv.BotSendTextLarkMsg(c, "chat_id", b.ChatId, modelv1.GroupHelpInfo)
	_, _ = chatSrv.BotSendSharedChatLarkMsg(c, "chat_id", b.ChatId, AskPingCatChatID)
}

type ChatMemUID struct {
	UserId string `json:"user_id"`
}

type ChatMemUser struct {
	UserId ChatMemUID `json:"user_id"`
}

type MemEventContent struct {
	ChatId string        `json:"chat_id"`
	Users  []ChatMemUser `json:"users"`
}

func (u *LarkEventService) handleChatMemberEvent(c context.Context, evt2 modelv1.EventSchemaV2) {
	log.L(c).Info("LarkEventSrv.handleChatMemberEvent called.")

	chatID, _ := evt2.Event["chat_id"].(string)
	users, _ := evt2.Event["users"].([]interface{})
	if chatID != AskPingCatChatID || len(users) == 0 {
		return
	}

	e, err := json.Marshal(evt2.Event)
	if err != nil {
		log.Error(err.Error())

		return
	}
	var eContent MemEventContent
	if err := json.Unmarshal(e, &eContent); err != nil {
		log.Error(err.Error())

		return
	}

	isRemove := true
	if evt2.Header.EventType == "im.chat.member.user.added_v1" {
		isRemove = false
	}

	rStore := &rstorage.RedisCluster{
		KeyPrefix: rstore.AskPingCatChat,
	}

	for _, u := range eContent.Users {
		if u.UserId.UserId == "" {
			continue
		}
		if isRemove {
			log.Info("Remove user " + u.UserId.UserId + " from 'Ask PingCat' user list in redis")
			rStore.DeleteKey(u.UserId.UserId)
		} else {
			log.Info("Add user " + u.UserId.UserId + " into 'Ask PingCat' user list in redis")
			_ = rStore.SetKey(u.UserId.UserId, "y", 0)
		}
	}
}

type MsgTextCont struct {
	Text string `json:"text"`
}

func (u *LarkEventService) handleChatMsgEvent(c *gin.Context, evt2 modelv1.EventSchemaV2) {
	log.L(c).Info("LarkEventSrv.handleChatMsgEvent called.")

	e, err := json.Marshal(evt2.Event)
	if err != nil {
		log.L(c).Error(err.Error())

		return
	}
	var me modelv1.LarkMsgEvent
	if err := json.Unmarshal(e, &me); err != nil {
		log.L(c).Error(err.Error())

		return
	}

	senderLarkID := me.Sender.SenderID.UserID
	c.Set("larkUserID", senderLarkID)

	chatSrv := &ChatService{store: u.store}
	// todo: utilize the answer from app router and return it directly to lark, thus we can reduce a time cost LLM request
	ok := chatSrv.PreValidateLarkMsg(c, &me)
	if ok {
		_ = chatSrv.ReplyLarkMsg(c, &me)
	}
}

// DispatcherV1 Dispatcher dispatch event v1.
func (u *LarkEventService) DispatcherV1(c *gin.Context, evt1 modelv1.EventSchemaV1) {
	log.L(c).Info("LarkEventSrv.DispatcherV1 called.")

	eventType, _ := evt1.Event["type"].(string)
	if eventType == "p2p_chat_create" {
		user, ok := evt1.Event["user"].(map[string]string)
		if !ok {
			return
		}
		senderLarkID := user["user_id"]
		if senderLarkID == "" {
			return
		}
		c.Set("larkUserID", senderLarkID)
		chatSrv := &ChatService{store: u.store}
		_, _ = chatSrv.BotSendTextLarkMsg(c, "user_id", senderLarkID, modelv1.P2PHelpInfo)
		_, _ = chatSrv.BotSendSharedChatLarkMsg(c, "user_id", senderLarkID, AskPingCatChatID)
	}
}

func (u *LarkEventService) HandleMsgCard(c *gin.Context, msgCard modelv1.MsgCardBody) (interface{}, error) {
	log.L(c).Info("LarkEventSrv.HandleMsgCard called.")

	kba, err := u.store.KBAnswer().GetByAnsID(c, msgCard.OpenMessageID)
	if err != nil {
		log.L(c).Error(err.Error())

		return nil, err
	}
	ac := msgCard.Action.Value
	fbv, ok := ac["fb"]
	if !ok {
		log.L(c).Error("no feedback in action")

		return nil, errors.New("no feedback in action")
	}
	var kbReactions []modelv1.KBReaction
	if kba.Reaction != "" {
		if err := json.Unmarshal([]byte(kba.Reaction), &kbReactions); err != nil {
			log.L(c).Error("no feedback in action")
		}
	}
	kbReactions = append(kbReactions, modelv1.KBReaction{
		V:          fbv,
		UserLarkID: msgCard.UserID,
	})
	rStr, _ := json.Marshal(kbReactions)
	kba.Reaction = string(rStr)
	_, err = u.store.KBAnswer().Update(c, kba)
	if err != nil {
		log.L(c).Error(err.Error())

		return nil, err
	}

	itchatSrv := &ITChatAPPService{store: u.store}

	var refs []KBRef
	if err := json.Unmarshal([]byte(kba.KBRefs), &refs); err != nil {
		log.L(c).Error(err.Error())
	}
	cardCont := itchatSrv.ConstructITKBCardCont(kba.Answer, kba.Question, fbv, refs)

	if fbv == modelv1.ReactionBad {
		if kba.LarkChatID != "" {
			go func() {
				_ = itchatSrv.AddChatMember(c, msgCard.UserID, kba.LarkChatID)
			}()
		} else {
			go func() {
				_ = itchatSrv.TransferManualService(c, kba)
			}()
		}
	}

	return cardCont, nil
}
