package pposRequest

import (
	"PPOSUtils/utils"
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"sync"
	"time"

	"PPOSUtils/vendorSDK/req"

	//"net/http"
	"errors"
	"net/url"
	"strings"
)

var (
	ApiLongTimeContainsMp = []string{
		"/base/account/loginMp",
	}
	ApiLongTimeMp = utils.SliceString{
		"/base/account/loginMp",
		"/field/schemeRule/add",
		"/field/schemeRule/update",
		"/field/fieldBuyFixed/update",
		"/member/memberCard/getMemberCardInfo",
		"/statistic/combine/memberConsumptionList",
		"/statistic/combine/memberConsumptionSum",
		"/device/face/comparison",
		"/device/face/issue",
		"/device/face/update",
		"/device/face/delete",
		"/device/gate/edit",
		"/member/member/uploadFace",
		"/member/member/uploadFaceDel",
		"/member/member/del",
		"/enterAdmin/gateAuthCard/add",
		"/enterAdmin/gateAuthCard/update",
		"/enterAdmin/gateAuthCard/del",
		"/shop/order/create",
		"/shop/refund/create",
		"/shop/refund/notice",
		"/shop/order/refund",
		"/shop/order/payCodeCreate",
		"/statistic/summary/summaryStatementOfRevenue",
		"/member/memberCard/oneCardExtension",
		"/member/memberCard/specialCardExtension",
		"/class/sign/timeOrder",
		"/member/statistic/oneCardSettlementIncome",
		"/member/statistic/oneCardSettlementIncomeSum",
		"/statistic/summary/oneCardSettlementIncome",
		"/statistic/summary/oneCardSettlementIncomeSum",
		"/statistic/paymentRecord/getSaleConsumeRecordList",
		"/statistic/paymentRecord/saleConsumeRecordSum",
		"/statistic/paymentRecord/getPaymentRecordList",
		"/class/sign/timeOrder",
		"/member/memberCard/extensionImport",
		"/member/memberCard/oneCardImport",
		"/member/memberCard/specialCardImport",
		"/statistic/stadiumSaleConsume/openCardStadiumClearing",
		"/statistic/stadiumSaleConsume/openCardStadiumClearingNew",
		"/statistic/summary/consolidatedFinancialStatement",
		"/goods/storageGoods/onSaleBatch",
		"/field/scheme/copy",
		"/statistic/skuPayRecord/getTicketRecordList",
		"/device/lightLine/cmdByMod",
		"/device/lightLine/queryByMod",
		"/field/fieldBuyPlan/getLockOperateRecordList",
	}
	ApiLongTimeMp2 = utils.SliceString{
		"/shop/order/pay",
		"/shop/order/allList",
		"/enterAdmin/bracelet/borrowBracelet",
		"/enterAdmin/bracelet/borrow_bracelet",
		"/enterAdmin/bracelet/borrow_bracelet_call_back",
		"/enterAdmin/bracelet/back_bracelet",
		"/enterAdmin/bracelet/back_bracelet_call_back",
		"/enterAdmin/braceletYj/borrowBracelet",
		"/enterAdmin/braceletYj/borrowBraceletCallback",
		"/enterAdmin/braceletYj/backBracelet",
		"/enterAdmin/braceletYj/backBraceletCallback",
		"/platform/lhdOrder/create",             //创建订单
		"/platform/lhdOrder/cancel",             //取消订单
		"/platform/lhdOrder/payNotice",          //订单支付通知
		"/platform/lhdOrder/refund",             //退单
		"/platform/lhdOrder/verify",             //订单核销
		"/socialSports/instructorImport/import", //社体指导员导入
	}
)

var cacheCfg = map[string]string{}
var cacheCfgLock sync.RWMutex

func getCacheCfg(key string) string {
	cacheCfgLock.RLock()
	defer cacheCfgLock.RUnlock()
	_v, _ok := cacheCfg[key]
	if !_ok {
		return ""
	}
	return _v
}

func setCacheCfg(k, v string) {
	cacheCfgLock.Lock()
	defer cacheCfgLock.Unlock()
	cacheCfg[k] = v
}

type CacheData struct {
	CTime   int64
	Expires int64
	Data    utils.Mp
}

// NewPPOSClient ...
func NewPPOSClient(appId, apiKey, apiDomain string) *PPOSClient {
	return &PPOSClient{
		appId:     appId,
		apiKey:    apiKey,
		apiDomain: apiDomain,
	}
}

// ClientProxy ...
type ClientProxy interface {
	Proxy(url string, param ClientParam, method string, v ...interface{}) (http.Header, []byte, error)
}

// ClientParam ...
type ClientParam interface {
	// 返回参数列表
	Params() (url.Values, error)
}

// PPOSClient ...
type PPOSClient struct {
	appId               string
	apiKey              string
	apiDomain           string
	header              http.Header
	apiCache            utils.ApiCacheInterface
	paramsMiddleHander  func(url string, value url.Values) (url.Values, error)
	headerMiddleHandler utils.JaegerClientHeaderHandler
}

func (that *PPOSClient) SetHeader(key, value string) *PPOSClient {
	if that.header == nil {
		that.header = make(http.Header)
	}
	that.header.Set(key, value)
	return that
}

func (that *PPOSClient) SetApiCache(apiCache utils.ApiCacheInterface) *PPOSClient {
	that.apiCache = apiCache
	return that
}

// 在这里添加耗时比较长的SDK接口
func (that *PPOSClient) longTimeInterface(url string) int {
	_defaultOutTime := 5
	if ApiLongTimeMp.In(url) {
		_defaultOutTime = 15
	}
	return that.longLongTimeInterface(url, _defaultOutTime)
}
func (that *PPOSClient) longLongTimeInterface(url string, outTime int) int {
	if ApiLongTimeMp2.In(url) {
		return 15 * 4
	}
	return that.longTimeContainsInterface(url, outTime)
}
func (that *PPOSClient) longTimeContainsInterface(url string, outTime int) int {
	for _, _v := range ApiLongTimeContainsMp {
		if strings.Contains(url, _v) {
			return 15
		}
	}
	return outTime
}

func (that *PPOSClient) GetTimeOut(url string) time.Duration {
	return time.Duration(that.longTimeInterface(url))
}

func (that *PPOSClient) SetParamsMiddleHander(hander func(url string, value url.Values) (url.Values, error)) *PPOSClient {
	that.paramsMiddleHander = hander
	return that
}

func (that *PPOSClient) SetHeaderMiddleHandler(handler utils.JaegerClientHeaderHandler) *PPOSClient {
	that.headerMiddleHandler = handler
	return that
}

// _URLValues ...
func (that *PPOSClient) _getURLValues(url string, param ClientParam) (url.Values, error) {
	_params, _err := param.Params()
	if _err != nil {
		return nil, _err
	}
	if that.paramsMiddleHander != nil {
		_params, _err = that.paramsMiddleHander(url, _params)
	}
	return that._getSign(_params), nil
}

func (that *PPOSClient) _getSign(params url.Values) url.Values {
	params.Del("ppos_sign")
	params.Set("app_id", that.appId)
	params.Set("nonce_str", that.getNonceStr())
	params.Set("ppos_sign", utils.SignMD5(that.apiKey, params))
	return params
}

func (that *PPOSClient) getCache(url string, param ClientParam) (utils.Mp, bool) {
	//_cacheEnable := false
	_url := strings.Trim(strings.ToLower(url), "/")
	_cacheCfg := getCacheCfg(_url)
	if that.apiCache != nil && _cacheCfg != "" {
		//_cacheEnable = true
		_params, _err := param.Params()
		if _err != nil {
			return nil, false
		}
		_keySlice := []string{}
		_slice := strings.Split(_cacheCfg, "｜")
		//必传参数：不能为空
		_cacheRequiredParams := strings.Split(strings.ReplaceAll(_slice[0], "，", ","), ",")
		if len(_cacheRequiredParams) == 0 {
			return nil, false
		}
		for _, _v := range _cacheRequiredParams {
			if !_params.Has(_v) {
				return nil, false
			}
			_keySlice = append(_keySlice, _v+"="+_params.Get(_v))
		}
		//选传参数
		if len(_slice) > 1 {
			_cacheParams := strings.Split(strings.ReplaceAll(_slice[1], "，", ","), ",")
			if len(_cacheParams) > 0 {
				for _, _v := range _cacheParams {
					if _params.Has(_v) {
						_keySlice = append(_keySlice, _v+"="+_params.Get(_v))
					}
				}
			}
		}
		//
		_cacheKey := utils.Md5Encode(_url + "?" + strings.Join(_keySlice, "&"))
		_data, _b := that.apiCache.Get(_cacheKey)
		if !_b {
			return nil, false
		}
		switch _data := _data.(type) {
		case *CacheData:
			if time.Now().Unix() > _data.Expires {
				return nil, false
			}
			return _data.Data, true
		default:
			return nil, false
		}
	}
	return nil, false
}

func (that *PPOSClient) SetCache(url string, param ClientParam, cacheCfg string, data utils.Mp) {
	if that.apiCache != nil && cacheCfg != "" && data.Has("code") && utils.ToInt64(data.Get("code")) == 200 {
		_url := strings.Trim(strings.ToLower(url), "/")
		_params, _err := param.Params()
		if _err != nil {
			return
		}
		_keySlice := []string{}
		//
		_slice := strings.Split(cacheCfg, "|")
		if len(_slice) > 1 {

			//必传参数：不能为空
			_cacheRequiredParams := strings.Split(strings.ReplaceAll(_slice[1], "，", ","), ",")
			if len(_cacheRequiredParams) == 0 {
				return
			}
			for _, _v := range _cacheRequiredParams {
				if !_params.Has(_v) {
					return
				}
				_keySlice = append(_keySlice, _v+"="+_params.Get(_v))
			}
			//选传参数
			if len(_slice) > 2 {
				_cacheParams := strings.Split(strings.ReplaceAll(_slice[2], "，", ","), ",")
				if len(_cacheParams) > 0 {
					for _, _v := range _cacheParams {
						if _params.Has(_v) {
							_keySlice = append(_keySlice, _v+"="+_params.Get(_v))
						}
					}
				}
			}
			//
			_cTime := time.Now().Unix()
			_expires := utils.ToInt64(_slice[0])
			//拿到响应时，数据已过期
			if _cTime > _expires {
				return
			}
			_cacheKey := utils.Md5Encode(_url + "?" + strings.Join(_keySlice, "&"))

			//异步更新缓存
			go func(cacheEngine utils.ApiCacheInterface, cacheKey string, newCacheData *CacheData) {
				defer func() {
					if _err := recover(); _err != nil {
						//捕获异常，防止异常导致父携程挂掉。。。
					}
				}()
				//更新配置
				_cfg := getCacheCfg(_url)
				_newCfg := strings.Join(_slice[1:], "|")
				if _cfg != _newCfg {
					setCacheCfg(_url, _newCfg)
				}
				//先读取缓存，查看
				_cacheData, _b := that.apiCache.Get(cacheKey)
				//缓存不存在，则写入缓存
				if !_b {
					cacheEngine.Set(cacheKey, newCacheData, time.Duration(newCacheData.Expires)*time.Second)
				}
				switch _data := _cacheData.(type) {
				case *CacheData:
					//缓存超过2秒，则更新
					if _cTime+1 > _data.CTime {
						cacheEngine.Set(cacheKey, newCacheData, time.Duration(newCacheData.Expires)*time.Second)
					}
				default:
					cacheEngine.Set(cacheKey, newCacheData, time.Duration(newCacheData.Expires)*time.Second)
				}
				fmt.Println("========== ppos sdk cache set success =========", _cacheKey)
			}(that.apiCache, _cacheKey, &CacheData{
				CTime:   _cTime,
				Expires: _expires,
				Data:    data,
			})
		}
	}
}

func (that *PPOSClient) GetMpData(url string, param ClientParam) (utils.Mp, error) {
	//获取缓存
	_cacheData, _b := that.getCache(url, param)
	if _b {
		fmt.Println("========== ppos sdk cache get success =========", url)
		return _cacheData, nil
	}
	//
	_resp, _err := that.Post(url, param)
	//fmt.Print(_resp)
	if _err != nil {
		if strings.Contains(_err.Error(), "Timeout") {
			return nil, errors.New("系统繁忙，请稍后重试！")
		}
		return nil, _err
	}
	_result := utils.NewMP()
	if _err := _resp.ToJSON(&_result); _err != nil {
		return nil, _err
	}
	//设置缓存
	//fmt.Println(_resp.Response().Header)
	that.SetCache(url, param, _resp.Response().Header.Get("ApiCache"), _result)
	//
	return _result, nil
}

// 处理响应
func (that *PPOSClient) GetData(url string, param ClientParam) (interface{}, error) {
	_result, _err := that.GetMpData(url, param)
	if _err != nil {
		return nil, _err
	}
	_slice := []string{
		"code",
		"message",
		"data",
	}
	for _, _v := range _slice {
		if _, _ok := _result[_v]; !_ok {
			return nil, errors.New("")
		}
	}
	_code := utils.ToString(_result["code"], true)
	if _code != "200" {
		return nil, errors.New(utils.ToString(_result["message"]))
	}
	return _result["data"], nil
}

// Proxy ...
func (that *PPOSClient) Proxy(urlPath string, param interface{}, method string, v ...interface{}) (http.Header, []byte, error) {
	_resp, _err := that._proxy(urlPath, param, strings.ToLower(method), v...)
	if _err != nil {
		return nil, nil, _err
	}
	_body, _err := _resp.ToBytes()
	_header := _resp.Response().Header
	return _header, _body, _err
}

func (that *PPOSClient) _proxy(urlPath string, param interface{}, method string, v ...interface{}) (*req.Resp, error) {
	switch _param := param.(type) {
	case ClientParam:
		_urlValues, _err := that._getURLValues(urlPath, _param)
		if _err != nil {
			return nil, _err
		}
		_sign := "Signature-ppos-pro/" + _urlValues.Get("ppos_sign")
		_header := make(http.Header)
		_header.Add("Authorization", _sign)
		if that.headerMiddleHandler != nil {
			that.headerMiddleHandler(urlPath, _header)
		}
		_urlValues.Del("ppos_sign")
		v = append(v, _urlValues, _header)
	case *req.BodyJson:
		_urlValues := utils.NewMP()
		_ = json.Unmarshal(_param.GetByte(), &_urlValues)
		fmt.Println("==========json sign befor:===========", _urlValues)
		_sign := "Signature-ppos-pro/" + utils.SignMD5(that.apiKey, _urlValues)
		_header := make(http.Header)
		_header.Add("Authorization", _sign)
		if that.headerMiddleHandler != nil {
			that.headerMiddleHandler(urlPath, _header)
		}
		v = append(v, _param, _header)
	}
	//
	_timeOut := that.GetTimeOut(urlPath)
	//fmt.Println("++++pposTimeOut++++", _timeOut)
	req.SetTimeout(_timeOut * time.Second)
	if method == "get" {
		return req.Get(that._buildAPI(urlPath), v...)
	}
	_rs, _err := req.Post(that._buildAPI(urlPath), v...)
	return _rs, _err
}

// Post ...
func (that *PPOSClient) Post(url string, param ClientParam, v ...interface{}) (*req.Resp, error) {
	_URLValues, _err := that._getURLValues(url, param)
	if _err != nil {
		return nil, _err
	}
	_sign := "Signature-ppos-pro/" + _URLValues.Get("ppos_sign")
	_URLValues.Del("ppos_sign")
	_header := make(http.Header)
	if that.header != nil {
		_header = that.header
	}
	_header.Add("Authorization", _sign)
	if that.headerMiddleHandler != nil {
		that.headerMiddleHandler(url, _header)
	}
	v = append(v, _URLValues, _header)
	req.SetTimeout(that.GetTimeOut(url) * time.Second)
	_reqUrl := that._buildAPI(url)
	fmt.Print("========== ppos sdk req ==========", _reqUrl, "  ")
	return req.Post(_reqUrl, v...)
}

func (that *PPOSClient) Get(url string, param ClientParam, v ...interface{}) (*req.Resp, error) {
	_URLValues, _err := that._getURLValues(url, param)
	if _err != nil {
		return nil, _err
	}
	url = that._buildAPI(url)
	fmt.Println(url, _URLValues)
	v = append(v, _URLValues)
	if that.header != nil {
		v = append(v, that.header)
	}
	req.SetTimeout(that.GetTimeOut(url) * time.Second)
	return req.Get(url, v...)
}

// BuildAPI ...
func (that *PPOSClient) _buildAPI(paths ...string) string {
	var path = that.apiDomain
	for _, p := range paths {
		//
		p = strings.TrimSpace(p)
		if strings.HasPrefix(p, "http://") {
			return p
		}
		if strings.HasPrefix(p, "https://") {
			return 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 *PPOSClient) getNonceStr() (nonceStr string) {
	chars := "abcdefghijklmnopqrstuvwxyz0123456789"
	for i := 0; i < 32; i++ {
		idx := rand.Intn(len(chars) - 1)
		nonceStr += chars[idx : idx+1]
	}
	return nonceStr
}

// 发送json请求，参数data
func (that *PPOSClient) GetJsonData(url string, param ClientParam) (interface{}, error) {
	_data, _ := param.Params()
	//参数json序列化
	_jsonData := _data.Get("data")
	_http := http.Client{
		Timeout: 60 * time.Second, //60秒超时
	}
	//加入post请求参数
	request, err := http.NewRequest("POST", that._buildAPI(url), bytes.NewBuffer([]byte(_jsonData)))
	if err != nil {
		return nil, err
	}
	//增加header选项
	request.Header.Add("Content-Type", "application/json")
	//处理返回结果
	response, err := _http.Do(request)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()
	if http.StatusOK != response.StatusCode {
		return nil, err
	}
	//ReadAll
	_dataBody, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}
	_result := utils.NewMP()
	_err := json.Unmarshal(_dataBody, &_result)
	if _err != nil {
		return nil, _err
	}
	_slice := []string{
		"code",
		"message",
		"data",
	}
	for _, _v := range _slice {
		if _, _ok := _result[_v]; !_ok {
			return nil, errors.New("")
		}
	}
	if utils.ToInt64(_result["code"]) != 200 {
		return nil, errors.New(utils.ToString(_result["message"]))
	}
	return _result["data"], nil
}
