package feishubot

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"reflect"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/larksuite/oapi-sdk-go/card"
	larkcardhttp "github.com/larksuite/oapi-sdk-go/card/http"
	"github.com/larksuite/oapi-sdk-go/card/model"
	"github.com/larksuite/oapi-sdk-go/core"
	larkeventhttp "github.com/larksuite/oapi-sdk-go/event/http"
	v1 "github.com/larksuite/oapi-sdk-go/service/im/v1"
	"github.com/pingcap-inc/lark-bots/v2/hr"
	"github.com/pingcap-inc/lark-bots/v2/pkg/config"
	"github.com/spf13/cast"
)

func RegisterRoute(app *App) {
	gin.SetMode(config.GetString("ginmode"))
	ginR := gin.Default()
	oauthCh := make(chan hr.GithubOAuth, 20)
	hr.RegisterRoute(ginR, oauthCh)

	cli := app.Lark
	ginR.GET("/api", func(c *gin.Context) {
		larkeventhttp.Handle(cli.LarkConf, c.Request, c.Writer)
	})

	ginR.GET("/card", func(c *gin.Context) {
		larkcardhttp.Handle(cli.LarkConf, c.Request, c.Writer)
	})

	v1.SetMessageReceiveEventHandler(cli.LarkConf, func(ctx *core.Context, event *v1.MessageReceiveEvent) error {
		log.Println(*event.Event.Message, *event.Header, event.Event.Sender.SenderId.OpenId)
		if event.Event.Message.Content == `{"text":"bind-github"}` {
			go func() {
				req := cli.LarkContactService.Users.Get(core.WrapContext(context.Background()))
				req.SetUserId(event.Event.Sender.SenderId.OpenId)
				req.SetUserIdType("open_id")
				userInfo, err := req.Do()
				if err != nil {
					return
				}
				log.Println("send bind notification", userInfo.User.Email)
				err = SendGithubBindingMessage(cli, userInfo.User.Email,
					event.Event.Sender.SenderId.OpenId)
				if err != nil {
					log.Println("[error] ", err)
				}
			}()
		} else if event.Event.Message.Content == `{"text":"mute-github-binding"}` {
			go func() {
				req := cli.LarkContactService.Users.Get(core.WrapContext(context.Background()))
				req.SetUserId(event.Event.Sender.SenderId.OpenId)
				req.SetUserIdType("open_id")
				userInfo, err := req.Do()
				if err != nil {
					return
				}
				log.Println("mute github binding", userInfo.User.Email)
				err = MuteGithubBinding(app, userInfo.User.Email, event.Event.Message.MessageId)
				if err != nil {
					log.Println("[error] ", err)
				}
			}()
		}
		return nil
	})
	// deal card action event
	card.SetHandler(cli.LarkConf, func(ctx *core.Context, card *model.Card) (interface{}, error) {
		action := card.Action
		if action.Tag != "select_static" && action.Tag != "button" {
			log.Println("card action event ", action)
			return "", nil
		}

		rewrite := false
		if action.Tag == "button" {
			rewrite = true
		}

		var answers map[string]string
		var qMessage QuestionnaireMessage
		var ok bool
		app.Lock.Lock()
		if answers, ok = app.Answers[card.OpenMessageID]; !ok {
			answers = make(map[string]string)
			app.Answers[card.OpenMessageID] = answers
		} else {
			if rewrite {
				app.Answers[card.OpenMessageID] = make(map[string]string)
			}
		}
		qMessage, ok = app.Messages[card.OpenMessageID]
		app.Lock.Unlock()

		if !ok {
			err := app.DB.Find(&qMessage, QuestionnaireMessage{OpenMessageID: card.OpenMessageID}).Error
			if err != nil {
				log.Println("[error] get QuestionnaireMessage err ", err, card.OpenMessageID)
				return nil, nil
			}
		}

		q := app.Questionnaires[qMessage.QuestionnaireId]
		if rewrite {
			newCard := q.Card.Copy()
			statisticDate, _ := time.Parse(timeFormat, qMessage.StatisticTime)
			replaceData := struct {
				Yesterday string `key:"yesterday"`
			}{fmt.Sprintf("%d月%d日", statisticDate.Month(), statisticDate.Day())}
			newCard.Replace(replaceData)
			return newCard, nil
		}

		dbChan := make(chan struct{}, 1)
		for key, val := range action.Value {
			log.Printf("Key: %s, Value: %s, Option: %s \n", key, val, action.Option)
			question := val.(string)
			answers[question] = action.Option
			qa := QuestionnaireAnswer{
				OpenMessageID: card.OpenMessageID,
				Question:      question,
				Answer:        action.Option,
				AnswerTime:    time.Now(),
			}

			go func() {
				err := app.DB.Save(qa).Error
				if err != nil {
					log.Println("[error] Save QuestionnaireAnswer err ", err, qa)
				}
				dbChan <- struct{}{}
			}()
		}

		questionCt := q.QuestionCt
		if questionCt == 0 {
			for _, elm := range q.Card.Elements {
				if elm.Extra != nil && elm.Extra.Value.WorkCategory != "" {
					questionCt++
				}
			}
		}

		if len(answers) == questionCt {
			newCard := q.Card.Copy()
			statisticDate, _ := time.Parse(timeFormat, qMessage.StatisticTime)
			replaceData := struct {
				Yesterday string `key:"yesterday"`
			}{fmt.Sprintf("%d月%d日", statisticDate.Month(), statisticDate.Day())}
			newCard.Replace(replaceData)

			// replace card elements
			for key, answer := range answers {
				var preElement *CardElement
				for idx := range newCard.Elements {
					if newCard.Elements[idx].Extra != nil && newCard.Elements[idx].Extra.Value.WorkCategory == key {
						preElement = &newCard.Elements[idx]
						replaceElement := CardElement{}
						replaceElementTitle := CardElementField{}
						replaceElementTitle.IsShort = true
						replaceElementTitle.Text.Tag = "plain_text"
						replaceElementTitle.Text.Content = preElement.Text.Content
						replaceElementValue := CardElementField{}
						replaceElementValue.IsShort = true
						replaceElementValue.Text.Tag = "plain_text"
						replaceElementValue.Text.Content = fmt.Sprintf("\t\t\t\t%s小时", answer)
						replaceElement.Fields = append(replaceElement.Fields, replaceElementTitle, replaceElementValue)
						replaceElement.Tag = preElement.Tag
						newCard.Elements[idx] = replaceElement

						break
					}
				}
			}

			<-dbChan
			res := []struct {
				OpenMessageId string
				Ct            int
			}{}
			err := app.DB.Raw(`
select questionnaire_answers.open_message_id, COUNT(*) as ct from questionnaire_answers 
LEFT JOIN questionnaire_messages 
on questionnaire_answers.open_message_id = questionnaire_messages.open_message_id
WHERE questionnaire_messages.questionnaire_id = ? AND questionnaire_messages.email = ?
GROUP BY questionnaire_answers.open_message_id`, qMessage.QuestionnaireId, qMessage.Email).Find(&res).Error
			if err != nil {
				log.Println("[error] get total openmessage err", err, qMessage)
			}

			finishedCt := 0
			for _, tmp := range res {
				if tmp.Ct == questionCt {
					finishedCt++
				}
			}

			data := struct {
				FinishDays string `key:"finish_days"`
				RemainDays string `key:"remain_days"`
			}{
				FinishDays: cast.ToString(finishedCt),
				RemainDays: cast.ToString(len(q.CronTimes) - finishedCt),
			}

			if finishedCt >= len(q.CronTimes) {
				newCard.Elements = append(newCard.Elements, AddHr(),
					AddThanks(replace(q.Config.EndingFooter, data)))
				go app.GeneratePNG(q.Name, card.OpenID, qMessage.Email, qMessage.QuestionnaireId)
			} else {
				newCard.Elements = append(newCard.Elements, AddHr(),
					AddThanks(replace(q.Config.FinishFooter, data)))
			}

			return newCard, nil
		}

		return nil, nil
	})

	srv := &http.Server{
		Addr:    config.GetString("host") + ":" + config.GetString("port"),
		Handler: ginR,
	}
	app.Server = srv

	go hr.DealOAuthCh(oauthCh, app.DB)
}

func replace(s string, obj interface{}) string {
	if strings.Index(s, "${") == -1 {
		return s
	}

	objType := reflect.TypeOf(obj)
	if objType.Kind() != reflect.Struct {
		return s
	}
	objVal := reflect.ValueOf(obj)

	for i := 0; i < objType.NumField(); i++ {
		sf := objType.Field(i)
		value := objVal.Field(i)
		sfKey := sf.Tag.Get("key")
		if sfKey == "" {
			continue
		}
		s = strings.Replace(s, fmt.Sprintf("${%s}", sfKey), value.String(), -1)
	}

	return s
}
