package unipushapi

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"sync"
	"time"

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

const (
	prodURL = "https://restapi.getui.com/v2"

	authAction     = "auth"
	SuccessOffLine = "successed_offline"
	SuccessONLine  = "successed_online"
)

type TokenInfo struct {
	ExpireTime string `json:"expire_time"`
	Token      string `json:"token"`
}

type API struct {
	token        string
	appID        string
	appKey       string
	appSecret    string
	masterSecret string

	client *http.Client
	config *platformapi.APIConfig
	locker sync.RWMutex
}

func New(appID, appKey, appSecret, masterSecret string, config ...*platformapi.APIConfig) *API {
	curConfig := platformapi.DefAPIConfig
	if len(config) > 0 {
		curConfig = *config[0]
	}
	return &API{
		appID:        appID,
		appKey:       appKey,
		appSecret:    appSecret,
		masterSecret: masterSecret,

		client: &http.Client{Timeout: curConfig.ClientTimeout},
		config: &curConfig,
	}
}

func (a *API) signParam(appKey string, time int64, masterSecret string) (sig string) {
	//方法一：
	//创建一个基于SHA256算法的hash.Hash接口的对象
	hash := sha256.New()
	//输入数据
	hash.Write([]byte(appKey + utils.Int64ToStr(time) + masterSecret))
	//计算哈希值
	bytes := hash.Sum(nil)
	//将字符串编码为16进制格式,返回字符串
	hashCode := hex.EncodeToString(bytes)
	//返回哈希值
	return hashCode
}

func (a *API) AccessAPI(action string, bizParams map[string]interface{}) (retVal map[string]interface{}, err error) {
	params := make(map[string]interface{})
	time := time.Now().UnixNano() / 1e6
	params["timestamp"] = utils.Int64ToStr(time)
	params["appkey"] = a.appKey
	params["sign"] = a.signParam(a.appKey, time, a.masterSecret)
	result, _ := json.MarshalIndent(params, "", " ")
	fullURL := utils.GenerateGetURL(prodURL+"/"+a.appID, action, nil)
	err = platformapi.AccessPlatformAPIWithRetry(a.client,
		func() *http.Request {
			request, _ := http.NewRequest(http.MethodPost, fullURL, strings.NewReader(string(result)))
			if action != authAction {
				request.Header.Set("token", a.CBGetToken())
			}
			request.Header.Set("Content-Type", "application/json;charset=utf-8")
			return request
		},
		a.config,
		func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) {
			if jsonResult1 == nil {
				return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil")
			}
			if err == nil {
				if jsonResult1["msg"].(string) != "success" {
					errLevel = platformapi.ErrLevelGeneralFail
					err = utils.NewErrorCode(jsonResult1["msg"].(string), utils.Int64ToStr(utils.MustInterface2Int64(jsonResult1["code"])))
				}
				retVal = jsonResult1["data"].(map[string]interface{})
			}
			return errLevel, err
		})
	return retVal, err
}

func (a *API) AccessAPI2(action string, bizParams map[string]interface{}) (retVal map[string]interface{}, err error) {
	result, _ := json.MarshalIndent(bizParams, "", " ")
	fullURL := utils.GenerateGetURL(prodURL+"/"+a.appID, action, nil)
	err = platformapi.AccessPlatformAPIWithRetry(a.client,
		func() *http.Request {
			request, _ := http.NewRequest(http.MethodPost, fullURL, strings.NewReader(string(result)))
			request.Header.Set("token", a.CBGetToken())
			request.Header.Set("Content-Type", "application/json;charset=utf-8")
			return request
		},
		a.config,
		func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) {
			if jsonResult1 == nil {
				return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil")
			}
			if err == nil {
				if jsonResult1["msg"].(string) != "success" {
					errLevel = platformapi.ErrLevelGeneralFail
					err = utils.NewErrorCode(jsonResult1["msg"].(string), utils.Int64ToStr(utils.MustInterface2Int64(jsonResult1["code"])))
				}
				retVal = jsonResult1
			}
			return errLevel, err
		})
	return retVal, err
}

func (a *API) CBSetToken(newToken string) bool {
	curToken := a.CBGetToken()
	if curToken != newToken {
		a.locker.Lock()
		defer a.locker.Unlock()
		a.token = newToken
		return true
	}
	return false
}

func (a *API) CBGetToken() string {
	a.locker.RLock()
	defer a.locker.RUnlock()
	return a.token
}

func (a *API) CBRetrieveToken() (tokenInfo *TokenInfo, err error) {
	result, err := a.AccessAPI("auth", nil)
	if err != nil {
		return nil, err
	}
	tokenInfo = &TokenInfo{
		Token:      utils.Interface2String(result["token"]),
		ExpireTime: utils.Interface2String(result["expire_time"]),
	}
	a.CBSetToken(tokenInfo.Token)
	return tokenInfo, nil
}

type PushMsg struct {
	Notificatio Notification `json:"notification"`
}

type Notification struct {
	Title     string `json:"title"`
	Body      string `json:"body"`
	ClickType string `json:"click_type"`
	URL       string `json:"url"`
}

type Audience struct {
	CID []string `json:"cid"`
}

type Settings struct {
	TTL int `json:"ttl"`
}

type Transmission struct {
	Transmission string `json:"transmission"`
}

func (a *API) PushToSingle(cid string, transmission bool, notification *Notification) (status string, err error) {
	params := map[string]interface{}{
		"request_id": utils.GetUUID(),
		"audience": &Audience{
			[]string{cid},
		},
	}
	if !transmission {
		params["push_message"] = &PushMsg{
			Notificatio: Notification{
				Title:     notification.Title,
				Body:      notification.Body,
				ClickType: "startapp", //打开应用首页
			},
		}
	} else {
		params["push_message"] = &Transmission{
			Transmission: notification.Body,
		}
	}
	result2, err := a.AccessAPI2("push/single/cid", params)
	if err != nil {
		return "", err
	} else {
		for _, v := range result2["data"].(map[string]interface{}) {
			for _, vv := range v.(map[string]interface{}) {
				if vv.(string) != "" {
					status = vv.(string)
				}
			}
		}
	}
	return status, nil
}
