package mtwmapi

import (
	"bytes"
	"crypto/md5"
	"fmt"
	"git.rosy.net.cn/baseapi/utils"
	"mime/multipart"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"

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

const (
	apiURL        = "https://waimaiopen.meituan.com/api/v1"
	sandboxAPIURL = "http://openapi.b.waimai.test.sankuai.com/api/v1"
	signKey       = "sig"

	appIDc4 = "589"
	appIDsc = "5873"
)

const (
	KeyAppPoiCode  = "app_poi_code"
	KeyAppPoiCodes = "app_poi_codes"
	KeyAppFoodCode = "app_food_code"
	KeyImgName     = "img_name"
	KeyImgData     = "img_data"
	KeyOrderID     = "order_id"
	KeyAppID       = "app_id"
)

const (
	resultKeyData       = "data"
	resultKeyMsg        = "msg"
	resultKeySuccessMsg = "success_msg"
)
const (
	GeneralMaxLimit = 100 // 大多数的API的批处理最大条数
)

const (
	ErrCodeSuccess              = 0
	ErrCodeSysErr               = 700 // 系统错误，按美团外卖技术支持的说法，可当成需重试的错误
	ErrCodeParameterFormatWrong = 705 // 参数格式错误
	ErrCodeAccessLimited        = 711 // 接口调用过于频繁，触发流控，请降低调用频率

	ErrCodeNoAppFood                     = 805  // 不存在此菜品
	ErrCodeNoSuchOrder                   = 806  // 不存在此订单
	ErrCodeOpFailed                      = 808  // 操作失败（如订单在操作时，状态已变更等情况）
	ErrCodeSkuCategoryNotExist           = 1021 // 菜品分类不存在
	ErrCodeSkuCategoryExist              = 1037 // 菜品分类已存在
	ErrCodeCanNotModifyStoreDeliveryInfo = 3018 // 商家已接入美团配送，不可修改门店配送相关信息
)

type API struct {
	platformapi.APICookie

	appID       string
	secret      string
	token       string
	callbackURL string
	client      *http.Client
	config      *platformapi.APIConfig
}

func (a *API) Error() string {
	//TODO implement me
	panic("implement me")
}

var (
	canRetryCodes = map[int]int{
		ErrCodeAccessLimited: 1,
	}
	canRecoverCodes = map[int]int{
		ErrCodeSysErr: 1,
	}
)

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

func (a *API) genURL(cmd string) string {
	return apiURL + "/" + cmd
}

func (a *API) signParams(signURL string, params map[string]interface{}) string {
	keys := make([]string, 0)
	for k := range params {
		if k != signKey {
			keys = append(keys, k)
		}
	}

	sort.Strings(keys)
	finalStr := signURL
	kvPaires := make([]string, len(keys))
	for k, key := range keys {
		if params[key] != nil {
			kvPaires[k] = key + "=" + fmt.Sprintf("%v", params[key])
		}
	}
	finalStr += strings.Join(kvPaires, "&") + a.secret
	// baseapi.SugarLogger.Debug(finalStr)
	return fmt.Sprintf("%x", md5.Sum([]byte(finalStr)))
}

func (a *API) GetAppID() string {
	return a.appID
}

func (a *API) SetToken(token string) {
	a.token = token
}

func (a *API) GetToken() string {
	return a.token
}

func (a *API) AccessAPI2(cmd string, isGet bool, bizParams map[string]interface{}, resultKey, trackInfo string) (retVal interface{}, err error) {
	params := make(map[string]interface{})
	params["timestamp"] = time.Now().Unix()
	params["app_id"] = a.appID
	if a.appID == appIDsc {
		params["access_token"] = a.token
	}
	//params["access_token"] = "token_s14CNZErtLaXijy0JtBAXg"
	params = utils.MergeMaps(params, bizParams)
	imgData := params[KeyImgData]
	if imgData != nil {
		delete(params, KeyImgData)
	}
	signURL := a.genURL(cmd) + "?"
	params[signKey] = a.signParams(signURL, params)
	err = platformapi.AccessPlatformAPIWithRetry(a.client,
		func() *http.Request {
			var request *http.Request
			if isGet {
				fullURL := utils.GenerateGetURL(apiURL, cmd, params)
				// baseapi.SugarLogger.Debug(fullURL)
				request, _ = http.NewRequest(http.MethodGet, fullURL, nil)
			} else {
				fullURL := a.genURL(cmd)
				if imgData == nil {
					request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(utils.Map2URLValues(params).Encode()))
					request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
				} else {
					var b bytes.Buffer
					w := multipart.NewWriter(&b)
					if fw, err := w.CreateFormFile("file", params[KeyImgName].(string)); err != nil {
						panic(err.Error())
					} else {
						fw.Write(imgData.([]byte))
					}
					for k, v := range params {
						w.WriteField(k, url.QueryEscape(fmt.Sprint(v)))
					}
					w.Close()
					request, _ = http.NewRequest(http.MethodPost, fullURL, &b)
					request.Header.Set("Content-Type", w.FormDataContentType())
				}
				request.Header.Set("charset", "UTF-8")
			}
			if trackInfo != "" {
				request.Header.Set(platformapi.KeyTrackInfo, trackInfo)
			}
			// request.Close = true //todo 为了性能考虑还是不要关闭
			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")
			}
			// 不管有无错误，都尝试取得数据（因为有出错，但有有效数据返回的情况），比如ecommerce/order/getOrderIdByDaySeq
			if resultKey != "" {
				retVal = jsonResult1[resultKey]
			} else {
				retVal = jsonResult1
			}
			if errObj, ok := jsonResult1["error"]; ok {
				errorInfo := errObj.(map[string]interface{})
				errCode := int(utils.MustInterface2Int64(errorInfo["code"]))
				if errCode != ErrCodeSuccess {
					newErr := utils.NewErrorIntCode(errorInfo["msg"].(string), errCode)
					if canRetryCodes[errCode] == 1 {
						return platformapi.ErrLevelExceedLimit, newErr
					} else if canRecoverCodes[errCode] == 1 {
						return platformapi.ErrLevelRecoverableErr, newErr
					} else {
						return platformapi.ErrLevelCodeIsNotOK, newErr
					}
				}
			} else if errObj2, ok := jsonResult1["error_list"]; ok {
				errorInfo := errObj2.([]interface{})
				errStr := ""
				code := int64(0)
				for _, v := range errorInfo {
					errStr += v.(map[string]interface{})["msg"].(string)
					code = utils.MustInterface2Int64(v.(map[string]interface{})["code"])
				}
				newErr := utils.NewErrorIntCode(errStr, int(code))
				return platformapi.ErrLevelCodeIsNotOK, newErr
			}
			return platformapi.ErrLevelSuccess, nil
		})
	err = platformapi.RebuildError(err, bizParams, []string{
		KeyAppPoiCode,
		KeyAppPoiCodes,
		KeyAppFoodCode,
	})
	return retVal, err
}

func (a *API) AccessAPI3(cmd string, isGet bool, bizParams map[string]interface{}) (retVal interface{}, err error) {
	params := make(map[string]interface{})
	params["timestamp"] = time.Now().Unix()
	params["app_id"] = a.appID
	params = utils.MergeMaps(params, bizParams)
	signURL := a.genURL(cmd) + "?"
	params[signKey] = a.signParams(signURL, params)
	err = platformapi.AccessPlatformAPIWithRetry(a.client,
		func() *http.Request {
			var request *http.Request
			if isGet {
				fullURL := utils.GenerateGetURL(apiURL, cmd, params)
				request, _ = http.NewRequest(http.MethodGet, fullURL, nil)
			} else {
				fullURL := a.genURL(cmd)
				request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(utils.Map2URLValues(params).Encode()))
				request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
				request.Header.Set("charset", "UTF-8")
			}
			// request.Close = true //todo 为了性能考虑还是不要关闭
			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")
			}
			// 不管有无错误，都尝试取得数据（因为有出错，但有有效数据返回的情况），比如ecommerce/order/getOrderIdByDaySeq
			retVal = jsonResult1
			if errObj, ok := jsonResult1["data"]; ok {
				switch errObj.(type) {
				case string:
					errorInfo := errObj.(string)
					if errorInfo != "ok" {
						var newErr *utils.ErrorWithCode
						if cmd == "bill/list" {
							newErr = utils.NewErrorIntCode(jsonResult1["error"].(interface{}).(map[string]interface{})["msg"].(string), int(utils.MustInterface2Int64(jsonResult1["error"].(interface{}).(map[string]interface{})["code"])))
						} else {
							newErr = utils.NewErrorIntCode(jsonResult1["error_list"].([]interface{})[0].(map[string]interface{})["msg"].(string), int(utils.MustInterface2Int64(jsonResult1["error_list"].([]interface{})[0].(map[string]interface{})["code"])))
						}
						return errLevel, newErr
					}
				case interface{}:
				}

			}
			return platformapi.ErrLevelSuccess, nil
		})
	return retVal, err
}

func (a *API) AccessAPI(cmd string, isGet bool, bizParams map[string]interface{}) (retVal interface{}, err error) {
	return a.AccessAPI2(cmd, isGet, bizParams, resultKeyData, "")
}

//美团认证
//https://open-shangou.meituan.com/home/guide/38
func (a *API) GetOAuthCode(appPoiCode string) (retVal interface{}, err error) {
	retVal, err = a.AccessAPI2("oauth/authorize", true, map[string]interface{}{
		"app_poi_code":  appPoiCode,
		"response_type": "code",
	}, "code", "")
	return retVal, err
}

func (a *API) GetAccessToken(code string) (retVal interface{}, err error) {
	retVal, err = a.AccessAPI2("oauth/token", false, map[string]interface{}{
		"grant_type": "authorization_code",
		"code":       code,
	}, "access_token", "")
	return retVal, err
}

type TokenResult struct {
	Status       int         `json:"status"`
	State        interface{} `json:"state"`
	Message      string      `json:"message"`
	AccessToken  string      `json:"access_token"`
	ExpiresIn    int         `json:"expires_in"`
	RefreshToken string      `json:"refresh_token"`
	ReExpiresIn  int         `json:"re_expires_in"`
}

func (a *API) GetAccessToken2(appPoiCode string) (result *TokenResult, err error) {
	retVal, err := a.AccessAPI2("oauth/authorize", true, map[string]interface{}{
		"response_type": "token",
		"app_poi_code":  appPoiCode,
	}, "", "")
	if err == nil {
		utils.Map2StructByJson(retVal, &result, false)
	}
	return result, err
}

func (a *API) RefreshAccessToken(token string) (result *TokenResult, err error) {
	retVal, err := a.AccessAPI2("oauth/token", false, map[string]interface{}{
		"grant_type":    "refresh_token",
		"refresh_token": token,
	}, "", "")
	if err == nil {
		utils.Map2StructByJson(retVal, &result, false)
	}
	return result, err
}
