package getuiV2

import (
	"crypto/sha256"
	"fmt"
	"github.com/astaxie/beego/httplib"
	"gitlab.fjmaimaimai.com/mmm-go/gocomm/pkg/log"
	"openapi/pkg/infrastructure/push"
	"openapi/pkg/infrastructure/utils"
	"strings"
	"sync"
	"time"
)

const (
	host         = "https://restapi.getui.com"
	pushSingle   = "push/single/cid"
	saveListBody = "push/list/message"
	pushList     = "push/list/cid"
	authSign     = "auth"
)

var (
	authtoken  = ""
	expire     time.Time
	authMux    sync.RWMutex
	expireSpan = time.Second * 600 //token 10分钟过期
)

const (
	error_not_auth = "not_auth"
)

//GetuiNotification 个推消息推送
type GetuiNotification struct {
	Options *push.Options
	Request *httplib.BeegoHTTPRequest
	retry   int
}

func (notify *GetuiNotification) Init(options ...push.Option) error {
	notify.Options = &push.Options{}
	for _, o := range options {
		o(notify.Options)
	}
	notify.retry = 3
	return nil
}
func (notify *GetuiNotification) Send(option map[string]interface{}) (rsp map[string]interface{}, err error) {
	retry := 1
	for {
		switch notify.Options.PushType {
		case push.PushToSingle:
			rsp, err = notify.pushToSingle(option)
		case push.PushToList:
			rsp, err = notify.pushToList(option)
		default:
			rsp, err = notify.pushToSingle(option)
		}
		if err == nil {
			break
		}
		//重试
		if err != nil && retry > notify.retry {
			return
		}
		log.Error(fmt.Sprintf("【个推】 重试:%v 失败:%v", retry, err))
		retry++
		if retry > notify.retry {
			break
		}
	}
	return
}

//pushToSingle 单推
func (notify *GetuiNotification) pushToSingle(option map[string]interface{}) (rsp map[string]interface{}, err error) {
	var token string
	rsp = make(map[string]interface{})
	if token, err = notify.GetAuthToken(); err != nil {
		return
	}

	var (
		result *MessageBase
		url    = notify.Url(notify.Options.AppId, pushSingle)
		m      = notify.Message(pushSingle)
	)
	notify.Request = httplib.Post(url)
	notify.Request.Header("token", token)
	notify.Request.JSONBody(m)
	if err = notify.Request.ToJSON(&result); err != nil {
		return
	}
	rsp = result.Data
	notify.print(url, m, result, result)
	if err = handleResult(url, result); err != nil {
		return
	}
	return
}

//pushToList 群推
//步骤1.获取token
//步骤2.save_list_body保存消息共同体
//步骤3.push_list
func (notify *GetuiNotification) pushToList(option map[string]interface{}) (rsp map[string]interface{}, err error) {
	var (
		token  string
		taskId string
	)
	rsp = make(map[string]interface{})
	if token, err = notify.GetAuthToken(); err != nil {
		return
	}
	if taskId, err = notify.saveListBody(token, option); err != nil {
		return
	}
	var (
		result *MessageBase
		url    = notify.Url(notify.Options.AppId, pushList)
		m      = NewMapData()
	)
	m.AddFiled("audience.cid", notify.Options.ClientIds)
	m.AddFiled("taskid", taskId)
	m.AddFiled("is_async", true) //是否异步发送
	notify.Request = httplib.Post(url)
	notify.Request.Header("token", token)
	notify.Request.JSONBody(m.Data)
	if err = notify.Request.ToJSON(&result); err != nil {
		return
	}
	rsp = result.Data
	notify.print(url, m, result, result)
	if err = handleResult(url, result); err != nil {
		return
	}
	return
}

//saveListBody 保存消息共同体
func (notify *GetuiNotification) saveListBody(token string, option map[string]interface{}) (taskId string, err error) {
	var (
		result *MessageBase
		url    = notify.Url(notify.Options.AppId, saveListBody)
		m      = notify.Message(saveListBody)
	)
	notify.Request = httplib.Post(url)
	notify.Request.Header("token", token)
	notify.Request.JSONBody(m)
	delete(m, "audience")
	if err = notify.Request.ToJSON(&result); err != nil {
		return
	}
	notify.print(url, m, result, result)
	if err = handleResult(url, result); err != nil {
		return
	}
	if id, ok := result.Data["taskid"]; ok {
		taskId = id.(string)
		return
	}
	return "", fmt.Errorf("error task id")
}

//Message 组装消息体
func (notify *GetuiNotification) Message(method string) map[string]interface{} {
	msg := NewPushMessage(notify.Options)
	return msg
}

//Url  组装请求地址
func (notify *GetuiNotification) Url(param string, method string) string {
	return fmt.Sprintf("%v/v2/%v/%v", host, param, method)
}

//GetAuthToken 获取token
func (notify *GetuiNotification) GetAuthToken() (token string, err error) {
	if authtoken != "" && expire.Unix() > time.Now().Unix() {
		token = authtoken
		return
	}

	authMux.Lock()
	defer authMux.Unlock()
	// recheck
	if authtoken != "" && expire.Unix() > time.Now().Unix() {
		token = authtoken
		return
	}
	url := notify.Url(notify.Options.AppId, authSign)
	notify.Request = httplib.Post(strings.TrimSpace(url))
	req := &AuthSignRequest{
		Timestamp: fmt.Sprintf("%v", time.Now().Unix()*1000), //"1589797286000",//
		AppKey:    notify.Options.AppKey,
	}
	req.Sign = sign(req.AppKey, req.Timestamp, notify.Options.AppMasterSecret)
	if _, err = notify.Request.JSONBody(req); err != nil {
		return
	}

	var rsp *AuthSignResponse
	err = notify.Request.ToJSON(&rsp)
	notify.print(url, req, rsp, rsp.MessageBase)
	if err != nil {
		return
	}
	if err = handleResult(url, rsp.MessageBase); err != nil {
		return
	}
	authtoken = rsp.Data["token"].(string)
	token = rsp.Data["token"].(string)
	expire = time.Now().Add(rsp.GetExpireTime(expireSpan))
	log.Info(fmt.Sprintf("【个推】token:%v expire:%v", token, expire))
	return
}

//打印日志 debug_module=true  print debug log
func (notify *GetuiNotification) print(url string, v interface{}, rsp interface{}, result *MessageBase) {
	if !notify.Options.DebugModule {
		return
	}
	log.Error(fmt.Sprintf("【个推】 url:%v \n request:%v \n response:%v 结果:%v", url, utils.JsonAssertString(v), utils.JsonAssertString(rsp), result.Msg))
}

//处理结果
func handleResult(url string, result *MessageBase) (err error) {
	if result.Code == 0 {
		return
	}

	switch result.Code {
	case 0:
		break
	default:
		setToken("")
		break
	}
	err = fmt.Errorf("error:%v %v", result.Code, result.Msg)
	return err
}
func sign(appKey, timestamp, masterSecret string) string {
	sha := sha256.New()
	sha.Write([]byte(appKey + timestamp + masterSecret))
	return fmt.Sprintf("%x", sha.Sum(nil))
}

func setToken(token string) {
	//authMux.Lock()
	//defer authMux.Unlock()
	authtoken = token
}