package autonavi

import (
	"bytes"
	"crypto/md5"
	"fmt"
	"git.rosy.net.cn/baseapi/platformapi"
	"git.rosy.net.cn/baseapi/utils"
	"net/http"
	"sort"
	"strings"
)

const (
	signKey       = "sig"
	prodURL       = "https://restapi.amap.com"
	prodURLv3     = "/v3"
	prodURLFullv3 = prodURL + prodURLv3

	batchAPIStr = "batch"
)

const (
	StatusCodeFailed  = "0"
	StatusCodeSuccess = "1"
)

const (
	InfoCode_OK                            = "10000"
	InfoCode_ACCESS_TOO_FREQUENT           = "10004"
	InfoCode_QPS_HAS_EXCEEDED_THE_LIMIT    = "10014"
	InfoCode_SERVER_IS_BUSY                = "10016"
	InfoCode_RESOURCE_UNAVAILABLE          = "10017"
	InfoCode_CQPS_HAS_EXCEEDED_THE_LIMIT   = "10019"
	InfoCode_CKQPS_HAS_EXCEEDED_THE_LIMIT  = "10020"
	InfoCode_CIQPS_HAS_EXCEEDED_THE_LIMIT  = "10021"
	InfoCode_CIKQPS_HAS_EXCEEDED_THE_LIMIT = "10022"
	InfoCode_KQPS_HAS_EXCEEDED_THE_LIMIT   = "10023"
)

const (
	CoordSysAutonavi = "autonavi"
	CoordSysGPS      = "gps"
	CoordSysMapbar   = "mapbar"
	CoordSysBaidu    = "baidu"
)

const (
	FakeDistrictPadding = 9000000
	MaxConvertCount     = 40
)

var (
	exceedLimitCodes = map[string]int{
		InfoCode_ACCESS_TOO_FREQUENT:           1,
		InfoCode_QPS_HAS_EXCEEDED_THE_LIMIT:    1,
		InfoCode_CQPS_HAS_EXCEEDED_THE_LIMIT:   1,
		InfoCode_CKQPS_HAS_EXCEEDED_THE_LIMIT:  1,
		InfoCode_CIQPS_HAS_EXCEEDED_THE_LIMIT:  1,
		InfoCode_CIKQPS_HAS_EXCEEDED_THE_LIMIT: 1,
		InfoCode_KQPS_HAS_EXCEEDED_THE_LIMIT:   1,
	}

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

const (
	DistrictLevelCountry  = 0
	DistrictLevelProvince = 1
	DistrictLevelCity     = 2
	DistrictLevelDistrict = 3
	DistrictLevelStreet   = 4
)

const (
	RoadLevelAll  = 0 // 显示所有道路
	RoadLevelMain = 1 // 过滤非主干道路，仅输出主干道路数据

	HomeOrCorpDef        = 0 // 不对召回的排序策略进行干扰。
	HomeOrCorpPreferHome = 1 // 综合大数据分析将居家相关的 POI 内容优先返回，即优化返回结果中 pois 字段的poi顺序。
	HomeOrCorpPreferCorp = 2 // 综合大数据分析将公司相关的 POI 内容优先返回，即优化返回结果中 pois 字段的poi顺序。
)

var (
	levelStr2IntMap = map[string]int{
		"country":  DistrictLevelCountry,
		"province": DistrictLevelProvince,
		"city":     DistrictLevelCity,
		"district": DistrictLevelDistrict,
		"street":   DistrictLevelStreet,
	}
)

type District struct {
	Adcode    string      `json:"adcode"` // 国家行政编码
	Lng       float64     `json:"lng"`
	Lat       float64     `json:"lat"`
	CityCode  string      `json:"citycode"` // 电话区号
	Level     int         `json:"level"`
	Name      string      `json:"name"`
	Districts []*District `json:"districts"`
}

type Coordinate struct {
	Lng float64 `json:"lng"`
	Lat float64 `json:"lat"`
}

type ResponseResult map[string]interface{}

type API struct {
	platformapi.APICookie

	client *http.Client
	config *platformapi.APIConfig
	key    string
}

func (a *API) SetKey(key string) {
	a.key = key
}

type BuildingOrNeighborInfo struct {
	Name string `json:"name"`
	Type string `json:"type"`
}

type BusinessAreaInfo struct {
	ID       string `json:"id"`
	Location string `json:"location"`
	Name     string `json:"name"`
}

type StreetNumberInfo struct {
	Direction string `json:"direction"`
	Distance  string `json:"distance"`
	Location  string `json:"location"`
	Number    string `json:"number"`
	Street    string `json:"street"`
}

type AoiInfo struct {
	Adcode   string `json:"adcode"`
	Area     string `json:"area"`
	Distance string `json:"distance"`
	ID       string `json:"id"`
	Location string `json:"location"`
	Name     string `json:"name"`
	Type     string `json:"type"`
}

type PoiInfo struct {
	Address      string `json:"address"`
	Businessarea string `json:"businessarea"`
	Direction    string `json:"direction"`
	Distance     string `json:"distance"`
	ID           string `json:"id"`
	Location     string `json:"location"`
	Name         string `json:"name"`
	Poiweight    string `json:"poiweight"`
	Tel          string `json:"tel"`
	Type         string `json:"type"`
}

type RoadinterInfo struct {
	Direction  string `json:"direction"`
	Distance   string `json:"distance"`
	FirstID    string `json:"first_id"`
	FirstName  string `json:"first_name"`
	Location   string `json:"location"`
	SecondID   string `json:"second_id"`
	SecondName string `json:"second_name"`
}

type RoadInfo struct {
	Direction string `json:"direction"`
	Distance  string `json:"distance"`
	ID        string `json:"id"`
	Location  string `json:"location"`
	Name      string `json:"name"`
}

type AddressComponentInfo struct {
	Adcode   string                  `json:"adcode"`
	Building *BuildingOrNeighborInfo `json:"building"`
	// BusinessAreas []*BusinessAreaInfo     `json:"businessAreas"`
	City         string                  `json:"city"`
	Citycode     string                  `json:"citycode"`
	Country      string                  `json:"country"`
	District     string                  `json:"district"`
	Neighborhood *BuildingOrNeighborInfo `json:"neighborhood"`
	Province     string                  `json:"province"`
	StreetNumber *StreetNumberInfo       `json:"streetNumber"`
	Towncode     string                  `json:"towncode"`
	Township     string                  `json:"township"`
}

type RegeoCodeInfo struct {
	AddressComponent *AddressComponentInfo `json:"addressComponent"`
	Aois             []*AoiInfo            `json:"aois"`
	FormattedAddress string                `json:"formatted_address"`
	Pois             []*PoiInfo            `json:"pois"`
	Roadinters       []*RoadinterInfo      `json:"roadinters"`
	Roads            []*RoadInfo           `json:"roads"`
}

type tBatchAPIParams struct {
	APIStr    string
	APIParams map[string]interface{}
}

type tBatchAPIResponse struct {
	Result ResponseResult
	Err    error
}

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

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

	finalStr := ""
	for _, k := range keys {
		finalStr += k + "=" + fmt.Sprint(mapData[k])
	}

	return fmt.Sprintf("%X", md5.Sum([]byte(finalStr)))
}

func coordinate2String(lng, lat float64) (str string) {
	return fmt.Sprintf("%.6f,%.6f", lng, lat)
}

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

	err = platformapi.AccessPlatformAPIWithRetry(a.client,
		func() *http.Request {
			request, _ := http.NewRequest(http.MethodGet, utils.GenerateGetURL(prodURLFullv3, 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 := jsonResult1["status"].(string)
			if status == StatusCodeSuccess {
				retVal = jsonResult1
				return platformapi.ErrLevelSuccess, nil
			}
			infoCode := jsonResult1["infocode"].(string)
			newErr := utils.NewErrorCode(jsonResult1["info"].(string), infoCode)
			if _, ok := exceedLimitCodes[infoCode]; ok {
				return platformapi.ErrLevelExceedLimit, newErr
			} else if _, ok := canRetryCodes[infoCode]; ok {
				return platformapi.ErrLevelRecoverableErr, newErr
			} else {
				return platformapi.ErrLevelCodeIsNotOK, newErr
			}
		})
	return retVal, err
}

func (a *API) AccessAPI3(apiStr string, params map[string]interface{}) (retVal ResponseResult, err error) {
	err = platformapi.AccessPlatformAPIWithRetry(a.client,
		func() *http.Request {
			request, _ := http.NewRequest(http.MethodGet, utils.GenerateGetURL(BaseUrl, apiStr, params), 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 := jsonResult1["status"].(string)
			if status == StatusCodeSuccess {
				retVal = jsonResult1
				return platformapi.ErrLevelSuccess, nil
			}
			infoCode := jsonResult1["infocode"].(string)
			newErr := utils.NewErrorCode(jsonResult1["info"].(string), infoCode)
			if _, ok := exceedLimitCodes[infoCode]; ok {
				return platformapi.ErrLevelExceedLimit, newErr
			} else if _, ok := canRetryCodes[infoCode]; ok {
				return platformapi.ErrLevelRecoverableErr, newErr
			} else {
				return platformapi.ErrLevelCodeIsNotOK, newErr
			}
		})
	return retVal, err
}

func (a *API) BatchAccessAPI(apiList []*tBatchAPIParams) (retVal []*tBatchAPIResponse, err error) {
	if len(apiList) == 0 {
		return nil, nil
	}

	var ops []map[string]interface{}
	for _, v := range apiList {
		params2 := utils.MergeMaps(utils.Params2Map("key", a.key, "output", "json"), v.APIParams)
		params2[signKey] = a.signParams(params2)
		op := make(map[string]interface{})
		op["url"] = utils.GenerateGetURL(prodURLv3, v.APIStr, params2)
		ops = append(ops, op)
	}
	params2 := map[string]interface{}{
		"ops": ops,
	}

	err = platformapi.AccessPlatformAPIWithRetry(a.client,
		func() *http.Request {
			request, _ := http.NewRequest(http.MethodGet, utils.GenerateGetURL(prodURLFullv3, batchAPIStr, utils.Params2Map("key", a.key)),
				bytes.NewReader(utils.MustMarshal(params2)))
			request.Header.Set("charset", "UTF-8")
			request.Header.Set("Content-Type", "application/json")
			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")
			}
			if resultList, ok := jsonResult1[platformapi.KeyData].([]interface{}); ok {
				for _, v := range resultList {
					jsonResult1 := v.(map[string]interface{})
					status := int(utils.ForceInterface2Int64(jsonResult1["status"]))
					if status == http.StatusOK {
						jsonResult1 := v.(map[string]interface{})["body"].(map[string]interface{})
						var retVal2 map[string]interface{}
						var err2 error
						status := jsonResult1["status"].(string)
						if status == StatusCodeSuccess {
							retVal2 = jsonResult1
						} else {
							infoCode := jsonResult1["infocode"].(string)
							err2 = utils.NewErrorCode(jsonResult1["info"].(string), infoCode)
						}
						retVal = append(retVal, &tBatchAPIResponse{
							Result: retVal2,
							Err:    err2,
						})
					}
				}
				errLevel = platformapi.ErrLevelSuccess
			} else {
				infoCode := jsonResult1["infocode"].(string)
				err = utils.NewErrorCode(jsonResult1["info"].(string), infoCode)
				if _, ok := exceedLimitCodes[infoCode]; ok {
					errLevel = platformapi.ErrLevelExceedLimit
				} else if _, ok := canRetryCodes[infoCode]; ok {
					errLevel = platformapi.ErrLevelRecoverableErr
				} else {
					errLevel = platformapi.ErrLevelCodeIsNotOK
				}
			}
			return errLevel, err
		})
	return retVal, err
}

// 为了方便调用者编码，如果失败，也会返回未转换的原始值
func (a *API) CoordinateConvert(lng, lat float64, coordsys string) (retLng, retLat float64, err error) {
	// outCoords, err := a.BatchCoordinateConvert([]*Coordinate{
	// 	&Coordinate{
	// 		Lng: lng,
	// 		Lat: lat,
	// 	},
	// }, coordsys)
	// if err == nil {
	// 	retLng, retLat = outCoords[0].Lng, outCoords[0].Lat
	// }
	if coordsys == "" || coordsys == CoordSysAutonavi {
		return lng, lat, nil
	}
	if lng == 0.0 && lat == 0.0 {
		return 0.0, 0.0, nil
	}
	params := map[string]interface{}{
		"locations": coordinate2String(lng, lat),
		"coordsys":  coordsys,
	}
	result, err := a.AccessAPI("assistant/coordinate/convert", params)
	if err == nil {
		coordinate := result["locations"].(string)
		index := strings.Index(coordinate, ",")
		return utils.Str2Float64(coordinate[:index]), utils.Str2Float64(coordinate[index+1:]), nil
	}
	return lng, lat, err
}

func (a *API) GetCoordinateFromAddress(address string, cityInfo string) (lng, lat float64, districtCode int) {
	params := map[string]interface{}{
		"address": address,
	}
	if cityInfo != "" {
		params["city"] = cityInfo
	}

	result, err := a.AccessAPI("geocode/geo", params)
	if err == nil {
		if geocodes, ok := result["geocodes"].([]interface{}); ok && len(geocodes) > 0 {
			geocode := geocodes[0].(map[string]interface{})
			locationList := strings.Split(utils.Interface2String(geocode["location"]), ",")
			if len(locationList) > 1 {
				return utils.Str2Float64WithDefault(locationList[0], 0), utils.Str2Float64WithDefault(locationList[1], 0), int(utils.Str2Int64(utils.Interface2String(geocode["adcode"])))
			}
		}
	}
	return lng, lat, 0
}

func (a *API) GetCoordinateFromAddressAll(address string, cityInfo string) (getCoordinateFromAddressByPageAllResult *GetCoordinateFromAddressByPageAllResult, err error) {
	getCoordinateFromAddressByPageAllResult = &GetCoordinateFromAddressByPageAllResult{}
	params := map[string]interface{}{
		"address": address,
	}
	if cityInfo != "" {
		params["city"] = cityInfo
	}

	result, err := a.AccessAPI("geocode/geo", params)
	if err == nil {
		if geocodes, ok := result["geocodes"].([]interface{}); ok && len(geocodes) > 0 {
			geocode := geocodes[0].(map[string]interface{})
			locationList := strings.Split(utils.Interface2String(geocode["location"]), ",")
			if len(locationList) > 1 {
				getCoordinateFromAddressByPageAllResult.Lng = utils.Str2Float64WithDefault(locationList[0], 0)
				getCoordinateFromAddressByPageAllResult.Lat = utils.Str2Float64WithDefault(locationList[1], 0)
			}
			if str, ok := geocode["city"].(string); ok {
				getCoordinateFromAddressByPageAllResult.CityName = str
			}
			if str, ok := geocode["district"].(string); ok {
				getCoordinateFromAddressByPageAllResult.AdName = str
			}
		}
	}
	return getCoordinateFromAddressByPageAllResult, err
}

func (a *API) GeoCodeRegeoSingle(lng, lat float64, radius int, isExt bool, poiTypes []string, roadLevel, homeOrCorp int) (coordInfo *RegeoCodeInfo, err error) {
	coordInfoList, err := a.GeoCodeRegeo([]*Coordinate{
		&Coordinate{
			Lng: lng,
			Lat: lat,
		},
	}, radius, isExt, poiTypes, roadLevel, homeOrCorp)
	if err == nil {
		coordInfo = coordInfoList[0]
	}
	return coordInfo, err
}

func (a *API) GeoCodeRegeo(coords []*Coordinate, radius int, isExt bool, poiTypes []string, roadLevel, homeOrCorp int) (coordInfoList []*RegeoCodeInfo, err error) {
	coordStrList := make([]string, len(coords))
	for k, v := range coords {
		coordStrList[k] = coordinate2String(v.Lng, v.Lat)
	}
	params := map[string]interface{}{
		"location": strings.Join(coordStrList, "|"),
		"batch":    len(coordStrList) > 1,
	}
	if radius > 0 {
		params["radius"] = radius
	}
	if isExt {
		params["extensions"] = "all"
		params["roadlevel"] = roadLevel
		if len(poiTypes) > 0 {
			params["poitype"] = strings.Join(poiTypes, "|")
		}
		if homeOrCorp > 0 {
			params["homeorcorp"] = homeOrCorp
		}
	}
	result, err := a.AccessAPI("geocode/regeo", params)
	if err == nil {
		if len(coordStrList) > 1 {
			err = utils.Map2StructByJson(result["regeocodes"], &coordInfoList, true)
		} else {
			coordInfoList = make([]*RegeoCodeInfo, 1)
			err = utils.Map2StructByJson(result["regeocode"], &coordInfoList[0], true)
		}
	}
	return coordInfoList, err
}

// 这里的District指的是地点，不是实际上的区，具体级别看level
// 这个函数返回的可能不是同级别的地点
func (a *API) GetDistricts(subDistrict int, keywords string) (districtList []*District, err error) {
	params := map[string]interface{}{
		"subdistrict": subDistrict,
	}
	if keywords != "" {
		params["keywords"] = keywords
	}

	result, err := a.AccessAPI("config/district", params)
	if err == nil {
		return a.getDistrictsFromInterface(result["districts"]), nil
	}
	return nil, err
}

func (a *API) getDistrictsFromInterface(districts interface{}) (districtList []*District) {
	if districts != nil {
		districts2 := districts.([]interface{})
		districtList = make([]*District, len(districts2))
		for k, v := range districts2 {
			v2 := v.(map[string]interface{})
			districtList[k] = &District{
				Adcode: utils.Interface2String(v2["adcode"]),
				Name:   utils.Interface2String(v2["name"]),
				Level:  GetDistrictLevel(utils.Interface2String(v2["level"])),
			}
			coordStrNodes := strings.Split(utils.Interface2String(v2["center"]), ",")
			if len(coordStrNodes) == 2 {
				if coordStrNodes[0] != "" && coordStrNodes[1] != "" {
					districtList[k].Lng = utils.Str2Float64(coordStrNodes[0])
					districtList[k].Lat = utils.Str2Float64(coordStrNodes[1])
				}
			}
			if cityCodeStr, ok := v2["citycode"].(string); ok {
				districtList[k].CityCode = cityCodeStr
			}
			districtList[k].Districts = a.getDistrictsFromInterface(v2["districts"])
		}
	}
	return districtList
}

func GetDistrictLevel(levelName string) (level int) {
	return levelStr2IntMap[levelName]
}

// 两点之间的步行距离，单位为米
func (a *API) WalkingDistance(lng1, lat1, lng2, lat2 float64) (distance float64) {
	params := map[string]interface{}{
		"origin":      coordinate2String(lng1, lat1),
		"destination": coordinate2String(lng2, lat2),
	}
	result, err := a.AccessAPI("direction/walking", params)
	if err == nil {
		if paths, _ := result["route"].(map[string]interface{})["paths"].([]interface{}); len(paths) > 0 {
			distance = utils.Interface2Float64WithDefault(paths[0].(map[string]interface{})["distance"], 0)
		}
	}
	return distance
}

func (a *API) BatchWalkingDistance(srcLng, srcLat float64, destCoords []*Coordinate) (distanceList []float64, err error) {
	var reqList []*tBatchAPIParams
	for _, v := range destCoords {
		reqList = append(reqList, &tBatchAPIParams{
			APIStr: "direction/walking",
			APIParams: map[string]interface{}{
				"origin":      coordinate2String(srcLng, srcLat),
				"destination": coordinate2String(v.Lng, v.Lat),
			},
		})
	}
	resultList, err := a.BatchAccessAPI(reqList)
	if err == nil {
		for _, v := range resultList {
			distance := float64(9527123)
			if v.Err == nil {
				if paths, _ := v.Result["route"].(map[string]interface{})["paths"].([]interface{}); len(paths) > 0 {
					distance = utils.Interface2Float64WithDefault(paths[0].(map[string]interface{})["distance"], 0)
				}
			}
			distanceList = append(distanceList, distance)
		}
	}
	return distanceList, err
}

// 这里的District指的是实际的District，有些市是没有区的，比如东莞，这种情况下返回的区码是一个假的区域，即市的编码加上9000000
func (a *API) GetCoordinateDistrictCode(lng, lat float64) (districtCode int) {
	result, err := a.GetCoordinateAreaInfo(lng, lat)
	if err == nil {
		addressComponent := result["regeocode"].(map[string]interface{})["addressComponent"].(map[string]interface{})
		if strAdcode, ok := addressComponent["adcode"].(string); ok {
			districtCode = int(utils.Str2Int64WithDefault(strAdcode, 0))
			if _, ok := addressComponent["district"].(string); !ok {
				districtCode += FakeDistrictPadding
			}
		}
	}
	return districtCode
}

func (a *API) GetCoordinateAreaInfo(lng, lat float64) (areaInfo map[string]interface{}, err error) {
	params := map[string]interface{}{
		"location": coordinate2String(lng, lat),
	}
	result, err := a.AccessAPI("geocode/regeo", params)
	return result, err
}

func (a *API) GetCoordinateTownInfo(lng, lat float64) (townName, townCode string) {
	result, err := a.GetCoordinateAreaInfo(lng, lat)
	// baseapi.SugarLogger.Debug(utils.Format4Output(result, false))
	if err == nil {
		addressComponent := result["regeocode"].(map[string]interface{})["addressComponent"].(map[string]interface{})
		townName = utils.Interface2String(addressComponent["township"])
		townCode = utils.Interface2String(addressComponent["towncode"])
	}
	return townName, townCode
}

func (a *API) GetCoordinateCityInfo(lng, lat float64) (cityName, cityCode string) {
	result, err := a.GetCoordinateAreaInfo(lng, lat)
	// baseapi.SugarLogger.Debug(utils.Format4Output(result, false))
	if err == nil {
		addressComponent := result["regeocode"].(map[string]interface{})["addressComponent"].(map[string]interface{})
		if addressComponent["city"] != nil {
			if cn, ok := addressComponent["city"].(string); ok {
				cityName = cn
			} else {
				if s, ok := addressComponent["province"].(string); ok {
					cityName = s
				}
			}
		}
		if s, ok := addressComponent["citycode"].(string); ok {
			cityCode = s
		}
		// cityCode = utils.Interface2String(addressComponent["citycode"])
	}
	return cityName, cityCode
}
