package tasks

import (
	"fmt"
	"git.rosy.net.cn/baseapi/platformapi/fnpsapi"
	beego "github.com/astaxie/beego/server/web"
	"io/ioutil"
	"net/http"
	"time"

	"git.rosy.net.cn/baseapi/platformapi"

	"git.rosy.net.cn/jx-callback/business/jxutils/eventhub/syseventhub"
	"git.rosy.net.cn/jx-callback/business/model"
	"git.rosy.net.cn/jx-callback/business/model/dao"
	"git.rosy.net.cn/jx-callback/business/model/legacymodel"
	"git.rosy.net.cn/jx-callback/globals/api"

	"git.rosy.net.cn/baseapi/platformapi/weimobapi"
	"git.rosy.net.cn/baseapi/platformapi/yilianyunapi"
	"git.rosy.net.cn/baseapi/utils"
	"git.rosy.net.cn/jx-callback/globals"

	"github.com/astaxie/beego/client/orm"
)

const (
	weixinTokenExpires    = 7200 * time.Second
	dingdingTokenExpires  = 7200 * time.Second
	weimobTokenExpires    = 7200 * time.Second
	yilianyunTokenExpires = 30 * 24 * 3600 * time.Second
	pushTokenExpires      = 7200 * time.Second
	fnTokenExpires        = 1 * time.Hour
	tiktokTokenExpires    = 14 * 24 * 3600 * time.Second

	maxRefreshGap = 5 * 60 * time.Second
	errRefreshGap = 10 * time.Second
	minRefreshGap = 1 * time.Second
)

type CallResult struct {
	Code string `json:"code"`
	Desc string `json:"desc"`
	Data string `json:"data"`
}

func RefreshConfig(configKey string, expiresTime time.Duration, configGetter func() (string, string, string), configSetter func(value, value2 string)) error {
	needRefreshGap := expiresTime * 8 / 10
	sleepGap := expiresTime / 10
	if sleepGap > maxRefreshGap {
		sleepGap = maxRefreshGap
	}

	refreshFunc := func() (sleepDuration time.Duration) {
		sleepDuration = sleepGap
		curConfig := &legacymodel.Config{
			Thirdparty: configKey,
		}
		db := orm.NewOrm()
		// 0: don't refresh, 1 update, 2 insert
		handleType := 0
		if err := db.Read(curConfig, "Thirdparty"); err != nil {
			if err != orm.ErrNoRows {
				return errRefreshGap
			}
			handleType = 2
		} else {
			configSetter(curConfig.Token, curConfig.RefreshToken)
			if curConfig.Date <= utils.Time2Str(time.Now().Add(-needRefreshGap)) {
				handleType = 1
			}
		}
		if handleType != 0 {
			if curConfig.Token, curConfig.Date, curConfig.RefreshToken = configGetter(); curConfig.Token == "" {
				if globals.IsProductEnv() {
					sleepDuration = errRefreshGap
				} else {
					globals.SugarLogger.Infof("RefreshConfig %s get empty token", configKey)
				}
				return sleepDuration
			}
			configSetter(curConfig.Token, curConfig.RefreshToken)
			if curConfig.Date == "" {
				curConfig.Date = utils.GetCurTimeStr()
			} else {
				sleepDuration = expiresTime - time.Now().Sub(utils.Str2Time(curConfig.Date))
			}
			var num int64
			var err error

			if handleType == 1 {
				num, err = db.Update(curConfig, "Token", "Date", "RefreshToken")
			} else {
				num, err = db.Insert(curConfig)
			}
			if err != nil || num == 0 {
				globals.SugarLogger.Errorf("db error:%v, num:%d, curConfig:%v", err, num, curConfig)
			}
		}
		if sleepDuration < minRefreshGap {
			sleepDuration = minRefreshGap
		} else if sleepDuration > maxRefreshGap {
			sleepDuration = maxRefreshGap
		}
		return sleepDuration
	}
	utils.CallFuncAsync(func() {
		for {
			sleepGap := refreshFunc()
			time.Sleep(sleepGap)
		}
	})
	return nil
}

func RefreshWeixinToken() (err error) {
	if api.WeixinAPI != nil {
		err = RefreshConfig("wechat", weixinTokenExpires, func() (token string, expireTimeStr string, invalidParameter string) {
			if globals.IsMainProductEnv() {
				if tokenInfo, err := api.WeixinAPI.CBRetrieveToken(); err == nil {
					token = tokenInfo.AccessToken
				} else {
					globals.SugarLogger.Errorf("RefreshWeixinToken RefreshToken failed with error:%v", err)
				}
			} else {
				if tokenInfo := getWXTokenFromRemote(api.WeixinAPI.CBGetToken()); tokenInfo != nil {
					expireTimeStr = utils.Time2Str(time.Now().Add(-weixinTokenExpires))
					token = tokenInfo.Token
				}
			}
			return token, expireTimeStr, ""
		}, func(value, v2 string) {
			syseventhub.SysEventHub.OnNewWXToken(value)
			api.WeixinAPI.CBSetToken(value)
		})
	}
	return err
}

func RefreshWeixin2Token() (err error) {
	if api.WeixinMiniAPI2 != nil {
		err = RefreshConfig("wechat2", weixinTokenExpires, func() (token string, expireTimeStr string, invalidParameter string) {
			if globals.IsMainProductEnv() {
				if tokenInfo, err := api.WeixinMiniAPI2.CBRetrieveToken(); err == nil {
					token = tokenInfo.AccessToken
				} else {
					globals.SugarLogger.Errorf("RefreshWeixin2Token RefreshToken failed with error:%v", err)
				}
			} else {
				if tokenInfo := getWX2TokenFromRemote(api.WeixinMiniAPI2.CBGetToken()); tokenInfo != nil {
					expireTimeStr = utils.Time2Str(time.Now().Add(-weixinTokenExpires))
					token = tokenInfo.Token
				}
			}
			return token, expireTimeStr, ""
		}, func(value, v2 string) {
			syseventhub.SysEventHub.OnNewWX2Token(value)
			api.WeixinMiniAPI2.CBSetToken(value)
		})
	}
	return err
}

func RefreshFnToken() (err error) {
	if api.FnAPI != nil {
		err = RefreshConfig("fn", fnTokenExpires, func() (token string, expireTimeStr string, refreshToken string) {
			//if globals.IsMainProductEnv() {
			curConfig := &legacymodel.Config{
				Thirdparty: "fn",
			}
			if err := orm.NewOrm().Read(curConfig, "Thirdparty"); err != nil {
				globals.SugarLogger.Errorf("RefreshFnToken RefreshToken failed with error:%v", err)
			}
			api.FnAPI.SetToken(curConfig.Token)
			api.FnAPI.SetRefreshToken(curConfig.RefreshToken)

			if beego.BConfig.RunMode == model.ServerTypeVegetable {
				if tokenInfo, err := api.FnAPI.GetAccessToken(); err == nil {
					token = tokenInfo.BusinessDataObj.AccessToken
					refreshToken = tokenInfo.BusinessDataObj.RefreshToken
					sql := `UPDATE config SET token = ?,refresh_token = ?  WHERE thirdparty = ?`
					dao.ExecuteSQL(dao.GetDB(), sql, []interface{}{
						token, refreshToken, "fn",
					})
					if _, err = fnpsapi.HttpToGuoYuanFN(map[string]interface{}{"token": token, "refresh_token": refreshToken}, fnpsapi.FengNiaoCallbackTypeToken, model.ServerTypeVegetable); err != nil {
						globals.SugarLogger.Debugf("菜市蜂鸟token通知到果园错误:%v", err)
					}
					if _, err = fnpsapi.HttpToGuoYuanFN(map[string]interface{}{"token": token, "refresh_token": refreshToken}, fnpsapi.FengNiaoCallbackTypeToken, model.ServerTypeFruits); err != nil {
						globals.SugarLogger.Debugf("菜市蜂鸟token通知到宠物错误:%v", err)
					}
				} else {
					globals.SugarLogger.Errorf("RefreshFnToken RefreshToken failed with error:%v", err)
				}

			}

			return token, expireTimeStr, refreshToken
		}, func(value, v2 string) {
			api.FnAPI.SetToken(value)
			api.FnAPI.SetRefreshToken(v2)
		})
	}
	return err
}

func RefreshQywxToken() (err error) {
	if api.QywxAPI != nil {
		err = RefreshConfig("qywx", weixinTokenExpires, func() (token string, expireTimeStr string, invalidParameter string) {
			if globals.IsMainProductEnv() {
				if tokenInfo, err := api.QywxAPI.GetToken(); err == nil {
					token = tokenInfo
				} else {
					globals.SugarLogger.Errorf("RefreshQywxToken RefreshToken failed with error:%v", err)
				}
			}
			return token, expireTimeStr, ""
		}, func(value, v2 string) {
			api.QywxAPI.SetToken(value)
		})
	}
	return err
}

func RefreshWeixin3Token() (err error) {
	// if api.WeixinMiniAPI3 != nil {
	// 	err = RefreshConfig("wechat3", weixinTokenExpires, func() (token string, expireTimeStr string) {
	// 		if globals.IsMainProductEnv() {
	// 			if tokenInfo, err := api.WeixinMiniAPI3.CBRetrieveToken(); err == nil {
	// 				token = tokenInfo.AccessToken
	// 			} else {
	// 				globals.SugarLogger.Errorf("RefreshWeixin3Token RefreshToken failed with error:%v", err)
	// 			}
	// 		} else {
	// 			if tokenInfo := getWX3TokenFromRemote(api.WeixinMiniAPI3.CBGetToken()); tokenInfo != nil {
	// 				expireTimeStr = utils.Time2Str(time.Now().Add(-weixinTokenExpires))
	// 				token = tokenInfo.Token
	// 			}
	// 		}
	// 		return token, expireTimeStr
	// 	}, func(value string) {
	// 		syseventhub.SysEventHub.OnNewWX3Token(value)
	// 		api.WeixinMiniAPI3.CBSetToken(value)
	// 	})
	// }
	return err
}

func RefreshWeimobToken() (err error) {
	if api.WeimobAPI != nil {
		err = RefreshConfig("weimob", weimobTokenExpires, func() (token string, expireTimeStr string, invalidParameter string) {
			if globals.IsMainProductEnv() {
				if tokenInfo, err := api.WeimobAPI.RefreshTokenByRefreshToken(); err == nil {
					return string(utils.MustMarshal(tokenInfo)), utils.Time2Str(time.Now().Add((time.Duration(tokenInfo.ExpiresIn) - weimobTokenExpires/time.Second) * time.Second)), ""
				} else {
					globals.SugarLogger.Debugf("RefreshWeimobToken err:%s", err)
				}
			} else {
				curToken := ""
				if tokenInfo := api.WeimobAPI.GetToken(); tokenInfo != nil {
					curToken = string(utils.MustMarshal(tokenInfo))
				}
				if tokenInfo := getWeimobTokenFromRemote(curToken); tokenInfo != nil {
					expireTimeStr = utils.Time2Str(time.Now().Add(-weimobTokenExpires))
					token = tokenInfo.Token
				}
			}
			return token, expireTimeStr, ""
		}, func(value, v2 string) {
			var tokenInfo *weimobapi.TokenInfo
			err := utils.UnmarshalUseNumber([]byte(value), &tokenInfo)
			if err == nil {
				syseventhub.SysEventHub.OnNewWeimobToken(string(utils.MustMarshal(tokenInfo)))
				api.WeimobAPI.SetToken(tokenInfo)
			}
		})
	}
	return err
}

func RefreshDingDingToken() (err error) {
	api.DingDingAPI.RetrieveToken()
	return RefreshConfig("dingding", dingdingTokenExpires, func() (string, string, string) {
		if true { //globals.IsProductEnv() {
			if token, err := api.DingDingAPI.RetrieveToken(); err == nil {
				return token, "", ""
			} else {
				globals.SugarLogger.Errorf("RefreshDingDingToken RefreshToken failed with error:%v", err)
			}
		}
		return "", "", ""
	}, func(value, v2 string) {
		api.DingDingAPI.SetToken(value)
	})
}

func SaveWeimobToken(token *weimobapi.TokenInfo) (err error) {
	db := dao.GetDB()
	config := &legacymodel.Config{
		Thirdparty:   "weimob",
		Token:        string(utils.MustMarshal(token)),
		Date:         utils.Time2Str(time.Now().Add((time.Duration(token.ExpiresIn) - weimobTokenExpires/time.Second) * time.Second)),
		LastOperator: model.AdminName,
	}
	return dao.CreateOrUpdate(db, config)
}

func RefreshYilianyunToken() (err error) {
	return RefreshConfig("yilianyun", yilianyunTokenExpires, func() (token string, expireTimeStr string, invalidParameter string) {
		if globals.IsMainProductEnv() { // 只有京西菜市刷新易联云key
			if tokenInfo, err := api.YilianyunAPI.RetrieveToken(); err == nil {
				token = string(utils.MustMarshal(tokenInfo))
			} else {
				globals.SugarLogger.Errorf("RefreshYilianyunToken RefreshToken failed with error:%v", err)
			}
		} else {
			if tokenInfo := getYLYTokenFromRemote(api.YilianyunAPI.GetToken()); tokenInfo != nil {
				expireTimeStr = utils.Time2Str(time.Now().Add(-yilianyunTokenExpires))
				token = tokenInfo.Token
			}
		}
		return token, expireTimeStr, ""
	}, func(value, v2 string) {
		token := value
		var tokenInfo *yilianyunapi.TokenInfo
		if err := utils.TryUnmarshalUseNumber([]byte(value), &tokenInfo); err == nil {
			token = tokenInfo.AccessToken
		}
		syseventhub.SysEventHub.OnNewYLYToken(token)
		api.YilianyunAPI.SetToken(token)
	})
}

func PollingRemotEvent(remoteURL string, waitSecond int, params map[string]interface{}) (tokenInfo *syseventhub.TokenInfo) {
	if waitSecond == 0 {
		waitSecond = 5 * 60
	}
	params2 := utils.MergeMaps(params, map[string]interface{}{
		"accessKey":  globals.GetWeixinTokenKey,
		"waitSecond": waitSecond,
	})
	for {
		response, err := http.Get(utils.GenerateGetURL(remoteURL, "", params2))
		if err == nil {
			defer response.Body.Close()
			if response.StatusCode == http.StatusOK {
				data, err2 := ioutil.ReadAll(response.Body)
				if err = err2; err == nil {
					var result CallResult
					if err = utils.UnmarshalUseNumber(data, &result); err == nil {
						if result.Code == "0" {
							if result.Data != "" {
								if err = utils.UnmarshalUseNumber([]byte(result.Data), &tokenInfo); err == nil && tokenInfo != nil {
									break
								}
							}
						} else {
							err = fmt.Errorf("PollingRemotEvent %s return code is:%s", remoteURL, result.Code)
						}
					}
				}
			} else {
				err = platformapi.ErrHTTPCodeIsNot200
			}
		}
		if err != nil {
			time.Sleep(errRefreshGap)
		}
	}
	return tokenInfo
}

func getWXTokenFromRemote(oldToken string) (tokenInfo *syseventhub.TokenInfo) {
	if !globals.IsMainProductEnv() && globals.GetWeixinTokenKey != "" && globals.GetWeixinTokenURL != "" {
		tokenInfo = PollingRemotEvent(globals.GetWeixinTokenURL, 0, map[string]interface{}{
			"oldToken": oldToken,
		})
	}
	return tokenInfo
}

func getWX2TokenFromRemote(oldToken string) (tokenInfo *syseventhub.TokenInfo) {
	if !globals.IsMainProductEnv() && globals.GetWeixinTokenKey != "" && globals.GetWeixinTokenURL != "" {
		tokenInfo = PollingRemotEvent(globals.GetWeixin2TokenURL, 0, map[string]interface{}{
			"oldToken": oldToken,
		})
	}
	return tokenInfo
}

func getPushTokenFromRemote(oldToken string) (tokenInfo *syseventhub.TokenInfo) {
	if !globals.IsMainProductEnv() && globals.GetWeixinTokenKey != "" && globals.GetPushTokenURL != "" {
		tokenInfo = PollingRemotEvent(globals.GetPushTokenURL, 0, map[string]interface{}{
			"oldToken": oldToken,
		})
	}
	return tokenInfo
}

func getWX3TokenFromRemote(oldToken string) (tokenInfo *syseventhub.TokenInfo) {
	if !globals.IsMainProductEnv() && globals.GetWeixinTokenKey != "" && globals.GetWeixinTokenURL != "" {
		tokenInfo = PollingRemotEvent(globals.GetWeixinTokenURL, 0, map[string]interface{}{
			"oldToken": oldToken,
		})
	}
	return tokenInfo
}

func getYLYTokenFromRemote(oldToken string) (tokenInfo *syseventhub.TokenInfo) {
	if !globals.IsMainProductEnv() && globals.GetWeixinTokenKey != "" && globals.GetYLYTokenURL != "" {
		tokenInfo = PollingRemotEvent(globals.GetYLYTokenURL, 0, map[string]interface{}{
			"oldToken": oldToken,
		})
	}
	return tokenInfo
}

func getWeimobTokenFromRemote(oldToken string) (tokenInfo *syseventhub.TokenInfo) {
	if !globals.IsMainProductEnv() && globals.GetWeixinTokenKey != "" && globals.GetWeimobTokenURL != "" {
		tokenInfo = PollingRemotEvent(globals.GetWeimobTokenURL, 0, map[string]interface{}{
			"oldToken": oldToken,
		})
	}
	return tokenInfo
}

// RefreshTiktokToken 刷新抖店token
//func RefreshTiktokToken() (err error) {
//	if api.TiktokStore != nil {
//		err = RefreshConfig("tiktok", tiktokTokenExpires, func() (token string, expireTimeStr string, refreshToken string) {
//			curConfig := &legacymodel.Config{
//				Thirdparty: "tiktok",
//			}
//			if err := orm.NewOrm().Read(curConfig, "Thirdparty"); err != nil {
//				globals.SugarLogger.Errorf("RefreshTiktokToken RefreshToken failed with error:%v", err)
//			}
//			if curConfig.RefreshToken == "" {
//				if tokenInfo, err := api.TiktokStore.CreateToken(curConfig.Token); err != nil {
//					globals.SugarLogger.Errorf("tiktok store get token err:%v", err)
//				} else {
//					token = tokenInfo.AccessToken
//					refreshToken = tokenInfo.RefreshToken
//					expireTimeStr = time.Unix(tokenInfo.ExpiresIn, 0).Format("2006-01-02 15:04:05")
//				}
//			} else {
//				expireIn, _ := time.Parse("2006-01-02 15:04:05", curConfig.Date)
//				if expireIn.Unix() < time.Now().Unix() {
//					api.TiktokStore.SetToken(curConfig.Token)
//					api.TiktokStore.SetRefreshToken(curConfig.RefreshToken)
//					if tokenInfo, err := api.TiktokStore.RefreshToken(); err == nil {
//						token = tokenInfo.AccessToken
//						refreshToken = tokenInfo.RefreshToken
//						expireTimeStr = time.Unix(tokenInfo.ExpiresIn, 0).Format("2006-01-02 15:04:05")
//					} else {
//						globals.SugarLogger.Errorf("RefreshTiktokToken RefreshToken failed with error:%v", err)
//					}
//				}
//			}
//
//			sql := `UPDATE config SET token = ?,refresh_token = ?,data = ?  WHERE thirdparty = ?`
//			dao.ExecuteSQL(dao.GetDB(), sql, []interface{}{
//				token, refreshToken, expireTimeStr, "tiktok",
//			})
//			return token, expireTimeStr, refreshToken
//		}, func(value, v2 string) {
//			api.TiktokStore.SetToken(value)
//			api.TiktokStore.SetRefreshToken(v2)
//		})
//	}
//	return err
//}
