package fnpsapi

import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"math/rand"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"sync"
	"time"

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

const (
	sigKey = "signature"

	TestURL     = "https://exam-anubis.ele.me/anubis-webapi"
	URL         = "https://open-anubis.ele.me/anubis-webapi"
	tokenAction = "get_access_token"
)

type API struct {
	accessToken string
	appID       string
	appSecret   string
	locker      sync.RWMutex
	client      *http.Client
	config      *platformapi.APIConfig
}

func (a *API) SetToken(token string) {
	a.locker.Lock()
	defer a.locker.Unlock()
	a.accessToken = token
}

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

func (a *API) signParam(params map[string]interface{}) (sig string) {
	var valueList []string
	for k, v := range params {
		if k != sigKey {
			if str := fmt.Sprint(v); str != "" {
				valueList = append(valueList, fmt.Sprintf("%s=%s", k, str))
			}
		}
	}
	sort.Sort(sort.StringSlice(valueList))
	valueList = append(valueList, fmt.Sprintf("secret_key=%s", a.appSecret))
	sig = strings.Join(valueList, "&")
	binSig := md5.Sum([]byte(url.QueryEscape(sig)))
	sig = fmt.Sprintf("%x", binSig)
	return sig
}

func (a *API) signParam2(params map[string]interface{}) (sig string) {
	sb := new(strings.Builder)
	sb.WriteString("app_id=")
	sb.WriteString(a.appID)
	sb.WriteString("&access_token=")
	sb.WriteString(a.accessToken)
	sb.WriteString("&data=")
	sb.WriteString(params["data"].(string))
	sb.WriteString("&salt=")
	sb.WriteString(utils.Int64ToStr(utils.MustInterface2Int64(params["salt"])))
	sig = sb.String()
	binSig := md5.Sum([]byte(sig))
	sig = fmt.Sprintf("%x", binSig)
	return sig
}

func (a *API) AccessAPI(action string, url string, bizParams map[string]interface{}, isPost bool) (retVal map[string]interface{}, err error) {
	params := make(map[string]interface{})
	params["salt"] = GetSalt()
	params["app_id"] = a.appID
	if action != tokenAction {
		data, _ := json.Marshal(bizParams)
		params["data"] = string(data)
		signStr := a.signParam2(params)
		params[sigKey] = signStr
	} else {
		signStr := a.signParam(params)
		params[sigKey] = signStr
	}
	data, _ := json.Marshal(params)
	fullURL := utils.GenerateGetURL(url, action, nil)
	err = platformapi.AccessPlatformAPIWithRetry(a.client,
		func() *http.Request {
			var request *http.Request
			if isPost {
				request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(string(data)))
			} else {
				request, _ = http.NewRequest(http.MethodGet, utils.GenerateGetURL(url, action, params), nil)
			}
			request.Header.Set("Content-Type", "application/json")
			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 utils.MustInterface2Int64(jsonResult1["code"]) != 200 {
					errLevel = platformapi.ErrLevelGeneralFail
					err = utils.NewErrorCode(jsonResult1["msg"].(string), utils.Int64ToStr(utils.MustInterface2Int64(jsonResult1["code"])))
				}
				retVal = jsonResult1
			}
			return errLevel, err
		})
	return retVal, err
}

func GetSalt() (salt int) {
	rand.Seed(time.Now().UnixNano())
	return rand.Intn(8999) + 1000
}

type TokenInfo struct {
	AccessToken string `json:"access_token"`
	AppID       string `json:"app_id"`
	ExpireTime  int64  `json:"expire_time"`
}

func (a *API) GetAccessToken() (tokenInfo *TokenInfo, err error) {
	result, err := a.AccessAPI(tokenAction, URL, nil, false)
	if err == nil {
		utils.Map2StructByJson(result["data"], &tokenInfo, false)
	}
	return tokenInfo, err
}
