package v1

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"

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

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

type OncallBotChatAPPService struct {
	store store.Factory
}

type OncallBotChatAPPSrv interface {
	HandleLarkMsg(c context.Context, me *v1.LarkMsgEvent, trimStr string)
}

var _ OncallBotChatAPPSrv = (*OncallBotChatAPPService)(nil)

func NewOncallBotChatAPP(store store.Factory) *OncallBotChatAPPService {
	return &OncallBotChatAPPService{store: store}
}

const (
	// TODO don't hard code these put them in a .gitignore'd config file.
	oncallBotAccessKey    = "6437d2f93d5dd0274c1590ac"
	oncallBotAccessSecret = "fc4666c823b42fc671ce192d5f040d8d"
	OncallBotAPIEndPoint  = "https://chat-api.pingcap.net"
)

type ChatBotAPIRes struct {
	Content     string              `json:"content"`
	Instruction string              `json:"instruction"`
	Links       []ChatBotAPIResLink `json:"links"`
}

type ChatBotAPIResLink struct {
	Link  string `json:"link"`
	Title string `json:"title"`
}

func (s *OncallBotChatAPPService) HandleLarkMsg(c context.Context, me *v1.LarkMsgEvent, trimStr string) {
	log.L(c).Info("OncallBotChatAPPSrv.HandleLarkMsg called.")

	chatSrv := NewChat(s.store)
	if trimStr == "" {
		_, _ = chatSrv.SendTextLarkMsg(c, me, "Please input what you want to ask oncall-bot, e.g. /kb xxx")

		return
	}

	larkReaction, err := chatSrv.markMsgOnIt(c, me)
	if err != nil {
		log.L(c).Error(errors.Wrap(err, "Error when mark msg OnIt via lark reaction").Error())
	}

	// PingChat uses the email address for identification.
	var senderEmail string
	sender, err := s.store.User().GetByAnyID(c, me.Sender.SenderID.UserID)
	if err != nil {
		log.L(c).Error(errors.Wrapf(err, "User not found for %s", me.Sender.SenderID.UserID).Error())
		senderEmail = "lark_unknown:" + me.Sender.SenderID.UserID
	} else {
		senderEmail = sender.Email
	}

	res, err := s.oncallBotAPI(c, senderEmail, trimStr)
	if err != nil {
		_, _ = chatSrv.SendTextLarkMsg(c, me, "Query failed, please try again later.\n\n"+err.Error())
		if larkReaction != nil {
			err = chatSrv.unmarkMsgOnIt(c, me, larkReaction)
			if err != nil {
				log.L(c).Error(errors.Wrap(err, "Error when unmark msg OnIt via lark reaction").Error())
			}
		}

		return
	}

	// reply msg
	rcont := res.Content
	if len(res.Links) > 0 {
		if rcont != "" {
			rcont += "\n\n"
		}
		rcont += "Reference links:\n"
		for idx, link := range res.Links {
			rcont += fmt.Sprintf("%d. [%s](%s)\n", idx+1, link.Title, link.Link)
		}
	}
	_, _ = chatSrv.SendTextLarkMsg(c, me, rcont)
	if larkReaction != nil {
		err = chatSrv.unmarkMsgOnIt(c, me, larkReaction)
		if err != nil {
			log.L(c).Error(errors.Wrap(err, "Error when unmark msg OnIt via lark reaction").Error())
		}
	}
}

type OncallAPIReqDataItem struct {
	Role    string `json:"role"`
	Content string `json:"content"`
}

func (s *OncallBotChatAPPService) oncallBotAPI(c context.Context, senderEmail, msg string) (*ChatBotAPIRes, error) {
	log.L(c).Info("OncallBotChatAPPSrv.oncallBotAPI called.")

	reqBody := []OncallAPIReqDataItem{
		{
			Role:    "user",
			Content: msg,
		},
	}
	reqb, err := json.Marshal(&reqBody)
	if err != nil {
		log.L(c).Error(err.Error())

		return nil, errors.WithCode(code.ErrEncodingJSON, err.Error())
	}
	req, _ := http.NewRequestWithContext(c, "POST", OncallBotAPIEndPoint+"/chat", bytes.NewReader(reqb))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-PingChat-Access-Key", oncallBotAccessKey)
	req.Header.Set("X-PingChat-Access-Secret", oncallBotAccessSecret)
	req.Header.Set("X-PingChat-User", senderEmail)
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		log.L(c).Error(err.Error())

		return nil, errors.WithCode(code.ErrOncallBotRequest, "error when request query PingChat api, err: \n%s", err.Error())
	}
	defer func() {
		_ = resp.Body.Close()
	}()

	b, _ := io.ReadAll(resp.Body)
	if resp.StatusCode != http.StatusOK {
		log.L(c).Error(string(b))

		return nil, errors.WithCode(code.ErrOncallBotRequest, "error when request PingChat bot api, err: \n%s", string(b))
	}

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

		return nil, errors.WithCode(code.ErrDecodingJSON, "error when unmarshal oncall bot api err res, err: \n%s", err.Error())
	}

	return &res, nil
}
