package platformapi

import (
	"bytes"
	"compress/gzip"
	"errors"
	"fmt"
	"git.rosy.net.cn/jx-callback/globals"
	"io/ioutil"
	"math"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"

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

const (
	DefClientTimeout                 = 30 * time.Second
	DefMaxSleepSecondWhenExceedLimit = 62 // 超频类错误最大重试间隙（秒）
	DefMaxExceedLimitRetryCount      = 25 // 超频类错误最大重试次数
	DefMaxRecoverableRetryCount      = 1  // 可恢复类错误（一般指网络错），最大重试次数

	KeyTrackInfo = "TrackInfo"
	KeyData      = "fakeData"
)

type APIRetryConfig struct {
	MaxExceedLimitRetryCount      int
	MaxRecoverableRetryCount      int
	MaxSleepSecondWhenExceedLimit int
}

type APIConfig struct {
	APIRetryConfig
	ClientTimeout time.Duration
}

type AccessPlatformAPIWithRetryParam struct {
	APIRetryConfig
	Client  *http.Client
	Request *http.Request
}

var (
	DefAPIConfig = APIConfig{
		APIRetryConfig: APIRetryConfig{
			MaxExceedLimitRetryCount:      DefMaxExceedLimitRetryCount,
			MaxRecoverableRetryCount:      DefMaxRecoverableRetryCount,
			MaxSleepSecondWhenExceedLimit: DefMaxSleepSecondWhenExceedLimit,
		},
		ClientTimeout: DefClientTimeout,
	}
)

const (
	ErrLevelSuccess        = "JXC4_SUCCESS"
	ErrLevelExceedLimit    = "JXC4_EXCEED_LIMIT"
	ErrLevelRecoverableErr = "JXC4_RECOVERABLE"
	ErrLevelGeneralFail    = "JXC4_GENERAL_FAIL"
	ErrLevelCodeIsNotOK    = "JXC4_CODE_IS_NOT_OK"
)

const (
	maxDataSizeDontOutput = 200 * 1024
)

// common api access error
var (
	ErrAPIAccessFailed         = errors.New("access API failed")
	ErrHTTPCodeIsNot200        = errors.New("HTTP code is not 200")
	ErrResponseDataFormatWrong = errors.New("the data of response has wrong format")
)

// common callback response
var (
	ErrStrUnescapeError            = "can not unescape data:%v, error:%v"
	ErrStrUnmarshalError           = "can not unmarshal data:%v, error:%v"
	ErrStrCallbackSignatureIsWrong = "wrong signature"
)

func getClonedData(requestURL *url.URL, data []byte) string {
	if strings.Index(requestURL.String(), "uploadImg") >= 0 {
		return "binary content"
	}
	if len(data) > maxDataSizeDontOutput {
		return "request data is too large"
	}
	return string(data)
}

func NewDefAPIConfig() (conf *APIConfig) {
	obj := DefAPIConfig
	return &obj
}

func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http.Request, config *APIConfig, handleResponse func(response *http.Response, bodyStr string, bodyMap map[string]interface{}) (string, error)) error {
	exceedLimitRetryCount := 0
	recoverableErrorRetryCount := 0
	for {
		var savedBuf []byte
		request := handleRequest()
		if request.Body != nil {
			savedBuf2, err2 := ioutil.ReadAll(request.Body)
			if err2 == nil {
				savedBuf = savedBuf2
				request.Body = ioutil.NopCloser(bytes.NewReader(savedBuf))
			} else {
				baseapi.SugarLogger.Warnf("AccessPlatformAPIWithRetry failed with err:%v", err2)
			}
		}
		beginTime := time.Now()
		trackInfo := request.Header.Get(KeyTrackInfo)
		if trackInfo != "" {
			request.Header.Del(KeyTrackInfo)
		}
		trackInfo += ", " + utils.GetUUID()
		response, err := client.Do(request)
		usedMilliSecond := time.Now().Sub(beginTime) / time.Millisecond
		if err != nil {
			err, ok := err.(net.Error)
			baseapi.SugarLogger.Infof("%d,config:%d: err %s", recoverableErrorRetryCount, config.MaxRecoverableRetryCount, err)
			recoverableErrorRetryCount++
			if ok /*&& err.Timeout()*/ && recoverableErrorRetryCount <= config.MaxRecoverableRetryCount { // 只要是网络错误都重试
				continue
			} else {
				return ErrAPIAccessFailed
			}
		}
		if usedMilliSecond > 5000 {
			baseapi.SugarLogger.Infof("AccessPlatformAPIWithRetry:%s access api too slow, url:%v, usedMilliSecond:%d", trackInfo, request.URL, usedMilliSecond)
		}
		defer response.Body.Close()
		if response.StatusCode != http.StatusOK {
			// todo, hardcode，各平台都出现过偶发的返回非200的错误，饿百最多，美团外卖也出过，奇怪
			if true /*response.StatusCode == http.StatusBadRequest && request.URL.Hostname() == "api-be.ele.me"*/ {
				recoverableErrorRetryCount++
				if recoverableErrorRetryCount <= config.MaxRecoverableRetryCount {
					continue
				}
			}

			bodyData, err := ioutil.ReadAll(response.Body)
			if err == nil {
				baseapi.SugarLogger.Debugf("AccessPlatformAPIWithRetry:%s HTTP code is:%d, url:%v, response:%s", trackInfo, response.StatusCode, request.URL, string(bodyData))
			} else {
				baseapi.SugarLogger.Debugf("AccessPlatformAPIWithRetry:%s ioutil.ReadAll failed, HTTP code is:%d, url:%v, error:%v", trackInfo, response.StatusCode, request.URL, err)
			}
			if bodyData != nil {
				return errors.New(string(bodyData))
			}
			return ErrHTTPCodeIsNot200
		}
		var (
			errLevel     string
			bodyGeneral  interface{}
			bodyMap      map[string]interface{}
			parseJSONErr error
		)

		var bodyData []byte
		var getDataErr error
		//解析返回的gzip数据信息
		if response.Header.Get("Content-Encoding") == "gzip" {
			reader, e := gzip.NewReader(response.Body)
			if e != nil {
				fmt.Printf("create gzip reader err: %s \n\n", e.Error())
				return e
			}
			bodyData, getDataErr = ioutil.ReadAll(reader)
		} else {
			bodyData, getDataErr = ioutil.ReadAll(response.Body)
		}
		globals.SugarLogger.Debugf("%s", utils.Format4Output(time.Now(), false))
		if getDataErr != nil {
			baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s ioutil.ReadAll failed, url:%v, error:%v", trackInfo, request.URL, err)
			errLevel = ErrLevelRecoverableErr // 读取数据错误，或数据格式错误认为是偶发情况，重试
		} else {
			if err = utils.TryUnmarshalUseNumber(bodyData, &bodyGeneral); err != nil {
				parseJSONErr = err
				err = nil // 尝试忽略解析成json错
			}
			// 临时处理返回值居然不是map的情况
			if bodyMap2, ok := bodyGeneral.(map[string]interface{}); ok {
				bodyMap = bodyMap2
			} else {
				if bodyGeneral == nil {
					bodyGeneral = string(bodyData)
				}
				bodyMap = map[string]interface{}{
					KeyData: bodyGeneral,
				}

			}
			errLevel, err = handleResponse(response, string(bodyData), bodyMap)
			if err != nil && errLevel == ErrLevelRecoverableErr && parseJSONErr != nil {
				const maxOutputLen = 2000
				bodyDataLen := len(bodyData)
				bodyData2 := bodyData
				if bodyDataLen > maxOutputLen {
					bodyData2 = bodyData2[:maxOutputLen]
				}
			}
		}

		if err == nil {
			return nil
		} else if errLevel == ErrLevelExceedLimit {
			exceedLimitRetryCount++
			if exceedLimitRetryCount <= config.MaxExceedLimitRetryCount {
				sleepSeconds := int(math.Exp2(float64(exceedLimitRetryCount)))
				if sleepSeconds > config.MaxSleepSecondWhenExceedLimit {
					sleepSeconds = config.MaxSleepSecondWhenExceedLimit
				}
				time.Sleep(time.Duration(sleepSeconds) * time.Second)
				continue
			}
		} else if errLevel == ErrLevelRecoverableErr {
			recoverableErrorRetryCount++
			if recoverableErrorRetryCount <= config.MaxRecoverableRetryCount {
				continue
			}
		}
		return err
	}
}

func RebuildError(inErr error, bzParams map[string]interface{}, watchKeys []string) (outErr error) {
	if inErr != nil {
		if codeErr, ok := inErr.(*utils.ErrorWithCode); ok {
			for _, key := range watchKeys {
				if bzParams[key] != nil {
					codeErr.AddPrefixMsg(fmt.Sprintf("[%s:%v]", key, bzParams[key]))
				}
			}
		}
	}
	return inErr
}
