package feishubot

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/larksuite/oapi-sdk-go/core"
	v1 "github.com/larksuite/oapi-sdk-go/service/im/v1"

	"github.com/pingcap-inc/lark-bots/v2/pkg/job"
	"github.com/pingcap-inc/lark-bots/v2/pkg/util"
)

type QuestionnaireConfig struct {
	FinishFooter string `json:"finish_footer"`
	EndingFooter string `json:"ending_footer"`
}

type Questionnaire struct {
	ID                int    `gorm:"primaryKey"`
	Name              string `gorm:"varchar(255)"`
	TargetList        string `gorm:"text"`
	BlackList         string `gorm:"text"`
	NotificationTimes string `gorm:"text"`
	CardContent       string `gorm:"text"`
	QuestionCt        int
	ConfigStr         string              `gorm:"text"`
	CronTimes         []string            `gorm:"-"`
	Card              CardTemplate        `gorm:"-"`
	AppInst           *App                `gorm:"-"`
	Config            QuestionnaireConfig `gorm:"-"`
}

func (q Questionnaire) Run() {
	log.Println("Run Questionnaire", q)
	nowformat := time.Now().Format(timeFormat)

	notificationList := map[string]struct{}{}
	targetList := strings.Split(q.TargetList, ",")
	for _, target := range targetList {
		if strings.Contains(target, "@") {
			notificationList[target] = struct{}{}
		} else {
			departs := strings.Split(target, "|")
			sql := `
				SELECT hr_employees.email from hr_employees 
					LEFT JOIN hr_employee_departments 
					on hr_employees.id = hr_employee_departments.employee_id
					WHERE hr_employee_departments.department_id in (%s) and active = 1;
				`
			// todo cjl it may be cause SQL inject, but for now sheet can only modify by myself,
			// so temporary omit it. It can be repair by replace '%s' to ? and the execute code must
			// be moved into the if-else block.
			if len(departs) >= 2 {
				departs = departs[len(departs)-2:]
				subSql := "select id from hr_departments WHERE `parent_id` = " +
					"(select id from hr_departments where `name` = '%s') AND `name` = '%s'"
				sql = fmt.Sprintf(sql, fmt.Sprintf(subSql, departs[0], departs[1]))
			} else {
				subSql := "select id from hr_departments WHERE `name` = '%s'"
				sql = fmt.Sprintf(sql, fmt.Sprintf(subSql, departs[0]))
			}

			emails := []string{}
			err := q.AppInst.DB.Raw(sql).Scan(&emails).Error
			if err != nil {
				log.Println("[error] raw query", err, sql)
			}
			for _, email := range emails {
				notificationList[email] = struct{}{}
			}
		}
	}

	blackList := strings.Split(q.BlackList, ",")
	for _, black := range blackList {
		if _, ok := notificationList[black]; ok {
			delete(notificationList, black)
		}
	}

	if q.Name == "github" {
		uncomfirmEmails := []string{}
		err := q.AppInst.DB.Raw("select email from hr_employees where comfirm = 0 and active = 1").Scan(&uncomfirmEmails).Error
		if err != nil {
			log.Println("[error] query uncomfirm email failed ", err)
			return
		}
		tmpMap := make(map[string]struct{})
		for _, email := range uncomfirmEmails {
			tmpMap[email] = struct{}{}
		}

		for targetEmail := range notificationList {
			if _, ok := tmpMap[targetEmail]; !ok {
				continue
			}

			openId := q.AppInst.Lark.GetOpenIdByEmail(targetEmail)
			if openId == "" {
				log.Println("[error] GetOpenIdByEmail", targetEmail)
				continue
			}
			err := SendGithubBindingMessage(q.AppInst.Lark, targetEmail, openId)
			if err != nil {
				log.Println("[error] SendGithubBindingMessage ", targetEmail, openId)
			}
			log.Println("Send Notification email to ", targetEmail)
		}
	} else {
		// todo cjl maybe wrong when send many message on one day
		tmpDate := nowformat[:10]
		statisticCron := ""
		for _, cron := range q.CronTimes {
			if tmpDate > cron[:10] {
				statisticCron = cron
			} else {
				break
			}
		}

		if statisticCron == "" {
			statisticCron = time.Now().AddDate(0, 0, -1).Format(timeFormat)
		}
		statisticDate, _ := time.Parse(timeFormat, statisticCron)

		replaceData := struct {
			Yesterday string `key:"yesterday"`
		}{fmt.Sprintf("%d月%d日", statisticDate.Month(), statisticDate.Day())}
		newCard := q.Card.Copy()
		newCard.Replace(replaceData)
		cardContent, _ := json.Marshal(newCard)

		qMessages := []QuestionnaireMessage{}
		for target := range notificationList {
			openId := q.AppInst.Lark.GetOpenIdByEmail(target)
			if openId == "" {
				log.Println("[error] GetOpenIdByEmail", target)
				continue
			}

			sender := q.AppInst.Lark.LarkMessageService.Messages.Create(core.WrapContext(context.Background()),
				&v1.MessageCreateReqBody{ReceiveId: openId,
					Content: util.ByteSliceToString(cardContent),
					MsgType: "interactive",
				})
			sender.SetReceiveIdType("open_id")
			message, err := sender.Do()
			if err != nil {
				log.Println("Send Card err ", err, target)
			} else {
				log.Println("Send Card Message ", message)
			}

			qMessage := QuestionnaireMessage{OpenMessageID: message.MessageId,
				Email:           target,
				SendTime:        time.Now(),
				QuestionnaireId: q.ID,
				StatisticTime:   statisticCron,
			}

			q.AppInst.Lock.Lock()
			q.AppInst.Messages[qMessage.OpenMessageID] = qMessage
			q.AppInst.Lock.Unlock()

			qMessages = append(qMessages, qMessage)
		}

		_ = q.AppInst.DB.Save(qMessages).Error
	}

	// add next cron job
	for _, cronTime := range q.CronTimes {
		// not cron format 2006-01-02 15:04:05
		if len(nowformat) != len(cronTime) {
			break
		}

		if nowformat >= cronTime {
			continue
		}

		jobTime, _ := time.Parse(timeFormat, cronTime)
		log.Println("New Job ", jobTime.String(), cronTime)
		job.AddJob(fmt.Sprintf("%d %d %d %d ?",
			jobTime.Minute(), jobTime.Hour(), jobTime.Day(), jobTime.Month()), q)
		break
	}
}

func (q Questionnaire) Schd() {
	if len(q.CronTimes) == 1 && strings.HasPrefix(q.CronTimes[0], "daily-") {
		// cron like daily-18:00:00
		job.AddJob(fmt.Sprintf("%s %s * * ?",
			q.CronTimes[0][9:11], q.CronTimes[0][6:8]), q)
	} else {
		nowformat := time.Now().Format(timeFormat)
		for _, cronTime := range q.CronTimes {
			if nowformat >= cronTime {
				continue
			}

			jobTime, _ := time.Parse(timeFormat, cronTime)
			job.AddJob(fmt.Sprintf("%d %d %d %d ?",
				jobTime.Minute(), jobTime.Hour(), jobTime.Day(), jobTime.Month()), q)
			break
		}
	}
}

type QuestionnaireMessage struct {
	OpenMessageID   string `gorm:"primaryKey"`
	Email           string
	SendTime        time.Time
	QuestionnaireId int
	StatisticTime   string
}

type QuestionnaireAnswer struct {
	OpenMessageID string `gorm:"primaryKey"`
	Question      string `gorm:"primaryKey"`
	Answer        string
	AnswerTime    time.Time
}
