/**
 * 核心服务接口sdk（golang版）
 *
 * @desc 啪啪运动 核心服务接口sdk
 * ---------------------------------------------------------------------
 * @author		unphp <unphp@qq.com>
 * @date		2018-12-18
 * @copyright	core-utils
 * ---------------------------------------------------------------------
 */

package cooper

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"

	"github.com/imroc/req"
)

var Version = "20191122.01"

//PAPAServerParam ...
type papaClientParam interface {
	// 返回参数列表
	Params() url.Values
}

//PapaClientRequest ...
type PapaClientRequest interface {
	Post(result interface{}) (int, error)
}

//NewCoreClient ...
func NewCoreClient(appId, apiKey, apiDomain string) (client *CoreClient) {
	client = &CoreClient{}
	client.appId = appId
	client.apiKey = apiKey
	client.apiDomain = apiDomain
	return client
}

//PapaResponse ...
type PapaResponse struct {
	*req.Resp
}

//CoreClient ...
type CoreClient struct {
	appId     string
	apiKey    string
	apiDomain string
	Client    *http.Client
	Request   BaiduRequestInterface
}

//NewRequest ...
func (that *CoreClient) NewRequest(request BaiduRequestInterface) *CoreClient {
	that.Request = request
	that.Request.SetClient(that)
	return that
}

//Do ...
func (that *CoreClient) Do(result interface{}) (int, error) {
	return that.Request.Post(result)
}

//_signMD5 签名算法（md5加密）
func (that *CoreClient) _signMD5(param url.Values) (sign string) {
	var pList = make([]string, 0, 0)
	for key := range param {
		var value = param.Get(key)
		if len(value) > 0 {
			pList = append(pList, key+"="+value)
		}
	}
	sort.Strings(pList)
	if that.apiKey != "" {
		pList = append(pList, "key="+that.apiKey)
	}
	var src = strings.Join(pList, "&")
	md5Ctx := md5.New()
	md5Ctx.Write([]byte(src))
	cipherStr := md5Ctx.Sum(nil)
	sign = strings.ToUpper(strings.Join(strings.Split(hex.EncodeToString(cipherStr), "")[5:20], ""))
	return sign
}

//_URLValues ...
func (that *CoreClient) _getURLValues(param papaClientParam) (value url.Values, err error) {
	var p = param.Params()
	fmt.Println("params", p)
	p.Set("app_id", that.appId)
	p.Set("nonce_str", that.getNonceStr())
	var sign = that._signMD5(p)
	p.Set("sign", sign)
	return p, nil
}

//Post ...
func (that *CoreClient) Post(url string, param papaClientParam) (*PapaResponse, error) {
	_URLValues, err := that._getURLValues(param)
	if err != nil {
		return nil, err
	}
	header := req.Header{
		"Accept":       "application/json",
		"Content-Type": "application/x-www-form-urlencoded",
	}
	resp, err := req.Post(that._buildAPI(url), _URLValues, header)
	if err != nil {
		return nil, err
	}
	return &PapaResponse{
		Resp: resp,
	}, nil
	//return t.doRequest(method, url, param)
}

//Get ...
func (that *CoreClient) Get(url string, param papaClientParam) (*PapaResponse, error) {
	_URLValues, err := that._getURLValues(param)
	if err != nil {
		return nil, err
	}
	header := req.Header{
		"Content-Type": "application/x-www-form-urlencoded",
	}
	resp, err := req.Get(that._buildAPI(url), _URLValues, header)
	if err != nil {
		return nil, err
	}
	return &PapaResponse{
		Resp: resp,
	}, nil
	//return t.doRequest(method, url, param)
}

//BuildAPI ...
func (that *CoreClient) _buildAPI(paths ...string) string {
	var path = that.apiDomain
	for _, p := range paths {
		p = strings.TrimSpace(p)
		if len(p) > 0 {
			if strings.HasSuffix(path, "/") {
				path = path + p
			} else {
				if strings.HasPrefix(p, "/") {
					path = path + p
				} else {
					path = path + "/" + p
				}
			}
		}
	}
	return path
}

func (that *CoreClient) getNonceStr() (nonceStr string) {
	chars := "abcdefghijklmnopqrstuvwxyz0123456789"
	for i := 0; i < 32; i++ {
		idx := rand.Intn(len(chars) - 1)
		nonceStr += chars[idx : idx+1]
	}
	return nonceStr
}

////////////////////////////////////////////////////////////////////////////////////////

//ResponseMap ...
type ResponseMap map[string]interface{}

//Has ...
func (that ResponseMap) Has(key string) bool {
	if _, _ok := that[key]; _ok {
		return true
	}
	return false
}

//Get ...
func (that ResponseMap) Get(key string) interface{} {
	return that[key]
}

func (that ResponseMap) String(key string) (string, error) {
	if that.Has(key) {
		switch n := that.Get(key).(type) {
		case int64:
			return strconv.FormatInt(n, 10), nil
		case string:
			return n, nil
		case int:
			return strconv.Itoa(n), nil
		case float32:
			return strconv.Itoa(int(n)), nil
		case float64:
			return strconv.Itoa(int(n)), nil
		case []byte:
			return string(n), nil
		case bool:
			if n {
				return "1", nil
			}
			return "0", nil
		}
		return "", errors.New("the type of data in map , can not to string")
	}
	return "0", errors.New("map not have this key")
}

//DefaultString ...
func (that ResponseMap) DefaultString(key string, defaultValue string) string {
	if that.Has(key) {
		switch n := that.Get(key).(type) {
		case int64:
			return strconv.FormatInt(n, 10)
		case string:
			return n
		case int:
			return strconv.Itoa(n)
		case float32:
			return strconv.Itoa(int(n))
		case float64:
			return strconv.Itoa(int(n))
		case []byte:
			return string(n)
		case bool:
			if n {
				return "1"
			}
			return "0"
		}
		return "0"
	}
	return defaultValue
}

////////////////////////////////////////////////////////////////////////////////////////

//JSONMap ...
type JSONMap url.Values

//UnmarshalJSON ...
func (m JSONMap) UnmarshalJSON(b []byte) error {
	var stuff map[string]interface{}
	err := json.Unmarshal(b, &stuff)
	if err != nil {
		return err
	}
	for key, value := range stuff {
		m[key] = []string{value.([]interface{})[0].(string)}
	}
	return nil
}

//Get ...
func (m JSONMap) Get(key string) string {
	if m == nil {
		return ""
	}
	vs := m[key]
	if len(vs) == 0 {
		return ""
	}
	return vs[0]
}

//Set ...
func (m JSONMap) Set(key, value string) {
	m[key] = []string{value}
}

//Add ...
func (m JSONMap) Add(key, value string) {
	m[key] = append(m[key], value)
}

//Del ...
func (m JSONMap) Del(key string) {
	delete(m, key)
}
