package yunzmall

import (
	"context"
	"fmt"
	"github.com/gogf/gf/container/garray"
	"github.com/gogf/gf/crypto/gmd5"
	"github.com/gogf/gf/crypto/gsha1"
	"github.com/gogf/gf/encoding/gjson"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/text/gregex"
	"github.com/gogf/gf/text/gstr"
	"github.com/gogf/gf/util/gconv"
	"github.com/gogf/gf/util/grand"
	"net/url"
	"sort"
	"time"
)

type Client struct {
	AppKey      string
	AppSecret   string
	accessToken string
	Url         string
	DB          int
	Source      int
}

const Url = "https://supply.yunzmall.com/supplyapi"
const pkgName = "yunzmall"
const CacheKey = "yunzmall:token:"

func New(req *Client) *Client {
	if req.DB == 0 {
		req.DB = 10
	}
	if req.Url == "" {
		req.Url = Url
	}
	return req
}

// post 请求
func (s *Client) post(ctx context.Context, method string, params g.Map, xToken ...interface{}) (str string, err error) {
	Start := gtime.TimestampMilli()
	Request := g.Client().ContentJson()
	var AppNonce = grand.S(16)
	var AppTimestamp = gtime.TimestampStr()
	Request.SetHeader("App-Nonce-Str", grand.S(16))
	Request.SetHeader("App-Timestamp", gtime.TimestampStr())
	if len(xToken) == 0 {
		var token string
		token, err = s.AccessToken(ctx)
		if err != nil {
			return
		}
		Request.SetHeader("x-token", token)
	}
	AppSign, err := s.sign(AppNonce, AppTimestamp, params)
	if err != nil {
		return
	}
	Request.SetHeader("App-Sign", AppSign)
	resp, err := Request.
		Timeout(time.Second*5).
		Post(s.Url+method, params)
	defer func() {
		_ = resp.Close()
		paramStr := gjson.New(params).MustToJsonString()
		ctx = context.WithValue(ctx, "Method", "POST")
		ctx = context.WithValue(ctx, "URI", method)
		if err != nil {
			g.Log().Ctx(ctx).Cat(pkgName).Cat("error").Infof("参数【%v】错误【%v】响应时间【%v ms】", paramStr, err.Error(), gtime.TimestampMilli()-Start)
		} else {
			g.Log().Ctx(ctx).Cat(pkgName).Infof("参数【%v】响应【%v】响应时间【%v ms】", paramStr, str, gtime.TimestampMilli()-Start)
		}
	}()
	if err != nil {
		return
	}
	str = resp.ReadAllString()
	return
}

func (s *Client) get(ctx context.Context, method string, params g.Map, xToken ...interface{}) (str string, err error) {
	Start := gtime.TimestampMilli()
	Request := g.Client().ContentJson()
	var AppNonce = grand.S(16)
	var AppTimestamp = gtime.TimestampStr()
	Request.SetHeader("App-Nonce-Str", grand.S(16))
	Request.SetHeader("App-Timestamp", gtime.TimestampStr())
	if len(xToken) == 0 {
		var token string
		token, err = s.AccessToken(ctx)
		if err != nil {
			return
		}
		Request.SetHeader("x-token", token)
	}
	AppSign, err := s.sign(AppNonce, AppTimestamp, params)
	if err != nil {
		return
	}
	postValues := url.Values{}
	for k, v := range params {
		postValues.Add(k, gconv.String(v))
	}
	URL, _ := url.Parse(s.Url + method)
	URL.RawQuery = postValues.Encode()
	urlPath := URL.String()
	Request.SetHeader("App-Sign", AppSign)
	resp, err := Request.
		Timeout(time.Second * 5).
		Get(urlPath)
	defer func() {
		_ = resp.Close()
		paramStr := gjson.New(params).MustToJsonString()
		ctx = context.WithValue(ctx, "Method", "GET")
		ctx = context.WithValue(ctx, "URI", method)
		if err != nil {
			g.Log().Ctx(ctx).Cat(pkgName).Cat("error").Infof("参数【%v】错误【%v】响应时间【%v ms】", paramStr, err.Error(), gtime.TimestampMilli()-Start)
		} else {
			g.Log().Ctx(ctx).Cat(pkgName).Infof("参数【%v】响应【%v】响应时间【%v ms】", paramStr, str, gtime.TimestampMilli()-Start)
		}
	}()
	if err != nil {
		return
	}
	str = resp.ReadAllString()
	return
}

func (s *Client) sign(nonce string, timestamp string, param g.Map) (res string, err error) {
	var keys []string
	keys = append(keys, "App-Nonce-Str")
	keys = append(keys, "App-Timestamp")

	mewparam := gjson.New(param).Map()
	mewparam["App-Timestamp"] = timestamp
	mewparam["App-Nonce-Str"] = nonce
	for k := range mewparam {
		keys = append(keys, k)
	}

	sort.Strings(keys)

	var array = garray.New()
	for _, v := range keys {
		array.Append(fmt.Sprintf("%s=%s", v, gconv.String(mewparam[v])))
	}
	array.Append(fmt.Sprintf("%s=%s", "appKey", s.AppKey))
	array.Append(fmt.Sprintf("%s=%s", "appSecret", s.AppSecret))
	var str = array.Join("&")
	str, err = gregex.ReplaceString(`\s`, "", str)
	if err != nil {
		return
	}
	str = gsha1.Encrypt(str)
	str = gmd5.MustEncryptString(str)
	res = gstr.ToUpper(str)
	return
}

type CommonRes struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
}