package getui

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"
	saveListBody = "save_list_body"
	pushList     = "push_list"
	authSign     = "auth_sign "
)

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 *Result
		url    = notify.Url(notify.Options.AppId, pushSingle)
		m      = notify.Message(pushSingle)
	)
	notify.Request = httplib.Post(url)
	notify.Request.Header("authtoken", token)
	notify.Request.JSONBody(m)
	if err = notify.Request.ToJSON(&result); err != nil {
		return
	}
	rsp[push.TaskID] = result.TaskId
	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 *Result
		url    = notify.Url(notify.Options.AppId, pushList)
		m      = struct {
			Cid        []string `json:"cid"`
			TaskId     string   `json:"taskid"`
			NeedDetail bool     `json:"need_detail"`
		}{
			Cid:        notify.Options.ClientIds,
			TaskId:     taskId,
			NeedDetail: true,
		}
	)
	notify.Request = httplib.Post(url)
	notify.Request.Header("authtoken", token)
	notify.Request.JSONBody(m)
	if err = notify.Request.ToJSON(&result); err != nil {
		return
	}
	rsp[push.TaskID] = result.TaskId
	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 *Result
		url    = notify.Url(notify.Options.AppId, saveListBody)
		m      = notify.Message(saveListBody)
	)
	notify.Request = httplib.Post(url)
	notify.Request.Header("authtoken", token)
	notify.Request.JSONBody(m)
	if err = notify.Request.ToJSON(&result); err != nil {
		return
	}
	notify.print(url, m, result, result)
	if err = handleResult(url, result); err != nil {
		return
	}
	taskId = result.TaskId
	return
}

//Message 组装消息体
func (notify *GetuiNotification) Message(method string) interface{} {
	var m interface{}
	switch notify.Options.MsgType {
	case push.SystemNotification:
		t := NewNotificationTemplate(notify.Options)
		if method == saveListBody {
			t.ClientId = ""
			t.RequestId = ""
		}
		m = t
		break
	case push.SystemTransmission:
		t := NewTransmissionTemplate(notify.Options)
		if method == saveListBody {
			t.ClientId = ""
			t.RequestId = ""
		}
		m = t
		break
	default:
		m = NewNotificationTemplate(notify.Options)
		break
	}
	return m
}

//Url  组装请求地址
func (notify *GetuiNotification) Url(param string, method string) string {
	return fmt.Sprintf("%v/v1/%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()
	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)
	_, err = notify.Request.JSONBody(req)
	if err != nil {
		return
	}
	var rsp *AuthSignResponse
	err = notify.Request.ToJSON(&rsp)
	notify.print(url, req, rsp, rsp.Result)
	if err != nil {
		return
	}
	if err = handleResult(url, rsp.Result); err != nil {
		return
	}
	authtoken = rsp.AuthToken
	token = rsp.AuthToken
	expire = time.Now().Add(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 *Result) {
	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.Result))
}

//处理结果
func handleResult(url string, result *Result) (err error) {
	if strings.ToLower(result.Result) == "ok" {
		return
	}
	switch result.Result {
	case error_not_auth:
		setToken("")
		break
	}
	err = fmt.Errorf("error:%v", result.Result)
	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 = ""
}