package baidunavi

import (
	"crypto/md5"
	"fmt"
	"net/http"
	"net/url"
	"sort"
	"strings"

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

const (
	signKey   = "sn"
	resultKey = "result"
	prodURL   = "http://api.map.baidu.com"
)

const (
	StatusCodeSuccess                    = 0
	StatusCodeInternalErr                = 1   // 服务器内部错误
	StatusCodeExceedDailyQuota           = 301 // 永久配额超限，限制访问
	StatusCodeExceedQuota                = 302 // 天配额超限，限制访问
	StatusCodeExceedDailyConcurrentQuota = 401 // 当前并发量已经超过约定并发配额，限制访问
	StatusCodeExceedConcurrentQuota      = 402 // 当前并发量已经超过约定并发配额，并且服务总并发量也已经超过设定的总并发配额，限制访问
)

const (
	CoordSysWGS84 = 1 // GPS设备获取的角度坐标，WGS84坐标
	CoordSysGCJ02 = 3 // google地图、soso地图、aliyun地图、mapabc地图和amap地图所用坐标，国测局（GCJ02）坐标
	CoordSysBaiDu = 5 // 百度地图采用的经纬度坐标
)

const (
	MaxCoordsConvBatchSize = 100
)

var (
	exceedLimitCodes = map[int]int{
		StatusCodeExceedDailyQuota:           1,
		StatusCodeExceedQuota:                1,
		StatusCodeExceedDailyConcurrentQuota: 1,
		StatusCodeExceedConcurrentQuota:      1,
	}

	canRetryCodes = map[int]int{
		StatusCodeInternalErr: 1,
	}
)

type Coordinate struct {
	Lng float64 `json:"x"`
	Lat float64 `json:"y"`
}

type API struct {
	client *http.Client
	config *platformapi.APIConfig
	ak     string
	sk     string
}

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

func (a *API) signParams(apiStr string, mapData map[string]interface{}) string {
	keys := make([]string, 0)
	for k := range mapData {
		if k != signKey {
			keys = append(keys, k)
		}
	}
	sort.Strings(keys)

	strList := []string{}
	for _, k := range keys {
		strList = append(strList, k+"="+url.QueryEscape(fmt.Sprint(mapData[k])))
	}
	finalStr := "/" + apiStr + "?" + strings.Join(strList, "&") + a.sk
	finalStr = url.QueryEscape(finalStr)
	return fmt.Sprintf("%x", md5.Sum([]byte(finalStr)))
}

func genGetURL(baseURL, apiStr string, params map[string]interface{}) string {
	keys := make([]string, 0)
	for k := range params {
		if k != signKey {
			keys = append(keys, k)
		}
	}
	sort.Strings(keys)

	strList := []string{}
	for _, k := range keys {
		strList = append(strList, k+"="+url.QueryEscape(fmt.Sprint(params[k])))
	}
	strList = append(strList, signKey+"="+url.QueryEscape(fmt.Sprint(params[signKey])))
	queryString := "?" + strings.Join(strList, "&")
	if apiStr != "" {
		return baseURL + "/" + apiStr + queryString
	}
	return baseURL + queryString
}

func (a *API) AccessAPI(apiStr string, params map[string]interface{}) (retVal interface{}, err error) {
	apiStr += "/"
	params2 := utils.MergeMaps(utils.Params2Map("ak", a.ak, "output", "json"), params)
	params2[signKey] = a.signParams(apiStr, params2)

	err = platformapi.AccessPlatformAPIWithRetry(a.client,
		func() *http.Request {
			request, _ := http.NewRequest(http.MethodGet, genGetURL(prodURL, apiStr, params2), nil)
			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")
			}
			status := int(utils.MustInterface2Int64(jsonResult1["status"]))
			if status == StatusCodeSuccess {
				retVal = jsonResult1[resultKey]
				return platformapi.ErrLevelSuccess, nil
			}
			newErr := utils.NewErrorIntCode(utils.Interface2String(jsonResult1["message"]), status)
			if _, ok := exceedLimitCodes[status]; ok {
				return platformapi.ErrLevelExceedLimit, newErr
			} else if _, ok := canRetryCodes[status]; ok {
				return platformapi.ErrLevelRecoverableErr, newErr
			} else {
				return platformapi.ErrLevelCodeIsNotOK, newErr
			}
		})
	return retVal, err
}

func (a *API) BatchCoordinateConvert(coords []*Coordinate, fromCoordSys, toCoordSys int) (outCoords []*Coordinate, err error) {
	if fromCoordSys == toCoordSys {
		return coords, nil
	}
	var coordsStrList []string
	for _, v := range coords {
		coordsStrList = append(coordsStrList, fmt.Sprintf("%.6f,%.6f", v.Lng, v.Lat))
	}
	params := map[string]interface{}{
		"coords": strings.Join(coordsStrList, ";"),
		"from":   fromCoordSys,
		"to":     toCoordSys,
	}
	result, err := a.AccessAPI("geoconv/v1", params)
	if err == nil {
		err = utils.Map2StructByJson(result, &outCoords, false)
	}
	return outCoords, err
}
