package v1

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

	"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 QueryDBChatAPPService struct {
	store store.Factory
}

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

var _ QueryDBChatAPPSrv = (*QueryDBChatAPPService)(nil)

func NewQueryDBChatAPP(store store.Factory) *QueryDBChatAPPService {
	return &QueryDBChatAPPService{store: store}
}

func (s *QueryDBChatAPPService) HandleLarkMsg(c context.Context, me *v1.LarkMsgEvent, trimStr string) {
	cmdPrefix := regexp.MustCompile(`^/q(uery)?\s+`)
	stripStr := cmdPrefix.ReplaceAllString(trimStr, "")

	chatSrv := NewChat(s.store)

	if stripStr == "" {
		_, _ = chatSrv.SendTextLarkMsg(c, me, "Please input what you want to query, e.g. /q 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())
	}

	res, err := s.queryDB(c, stripStr)
	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
	jsonData, err := json.MarshalIndent(res.Result.Data.Rows, "", "    ")
	if err != nil {
		jsonData = []byte("Rows data is not json format")
	}
	rMsg := fmt.Sprintf("Data rows:\n------\n%s\n------\n\n```\n%s\n```\n\nAI Latency: %s", jsonData, res.Result.Data.Result.SQL, res.Result.Data.Result.AILatency)
	_, _ = chatSrv.SendTextLarkMsg(c, me, rMsg)
	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 QueryDBRes struct {
	Result QueryDBResResult `json:"result"`
}
type QueryDBResResult struct {
	Data QueryDBResResultData `json:"data"`
}
type QueryDBResResultData struct {
	Rows   []map[string]interface{}   `json:"rows"`
	Result QueryDBResResultDataResult `json:"result"`
}
type QueryDBResResultDataResult struct {
	SQL       string `json:"sql"`
	AILatency string `json:"ai_latency"`
}
type QueryDBReqData struct {
	Query string `json:"query"`
}

const (
	QueryDBToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IklBTlRIRVJFQUwiLCJpYXQiOjE1MTYyMzkwMjJ9.G_L9LzRyEtK43QfZjFQldN_00vJFbwbY69f3lMp28wM"
	QueryURL     = "https://moss.pingcap.net/ask"
)

func (s *QueryDBChatAPPService) queryDB(c context.Context, q string) (*QueryDBRes, error) {
	log.L(c).Info("QueryDBChatAPPService.queryDB function called.")
	reqBody := QueryDBReqData{
		Query: q,
	}
	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", QueryURL, bytes.NewReader(reqb))
	req.Header.Set("Authorization", "Bearer "+QueryDBToken)
	req.Header.Set("Content-Type", "application/json")
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		log.L(c).Error(err.Error())

		return nil, errors.WithCode(code.ErrQueryDBRequest, "error when request query moss 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.ErrQueryDBRequest, "error when request query moss api, err: \n%s", string(b))
	}

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

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

	return &res, nil
}
