package v1

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

	"github.com/pingcap-inc/pchub/internal/apiserver/store"
	"github.com/pingcap-inc/pchub/internal/pkg/code"
	"github.com/pingcap-inc/pchub/internal/pkg/rstore"
	v1 "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/rstorage"
)

type larkChatService struct {
	store store.Factory
}

type LarkChatSrv interface {
	RefreshAskPCH(ctx context.Context) error
	SyncChats(ctx context.Context) error
	Search(ctx context.Context, kw string) ([]*v1.LartChat, error)
}

var _ LarkChatSrv = (*larkChatService)(nil)

func newLarkChat(srv *service) *larkChatService {
	return &larkChatService{store: srv.store}
}

func (l *larkChatService) Search(ctx context.Context, kw string) ([]*v1.LartChat, error) {
	us, err := l.store.LarkChat().Search(ctx, kw)
	if err != nil {
		coder := errors.ParseCoder(err)
		if coder.Code() != code.ErrRecordNotFound {
			log.L(ctx).Error(errors.Wrap(err, "Error when LarkChatStore.Search from db").Error())

			return nil, errors.Wrap(err, "Error when search chat from db.")
		}
	}

	return us, nil
}

const AskPCHChatID = "oc_d7efe65e698ddc10153f1dc9dd567ede"

type MemItem struct {
	MemberIdType string `json:"member_id_type"`
	MemberId     string `json:"member_id"`
	Name         string `json:"name"`
}

type MembersData struct {
	Items     []MemItem `json:"items"`
	PageToken string    `json:"page_token"`
	HasMore   bool      `json:"has_more"`
}

func (l *larkChatService) RefreshAskPCH(ctx context.Context) error {
	log.L(ctx).Info("LarkChatSrv.RefreshAskPCH called")

	commonService := &commonService{store: l.store}

	mItems, err := getChatMembers(ctx, commonService, "")
	if err != nil {
		log.L(ctx).Error(err.Error())

		return errors.Wrap(err, "Error when get lark chat member bacause of the error in getChatMembers")
	}

	log.L(ctx).Info("success to get ask pch group members.")

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

	if ok := rStore.DeleteScanMatch(rstore.AskPCHChat + "*"); !ok {
		return errors.New("Can not delete old keys")
	}

	for _, it := range *mItems {
		rStore.SetKey(it.MemberId, "y", 0)
	}

	log.L(ctx).Info("success to set ask pch group members in redis.")

	return nil
}

func getChatMembers(ctx context.Context, cs *commonService, pageToken string) (*[]MemItem, error) {
	var pageTokenQ string
	if pageToken != "" {
		pageTokenQ = "&page_token=" + pageToken
	}

	memsJsonRaw, err := cs.GetDataByReqLarkAPI(ctx, "GET", LarkOpenAPIBase+"/im/v1/chats/"+AskPCHChatID+"/members?member_id_type=user_id&page_size=100"+pageTokenQ, nil, nil)
	if err != nil {
		log.Error(err.Error())

		return nil, errors.WithCode(code.ErrHttpRequest, "Error when request lark chat members api")
	}

	var d MembersData
	if err := json.Unmarshal(memsJsonRaw, &d); err != nil {
		log.Error(err.Error())

		return nil, errors.WithCode(code.ErrDecodingJSON, "Error when unmarshal lark get chat member res")
	}

	var result []MemItem
	result = append(result, d.Items...)
	if d.HasMore && d.PageToken != "" {
		mItems, err := getChatMembers(ctx, cs, d.PageToken)
		if err != nil {
			log.Error(err.Error())

			return nil, errors.Wrap(err, "Error when getChatMembers for next page")
		}
		result = append(result, *mItems...)
	}

	return &result, nil
}

func (l *larkChatService) SyncChats(ctx context.Context) error {
	log.L(ctx).Info("LarkChatSrv.SyncChats called.")

	commonService := &commonService{store: l.store}

	larkChats, err := getLarkChats(ctx, commonService, "")
	if err != nil {
		log.L(ctx).Error(err.Error())

		return errors.Wrap(err, "Error when get lark chats due to the error in getLarkChats.")
	}

	log.L(ctx).Info("Success to get lark chats, total: " + strconv.Itoa(len(*larkChats)))

	chats, err := l.store.LarkChat().ListAll(ctx)
	if err != nil {
		coder := errors.ParseCoder(err)
		if coder.Code() != code.ErrRecordNotFound {
			log.L(ctx).Error(errors.Wrap(err, "Error when LarkChatStore.ListAll from db").Error())

			return errors.Wrap(err, "Error when list chats from db.")
		}
	}
	nCS := make(map[string]*v1.LartChat)
	for _, c := range chats {
		nCS[c.ChatLarkID] = c
	}

	newChatList, updateChatList, delChatIDsChatList := diffChats(larkChats, &nCS)

	err = l.store.LarkChat().Sync(ctx, newChatList, updateChatList, delChatIDsChatList)
	if err != nil {
		log.L(ctx).Error(err.Error())

		return errors.Wrap(err, "Error when sync chats into db, nothing will sync into db.")
	}

	log.L(ctx).Info("Success to sync lark chats.")

	return nil
}

func diffChats(larkChats *[]LarkChatItem, nCS *map[string]*v1.LartChat) (ncs, ucs []*v1.LartChat, dids []string) {
	larkChatIds := make(map[string]bool, len(*larkChats))

	for _, ch := range *larkChats {
		cInDB, ok := (*nCS)[ch.ChatID]
		larkChatIds[ch.ChatID] = true
		if ok {
			if cInDB.Name != ch.Name || cInDB.Desc != ch.Description || cInDB.Avatar != ch.Avatar || cInDB.OwnerID != ch.OwnerID {
				cInDB.Name = ch.Name
				cInDB.Desc = ch.Description
				cInDB.Avatar = ch.Avatar
				cInDB.OwnerID = ch.OwnerID
				ucs = append(ucs, cInDB)
			}
		} else {
			newC := v1.LartChat{
				ChatLarkID: ch.ChatID,
				CreatedAt:  time.Now(),
				Avatar:     ch.Avatar,
				Name:       ch.Name,
				Desc:       ch.Description,
				OwnerID:    ch.OwnerID,
			}
			ncs = append(ncs, &newC)
		}
	}

	for _, c := range *nCS {
		v, ok := larkChatIds[c.ChatLarkID]
		if ok && v {
			continue
		}
		dids = append(dids, c.ChatLarkID)
	}

	return ncs, ucs, dids
}

type LarkChatItem struct {
	ChatID      string `json:"chat_id"`
	Name        string `json:"name"`
	Avatar      string `json:"avatar"`
	Description string `json:"description"`
	OwnerID     string `json:"owner_id"`
}

type LarkChatsData struct {
	Items     []LarkChatItem `json:"items"`
	PageToken string         `json:"page_token"`
	HasMore   bool           `json:"has_more"`
}

func getLarkChats(ctx context.Context, cs *commonService, pageToken string) (*[]LarkChatItem, error) {
	var pageTokenQ string
	if pageToken != "" {
		pageTokenQ = "&page_token=" + pageToken
	}

	chatsJsonRaw, err := cs.GetDataByReqLarkAPI(ctx, "GET", LarkOpenAPIBase+"/im/v1/chats?page_size=50"+pageTokenQ, nil, nil)
	if err != nil {
		log.Error(err.Error())

		return nil, errors.Wrap(err, "Error when request lark chats api")
	}

	var d LarkChatsData
	if err = json.Unmarshal(chatsJsonRaw, &d); err != nil {
		log.L(ctx).Error(err.Error())

		return nil, errors.Wrap(err, "Error when unmarshal lark chats data")
	}

	var result []LarkChatItem
	result = append(result, d.Items...)
	if d.HasMore && d.PageToken != "" {
		mItems, err := getLarkChats(ctx, cs, d.PageToken)
		if err != nil {
			log.Error(err.Error())

			return nil, errors.Wrap(err, "Error when getLarkChats for next page")
		}
		result = append(result, *mItems...)
	}

	return &result, nil
}
