package main

import (
	"fmt"
	"log"
	"math"
	"strings"
	"time"

	json "github.com/json-iterator/go"
	"github.com/mohae/deepcopy"
	"github.com/spf13/cast"

	"github.com/pingcap/crm-bot/crm"
	"github.com/pingcap/crm-bot/pkg/job"
	"github.com/pingcap/crm-bot/pkg/util"
)

const (
	CRM_OBJ_SALESORDER  = "SalesOrderObj"   // 销售订单数据
	CRM_OBJ_QUOTEOBJ    = "QuoteObj"        // 报价订单数据
	CRM_OBJ_CONTRACT    = "object_e4icI__c" // 合同数据
	CRM_OBJ_SOFTWAREUSE = "object_u5eUb__c" // 程序使用通知函业务流程
	CRM_OBJ_INVOICING   = "object_Rx92H__c" // 开票申请
	CRM_OBJ_BID         = "object_h2EnS__c" // 投标
)

var (
	CRMNotificationConfMap map[string]CRMNotificationConf
	CRMRemaindConfMap      map[string]*CRM_Remaind_Conf
	CRMRemaindConfVec      []CRM_Remaind_Conf
)

func init() {
	CRMNotificationConfMap = make(map[string]CRMNotificationConf)
	CRMNotificationConfMap[CRM_OBJ_INVOICING] = CRMNotificationConf{
		"开票申请",
		[]CRMNotificationTag{
			{Key: "name", Name: "开票申请编号"},
			{Key: "field_8b1LC__c__r", Name: "客户名称"},
			{Key: "owner__r.name", Name: "负责人"},
			{Key: "field_3fQkO__c", Name: "开票总金额"},
		},
	}

	CRMNotificationConfMap[CRM_OBJ_SOFTWAREUSE] = CRMNotificationConf{
		"程序使用通知函",
		[]CRMNotificationTag{
			{Key: "name", Name: "程序使用编码"},
			{Key: "field_EgBmX__c", Name: "客户名称"},
			{Key: "owner__r.name", Name: "负责人"},
		},
	}
	CRMNotificationConfMap[CRM_OBJ_QUOTEOBJ] = CRMNotificationConf{
		"报价单",
		[]CRMNotificationTag{
			{Key: "name", Name: "报价单编码"},
			{Key: "account_id__r", Name: "客户名称"},
			{Key: "owner__r.name", Name: "负责人"},
			{Key: "field_kWmw2__c", Name: "报价金额"},
		},
	}
	CRMNotificationConfMap[CRM_OBJ_SALESORDER] = CRMNotificationConf{
		"销售订单",
		[]CRMNotificationTag{
			{Key: "name", Name: "销售订单编码"},
			{Key: "account_id__r", Name: "客户名称"},
			{Key: "owner__r.name", Name: "负责人"},
			{Key: "order_amount", Name: "销售订单金额"},
		},
	}
	CRMNotificationConfMap[CRM_OBJ_CONTRACT] = CRMNotificationConf{
		"协议类合同", []CRMNotificationTag{
			{Key: "name", Name: "协议名称"},
			{Key: "field_ym4z1__c__r", Name: "客户名称"},
			{Key: "field_amqXt__c__r", Name: "合作伙伴名称"},
			{Key: "owner__r.name", Name: "负责人"},
		},
	}
	CRMNotificationConfMap[CRM_OBJ_BID] = CRMNotificationConf{
		"投标项目授权",
		[]CRMNotificationTag{
			{Key: "field_9ZDm8__c", Name: "招标项目名称"},
			{Key: "field_kO9oR__c__r", Name: "客户名称"},
			{Key: "owner__r.name", Name: "负责人"},
		},
	}

	CRMRemaindConfMap = make(map[string]*CRM_Remaind_Conf, len(CRMRemaindConfVec))
}

type CRMNotificationTag struct {
	Key   string
	Name  string
	Value string
}

type CRMNotificationConf struct {
	Title string
	Tags  []CRMNotificationTag
}

type CRM_User struct {
	Email      string `gorm:"primaryKey"`
	LarkOpenId string
	CRMUserId  string
	Name       string
	Todos      map[string]*CRM_Todo `gorm:"-"`
	AppInst    *App                 `gorm:"-"`
}

func (user *CRM_User) Run() {
	//log.Printf("User %s Run Crm Query\n", user.Name)
	user.AppInst.Lock.RLock()
	acToken := user.AppInst.AccessToken
	user.AppInst.Lock.RUnlock()

	req := crm.CrmRequest{CorpAccessToken: acToken,
		CorpID: "FSCID_BBBDA0FD0A83028CD08938922C5D16DA", CurrentOpenUserID: user.CRMUserId}
	req.Data.BizType = 452
	req.Data.PageSize = 500
	resp, err := crm.QueryTodoList(&req, crm.Todo_URL)
	if err != nil {
		log.Println("[error] QueryTodoList err ", err, user.Email)
		return
	}

	todolist := []CRM_Todo{}
forbreak:
	for _, todoview := range resp.Data.TodoViewList {
		switch todoview.ObjectAPIName {
		case CRM_OBJ_CONTRACT, CRM_OBJ_QUOTEOBJ, CRM_OBJ_SALESORDER, CRM_OBJ_BID, CRM_OBJ_INVOICING, CRM_OBJ_SOFTWAREUSE:
			if _, ok := user.Todos[todoview.TodoID]; ok {
				break forbreak
			}

			req := &crm.CrmRequest{CorpID: "FSCID_BBBDA0FD0A83028CD08938922C5D16DA",
				CorpAccessToken: acToken, CurrentOpenUserID: user.CRMUserId,
			}
			req.Data.DataObjectAPIName = todoview.ObjectAPIName
			req.Data.ObjectDataId = todoview.ObjectID
			// get detail
			resp, err := crm.GetCrmData(req)
			if err != nil {
				log.Println("[error] GetCrmData", err, user)
				continue
			}

			configTags := deepcopy.Copy(CRMNotificationConfMap[todoview.ObjectAPIName].Tags).([]CRMNotificationTag)
			for i := range configTags {
				tag := &configTags[i]
				if dotIdx := strings.Index(tag.Key, "."); dotIdx != -1 {
					key := tag.Key[:dotIdx]
					subKey := tag.Key[dotIdx+1:]
					log.Println("debug: ", key, subKey, resp.Data[key])
					if value, ok := resp.Data[key]; ok {
						if subValue, subOk := value.(map[string]interface{})[subKey]; subOk {
							tag.Value = subValue.(string)
						}
					}
				} else {
					if value, ok := resp.Data[tag.Key]; ok {
						tag.Value = value.(string)
					}
				}
			}

			todo := CRM_Todo{
				Id:       todoview.TodoID,
				Owner:    user.Email,
				Tags:     configTags,
				Title:    CRMNotificationConfMap[todoview.ObjectAPIName].Title,
				user:     user,
				ObjName:  todoview.ObjectAPIName,
				ObjectId: todoview.ObjectID,
			}
			jBytes, _ := json.Marshal(configTags)
			todo.NotificationContent = util.ByteSliceToString(jBytes)
			todo.NotificationTime = time.Now()
			user.Todos[todo.Id] = &todo
			todo.DealRemainder()
			todolist = append(todolist, todo)

			err = user.AppInst.sendCard(&todo, user, nil)
			if err != nil {
				log.Println("[error] sendCard err ", err, user)
				continue
			}
		}
	}

	if len(todolist) > 0 {
		Save(user.AppInst.DB, todolist)
	}
}

type CRM_Remaind_Conf struct {
	Month          int
	DateBegin      int
	DateEnd        int
	SilentSecBegin int
	SilentSecEnd   int
	SlientDays     []int
	IntervalHour   float64
}

type CRM_Todo struct {
	Id                  string `gorm:"primaryKey"`
	Owner               string `gorm:"primaryKey"`
	Title               string
	Applicant           string
	Code                string
	NotificationTime    time.Time
	NotificationContent string
	RemindTime          time.Time
	Dealed              bool
	ObjName             string
	ObjectId            string
	Tags                []CRMNotificationTag `gorm:"-"`
	conf                *CRM_Remaind_Conf    `gorm:"-"`
	user                *CRM_User            `gorm:"-"`
}

func needRemaind(conf *CRM_Remaind_Conf) bool {
	now := time.Now()
	if _, ok := CRMRemaindConfMap[now.Format("2006-01-02")[5:10]]; !ok {
		log.Println("needRemaind date not exist ", now)
		return false
	}

	// check slient
	zeroTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
	dailySeconds := int(now.Sub(zeroTime).Seconds())

	if dailySeconds > conf.SilentSecBegin ||
		dailySeconds < conf.SilentSecEnd {
		log.Println("needRemaind silient second ", dailySeconds)
		return false
	}

	weekDay := now.Weekday()
	if weekDay == time.Sunday {
		weekDay = 7
	}

	for _, slientDay := range conf.SlientDays {
		if int(weekDay) == slientDay {
			log.Println("needRemaind silient day ", weekDay)
			return false
		}
	}

	return true
}

func (todo *CRM_Todo) GetConf(t time.Time) bool {
	// get config
	key := fmt.Sprintf("%02d-%02d", t.Month(), t.Day())
	if conf, ok := CRMRemaindConfMap[key]; !ok {
		log.Println("GetConf !ok ", key, t)
		return false
	} else {
		todo.conf = conf
	}

	return true
}

func (todo *CRM_Todo) DealRemainder() {
	if todo.Dealed {
		return
	}
	// get config
	if !todo.GetConf(todo.NotificationTime) {
		if todo.RemindTime.IsZero() {
			todo.RemindTime = todo.NotificationTime
		}
		return
	}
	// add next cron
	if !todo.RemindTime.IsZero() {
		if time.Now().After(todo.RemindTime) {
			todo.RemindTime = time.Now().Add(time.Duration(60*todo.conf.IntervalHour) * time.Minute)
			todo.Run()
			return
		}
	} else {
		if time.Now().Sub(todo.NotificationTime) > time.Duration(60*todo.conf.IntervalHour)*time.Minute {
			todo.RemindTime = time.Now().Add(time.Duration(60*todo.conf.IntervalHour) * time.Minute)
			todo.Run()
			return
		} else {
			todo.RemindTime = todo.NotificationTime.Add(time.Duration(60*todo.conf.IntervalHour) * time.Minute)
		}
	}

	_, err := job.AddJob(fmt.Sprintf("%d %d %d %d ?",
		todo.RemindTime.Minute(), todo.RemindTime.Hour(), todo.RemindTime.Day(), todo.RemindTime.Month()), todo)
	if err != nil {
		log.Println("[error] AddJob ", err, *todo)
	}
}

func (todo *CRM_Todo) Run() {
	log.Println("CRM_Todo Run", todo.user.Email, todo.RemindTime, todo.conf)
	var tmpTodo CRM_Todo
	if !todo.GetConf(time.Now()) {
		log.Println("conf = nil need not remaind", todo.Id)
		return
	}
	todo.user.AppInst.Lock.RLock()
	acToken := todo.user.AppInst.AccessToken
	todo.user.AppInst.Lock.RUnlock()

	req := crm.CrmRequest{CorpAccessToken: acToken,
		CorpID: "FSCID_BBBDA0FD0A83028CD08938922C5D16DA", CurrentOpenUserID: todo.user.CRMUserId}
	req.Data.BizType = 452
	req.Data.PageSize = 100
	retry := 0
doRetry:
	resp, err := crm.QueryTodoList(&req, crm.Finish_Todo_URL)
	if err != nil {
		log.Println("[error] QueryTodoList finish_todo", err)
		retry++
		todo.user.AppInst.Lock.Lock()
		todo.user.AppInst.AccessToken = crm.GetAccessToken()
		acToken = todo.user.AppInst.AccessToken
		req.CorpAccessToken = acToken
		todo.user.AppInst.Lock.Unlock()

		if retry > 3 {
			log.Println("[error] try failed goto next")
			goto Next
		} else {
			goto doRetry
		}
	}

	for _, d := range resp.Data.TodoDealViewList {
		if d.TodoID == todo.Id {
			todo.Dealed = true
			break
		}
	}

	if todo.user.AppInst.DB.Raw("select * from crm_todos where owner = ? and object_id = ? and dealed = ? limit 1",
		todo.Owner, todo.ObjectId, 1).Scan(&tmpTodo).RowsAffected >= 1 {
		todo.Dealed = true
	}

	if todo.user.AppInst.DB.Raw("select * from crm_todos where object_id = ? and dealed = ? limit 1",
		todo.ObjectId, 1).Scan(&tmpTodo).RowsAffected >= 1 {
		todo.Dealed = true
	}

	if !todo.Dealed {
		getReq := crm.CrmRequest{CorpAccessToken: acToken,
			CorpID: "FSCID_BBBDA0FD0A83028CD08938922C5D16DA", CurrentOpenUserID: todo.user.CRMUserId}
		getReq.Data.ObjectDataId = todo.ObjectId
		getReq.Data.DataObjectAPIName = todo.ObjName
		data, err := crm.GetCrmData(&getReq)
		if err != nil && err.Error() == "数据已作废或已删除" {
			// deleted
			todo.Dealed = true
		}
		if lifeStatus, ok := data.Data["life_status"]; ok {
			lifeStatus := lifeStatus.(string)
			// todo cjl 有撤单的情况可能还没考虑 是否可以通过 under_review来一次性判断
			if lifeStatus == "ineffective" ||
				lifeStatus == "normal" ||
				lifeStatus == "invalid" {
				todo.Dealed = true
			}
		}
	}

	if todo.Dealed {
		todo.RemindTime = time.Now()
		err = todo.user.AppInst.DB.Save(todo).Error
		if err != nil {
			log.Println("[error] AppInst.DB.Save", err)
		}
		return
	}

	// todo cjl check config update?

	if needRemaind(todo.conf) {
		// do remaind
		err := todo.user.AppInst.sendCard(todo, todo.user, func(card *CardTemplate) {
			card.Header.Template = "red"
			card.Header.Title.Content =
				fmt.Sprintf("您有一条【%s】待审批任务,已经超时%d小时未审批,请尽快处理.",
					todo.Title, int(math.Floor(time.Now().Sub(todo.NotificationTime).Hours())))
		})
		if err != nil {
			log.Println("[error] sendCard err ", err, todo.user)
		}
	}

Next:
	// add next cron
	if time.Now().After(todo.RemindTime.Add(-1 * time.Minute)) {
		todo.RemindTime = time.Now().Add(time.Duration(60*todo.conf.IntervalHour) * time.Minute)
	}

	err = todo.user.AppInst.DB.Save(todo).Error
	if err != nil {
		log.Println("[error] Save todo", err, *todo)
	}
	log.Println("crm_todo next cron", todo.Id, todo.RemindTime)
	_, err = job.AddJob(fmt.Sprintf("%d %d %d %d ?",
		todo.RemindTime.Minute(), todo.RemindTime.Hour(), todo.RemindTime.Day(), todo.RemindTime.Month()), todo)
	if err != nil {
		log.Println("[error] AddJob ", err, *todo)
	}
}

type CRM_Query_Activity struct {
}

func loadCRMRemindConf(sheetData []interface{}) {
	const headerRowNum = 1

	tmpConfVec := make([]CRM_Remaind_Conf, 0, len(sheetData))
	for i := headerRowNum; i < len(sheetData); i++ {
		columns := sheetData[i].([]interface{})
		dates := strings.Split(strings.TrimSpace(columns[1].(string)), "-")
		silentSecs := strings.Split(strings.TrimSpace(columns[2].(string)), ",")
		if len(dates) != 2 || len(silentSecs) != 2 {
			log.Println("[error] table config error")
			return
		}
		days := []int{}
		if columns[4] != nil {
			d := strings.TrimSpace(columns[4].(string))
			if d != "" {
				silentDays := strings.Split(d, ",")
				for _, day := range silentDays {
					day = strings.TrimSpace(day)
					days = append(days, cast.ToInt(day))
				}
			}
		}

		tmpConfVec = append(tmpConfVec,
			CRM_Remaind_Conf{cast.ToInt(strings.TrimSpace(columns[0].(string))),
				cast.ToInt(strings.TrimSpace(dates[0])),
				cast.ToInt(strings.TrimSpace(dates[1])),
				cast.ToInt(strings.TrimSpace(silentSecs[0])),
				cast.ToInt(strings.TrimSpace(silentSecs[1])),
				days, cast.ToFloat64(columns[3])},
		)
	}

	CRMRemaindConfVec = tmpConfVec
	//// for test
	//CRMRemaindConfVec = append(CRMRemaindConfVec,
	//	CRM_Remaind_Conf{12, 1, 14, 68400, 32400, []int{6, 7}, 3},
	//	CRM_Remaind_Conf{12, 15, 31, 68400, 32400, []int{6, 7}, 1},
	//)

	for i, conf := range CRMRemaindConfVec {
		for day := conf.DateBegin; day <= conf.DateEnd; day++ {
			CRMRemaindConfMap[fmt.Sprintf("%02d-%02d", conf.Month, day)] = &CRMRemaindConfVec[i]
		}
	}
}

func cardTemplate(todo *CRM_Todo, fn func(*CardTemplate)) string {
	card := CardTemplate{}
	card.Config.WideScreenMode = true
	card.CardLink.Url = "https://www.fxiaoke.com/XV/UI/Home#/remind/approvalByMe"
	card.Header.Template = "yellow"
	card.Header.Title.Tag = "plain_text"
	card.Header.Title.Content = fmt.Sprintf("来自纷享销客的【%s】待审批任务", todo.Title)
	for _, tag := range todo.Tags {
		elem := CardElement{Tag: "div"}
		elem.Text = &CardElementText{Tag: "lark_md", Content: fmt.Sprintf("**%s:** %s", tag.Name, tag.Value)}
		card.Elements = append(card.Elements, elem)
	}

	if fn != nil {
		fn(&card)
	}

	cardBytes, _ := json.Marshal(card)
	return util.ByteSliceToString(cardBytes)
}
