package com.sikey.wa04.business.infrastructure.adapter.amap;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sikey.wa04.business.domain.entity.Address;
import com.sikey.wa04.business.domain.entity.ChildId;
import com.sikey.wa04.business.domain.entity.Location;
import com.sikey.wa04.business.domain.usecase.CoordinateConverter;
import com.sikey.wa04.business.infrastructure.adapter.RequestParametersBuilder;
import com.sikey.wa04.business.infrastructure.configuration.AmapConfig;
import com.sikey.wa04.business.infrastructure.dto.request.iot.IotRequest;
import com.sikey.wa04.common.enums.LocationType;
import com.sikey.wa04.common.enums.TrajectoryChannel;
import com.sikey.wa04.common.exception.errors.Errors;
import com.sikey.wa04.common.utils.LocationUtil;
import com.sikey.wa04.common.utils.ObjectMapperUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.gavaghan.geodesy.GlobalCoordinates;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;

/**
 * 高德地图
 *
 * @author luoyangwei
 * @see <a href="https://lbs.amap.com/api/webservice/guide/api/georegeo">高德地图API</a>
 */
@Slf4j
@Component
public class AmapRequestService {

    /**
     * amapSuccessfulStatus is a amap successful status
     */
    private static final String AMAP_SUCCESSFUL_STATUS = "1";

    /**
     * <table>
     *     <tbody>
     *         <tr>
     *             <th>
     *                 <p>infocode</p>
     *             </th>
     *             <th>
     *                 <p>info返回值</p>
     *             </th>
     *             <th>
     *                 <p>状态描述</p></th>
     *             <th>
     *                 <p>问题排查策略</p>
     *             </th>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10000</p></td>
     *             <td>
     *                 <p>OK</p>
     *             </td>
     *             <td>
     *                 <p>请求正常</p>
     *             </td>
     *             <td>
     *                 <p>请求正常</p></td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10001</p>
     *             </td>
     *             <td>
     *                 <p>INVALID_USER_KEY</p></td>
     *             <td>
     *                 <p>key不正确或过期</p></td>
     *             <td>
     *                 <p>开发者发起请求时，传入的key不正确或者过期&nbsp;</p></td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10002</p>
     *             </td>
     *             <td>
     *                 <p>SERVICE_NOT_AVAILABLE</p>
     *             </td>
     *             <td>
     *                 <p>没有权限使用相应的服务或者请求接口的路径拼写错误</p>
     *             </td>
     *             <td>
     *                 <p>1.开发者没有权限使用相应的服务，例如：开发者申请了WEB定位功能的key，却使用该key访问逆地理编码功能时，就会返回该错误。反之亦然。</p>
     *                 <p>2.开发者请求接口的路径拼写错误。例如：正确的 https://restapi.amap.com/
     *                     <strong>v3</strong>/ip在程序中被拼装写了 https://restapi.amap.com/
     *                     <strong>vv3</strong>/ip"</p></td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10003</p>
     *             </td>
     *             <td>
     *                 <p>DAILY_QUERY_OVER_LIMIT</p>
     *             </td>
     *             <td>
     *                 <p>访问已超出日访问量</p>
     *             </td>
     *             <td>
     *                 <p>开发者的日访问量超限，被系统自动封停，第二天0:00会自动解封。</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10004</p>
     *             </td>
     *             <td>
     *                 <p>ACCESS_TOO_FREQUENT</p>
     *             </td>
     *             <td>
     *                 <p>单位时间内访问过于频繁</p>
     *             </td>
     *             <td>
     *                 <p>开发者的单位时间内（1分钟）访问量超限，被系统自动封停，下一分钟自动解封。</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10005</p>
     *             </td>
     *             <td>
     *                 <p>INVALID_USER_IP</p>
     *             </td>
     *             <td>
     *                 <p>IP白名单出错，发送请求的服务器IP不在IP白名单内</p>
     *             </td>
     *             <td>
     *                 <p>开发者在LBS官网控制台设置的IP白名单不正确。白名单中未添加对应服务器的出口IP。可到
     *                     <a href="/dev/" class="" target="">"控制台&gt;配置"</a>&nbsp;中设定IP白名单。</p></td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10006</p>
     *             </td>
     *             <td>
     *                 <p>INVALID_USER_DOMAIN</p>
     *             </td>
     *             <td>
     *                 <p>绑定域名无效</p>
     *             </td>
     *             <td>
     *                 <p>开发者绑定的域名无效，需要在官网控制台重新设置</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10007</p>
     *             </td>
     *             <td>
     *                 <p>INVALID_USER_SIGNATURE</p>
     *             </td>
     *             <td>
     *                 <p>数字签名未通过验证</p>
     *             </td>
     *             <td>
     *                 <p>开发者签名未通过开发者在key控制台中，开启了“数字签名”功能，但没有按照指定算法生成“数字签名”。</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10008</p>
     *             </td>
     *             <td>
     *                 <p>INVALID_USER_SCODE</p>
     *             </td>
     *             <td>
     *                 <p>MD5安全码未通过验证</p>
     *             </td>
     *             <td>
     *                 <p>需要开发者判定key绑定的SHA1,package是否与sdk包里的一致</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10009</p>
     *             </td>
     *             <td>
     *                 <p>USERKEY_PLAT_NOMATCH</p>
     *             </td>
     *             <td>
     *                 <p>请求key与绑定平台不符</p>
     *             </td>
     *             <td>
     *                 <p>请求中使用的key与绑定平台不符，例如：开发者申请的是js api的key，却用来调web服务接口</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10010</p>
     *             </td>
     *             <td>
     *                 <p>IP_QUERY_OVER_LIMIT</p>
     *             </td>
     *             <td>
     *                 <p>IP访问超限</p>
     *             </td>
     *             <td>
     *                 <p>未设定IP白名单的开发者使用key发起请求，从单个IP向服务器发送的请求次数超出限制，被系统自动封停。封停后无法自动恢复，需要提交
     *                     <a href="https://lbs.amap.com/dev/ticket/create/24" class="" target="">工单</a>联系我们。</p></td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10011</p>
     *             </td>
     *             <td>
     *                 <p>NOT_SUPPORT_HTTPS</p>
     *             </td>
     *             <td>
     *                 <p>服务不支持https请求</p>
     *             </td>
     *             <td>
     *                 <p>服务不支持https请求，如果需要申请支持，请提交
     *                     <a href="https://lbs.amap.com/dev/ticket/create/24" class="" target="">工单</a>联系我们</p></td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10012</p>
     *             </td>
     *             <td>
     *                 <p>INSUFFICIENT_PRIVILEGES</p>
     *             </td>
     *             <td>
     *                 <p>权限不足，服务请求被拒绝</p>
     *             </td>
     *             <td>
     *                 <p>由于不具备请求该服务的权限，所以服务被拒绝。</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10013</p>
     *             </td>
     *             <td>
     *                 <p>USER_KEY_RECYCLED</p>
     *             </td>
     *             <td>
     *                 <p>Key被删除</p>
     *             </td>
     *             <td>
     *                 <p>开发者删除了key，key被删除后无法正常使用</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10014</p>
     *             </td>
     *             <td>
     *                 <p>QPS_HAS_EXCEEDED_THE_LIMIT</p>
     *             </td>
     *             <td>
     *                 <p>云图服务QPS超限</p>
     *             </td>
     *             <td>
     *                 <p>QPS超出限制，超出部分的请求被拒绝。限流阈值内的请求依旧会正常返回</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10015</p>
     *             </td>
     *             <td>
     *                 <p>GATEWAY_TIMEOUT</p>
     *             </td>
     *             <td>
     *                 <p>受单机QPS限流限制</p>
     *             </td>
     *             <td>
     *                 <p>受单机QPS限流限制时出现该问题，建议降低请求的QPS或在控制台提工单联系我们</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10016</p>
     *             </td>
     *             <td>
     *                 <p>SERVER_IS_BUSY</p>
     *             </td>
     *             <td>
     *                 <p>服务器负载过高</p>
     *             </td>
     *             <td>
     *                 <p>服务器负载过高，请稍后再试</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10017</p>
     *             </td>
     *             <td>
     *                 <p>RESOURCE_UNAVAILABLE</p>
     *             </td>
     *             <td>
     *                 <p>所请求的资源不可用</p>
     *             </td>
     *             <td>
     *                 <p>所请求的资源不可用</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10019</p>
     *             </td>
     *             <td>
     *                 <p>CQPS_HAS_EXCEEDED_THE_LIMIT</p>
     *             </td>
     *             <td>
     *                 <p>使用的某个服务总QPS超限</p>
     *             </td>
     *             <td>
     *                 <p>QPS超出限制，超出部分的请求被拒绝。限流阈值内的请求依旧会正常返回</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10020</p>
     *             </td>
     *             <td>
     *                 <p>CKQPS_HAS_EXCEEDED_THE_LIMIT</p>
     *             </td>
     *             <td>
     *                 <p>某个Key使用某个服务接口QPS超出限制</p>
     *             </td>
     *             <td>
     *                 <p>QPS超出限制，超出部分的请求被拒绝。限流阈值内的请求依旧会正常返回</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10021</p>
     *             </td>
     *             <td>
     *                 <p>CUQPS_HAS_EXCEEDED_THE_LIMIT&nbsp;</p>
     *             </td>
     *             <td>
     *                 <p>账号使用某个服务接口QPS超出限制</p>
     *             </td>
     *             <td>
     *                 <p>QPS超出限制，超出部分的请求被拒绝。限流阈值内的请求依旧会正常返回</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10026</p>
     *             </td>
     *             <td>
     *                 <p>INVALID_REQUEST</p>
     *             </td>
     *             <td>
     *                 <p>账号处于被封禁状态</p>
     *             </td>
     *             <td>
     *                 <p>由于违规行为账号被封禁不可用，如有异议请登录
     *                     <a href="https://console.amap.com/dev/index" class="" target="">控制台</a>提交工单进行申诉</p></td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10029</p>
     *             </td>
     *             <td>
     *                 <p>ABROAD_DAILY_QUERY_OVER_LIMIT</p>
     *             </td>
     *             <td>
     *                 <p>某个Key的QPS超出限制</p>
     *             </td>
     *             <td>
     *                 <p>QPS超出限制，超出部分的请求被拒绝。限流阈值内的请求依旧会正常返回</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10041</p>
     *             </td>
     *             <td>
     *                 <p>NO_EFFECTIVE_INTERFACE</p>
     *             </td>
     *             <td>
     *                 <p>请求的接口权限过期</p>
     *             </td>
     *             <td>
     *                 <p>开发者发起请求时，请求的接口权限过期。请提交
     *                     <a href="https://lbs.amap.com/dev/ticket/create/24" class="" target="">工单</a>联系我们</p></td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10044</p>
     *             </td>
     *             <td>
     *                 <p>USER_DAILY_QUERY_OVER_LIMIT</p>
     *             </td>
     *             <td>
     *                 <p>账号维度日调用量超出限制</p>
     *             </td>
     *             <td>
     *                 <p>账号维度日调用量超出限制，超出部分的请求被拒绝。限流阈值内的请求依旧会正常返回</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>10045</p>
     *             </td>
     *             <td>
     *                 <p>USER_ABROAD_DAILY_QUERY_OVER_LIMIT</p>
     *             </td>
     *             <td>
     *                 <p>账号维度海外服务日调用量超出限制</p>
     *             </td>
     *             <td>
     *                 <p>账号维度海外服务接口日调用量超出限制，超出部分的请求被拒绝。限流阈值内的请求依旧会正常返回</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>20000</p>
     *             </td>
     *             <td>
     *                 <p>INVALID_PARAMS</p>
     *             </td>
     *             <td>
     *                 <p>请求参数非法</p>
     *             </td>
     *             <td>
     *                 <p>请求参数的值没有按照规范要求填写。例如，某参数值域范围为[1,3],开发者误填了’4’</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>20001</p>
     *             </td>
     *             <td>
     *                 <p>MISSING_REQUIRED_PARAMS</p>
     *             </td>
     *             <td>
     *                 <p>缺少必填参数</p>
     *             </td>
     *             <td>
     *                 <p>缺少接口中要求的必填参数</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>20002</p>
     *             </td>
     *             <td>
     *                 <p>ILLEGAL_REQUEST</p>
     *             </td>
     *             <td>
     *                 <p>请求协议非法</p>
     *             </td>
     *             <td>
     *                 <p>请求协议非法</p>
     *                 <p>比如某接口仅支持get请求，结果用了POST方式</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>20003</p>
     *             </td>
     *             <td>
     *                 <p>UNKNOWN_ERROR</p>
     *             </td>
     *             <td>
     *                 <p>其他未知错误</p>
     *             </td>
     *             <td>
     *                 <p>其他未知错误</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>20011</p>
     *             </td>
     *             <td>
     *                 <p>INSUFFICIENT_ABROAD_PRIVILEGES</p>
     *             </td>
     *             <td>
     *                 <p>查询坐标或规划点（包括起点、终点、途经点）在海外，但没有海外地图权限</p>
     *             </td>
     *             <td>
     *                 <p>使用逆地理编码接口、输入提示接口、周边搜索接口、路径规划接口时可能出现该问题，规划点（包括起点、终点、途经点）不在中国陆地范围内</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>20012</p>
     *             </td>
     *             <td>
     *                 <p>ILLEGAL_CONTENT</p>
     *             </td>
     *             <td>
     *                 <p>查询信息存在非法内容</p>
     *             </td>
     *             <td>
     *                 <p>使用搜索接口时可能出现该问题，通常是由于查询内容非法导致</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>20800</p>
     *             </td>
     *             <td>
     *                 <p>OUT_OF_SERVICE</p>
     *             </td>
     *             <td>
     *                 <p>规划点（包括起点、终点、途经点）不在中国陆地范围内</p>
     *             </td>
     *             <td>
     *                 <p>使用路径规划服务接口时可能出现该问题，规划点（包括起点、终点、途经点）不在中国大陆陆地范围内</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>20801</p>
     *             </td>
     *             <td>
     *                 <p>NO_ROADS_NEARBY</p>
     *             </td>
     *             <td>
     *                 <p>划点（起点、终点、途经点）附近搜不到路</p>
     *             </td>
     *             <td>
     *                 <p>使用路径规划服务接口时可能出现该问题，划点（起点、终点、途经点）附近搜不到路</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>20802</p>
     *             </td>
     *             <td>
     *                 <p>ROUTE_FAIL</p>
     *             </td>
     *             <td>
     *                 <p>路线计算失败，通常是由于道路连通关系导致</p>
     *             </td>
     *             <td>
     *                 <p>使用路径规划服务接口时可能出现该问题，路线计算失败，通常是由于道路连通关系导致</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>20803</p>
     *             </td>
     *             <td>
     *                 <p>OVER_DIRECTION_RANGE</p>
     *             </td>
     *             <td>
     *                 <p>起点终点距离过长。</p>
     *             </td>
     *             <td>
     *                 <p>使用路径规划服务接口时可能出现该问题，路线计算失败，通常是由于道路起点和终点距离过长导致。</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>300**</p>
     *             </td>
     *             <td>
     *                 <p>ENGINE_RESPONSE_DATA_ERROR</p>
     *             </td>
     *             <td>
     *                 <p>服务响应失败。</p>
     *             </td>
     *             <td>
     *                 <p>出现3开头的错误码，建议先检查传入参数是否正确，若无法解决，请详细描述错误复现信息，提
     *                     <a href="https://lbs.amap.com/dev/ticket/create/24" class="" target="">
     *                         <code class="inline-code">工单</code></a>给我们。（大数据接口请直接跟负责商务反馈）</p>
     *                 <p>如，30001、30002、30003、32000、32001、32002、32003、32200、32201、32202、32203。</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>40000</p>
     *             </td>
     *             <td>
     *                 <p>QUOTA_PLAN_RUN_OUT</p>
     *             </td>
     *             <td>
     *                 <p>余额耗尽</p>
     *             </td>
     *             <td>
     *                 <p>所购买服务的余额耗尽，无法继续使用服务</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>40001</p>
     *             </td>
     *             <td>
     *                 <p>GEOFENCE_MAX_COUNT_REACHED</p>
     *             </td>
     *             <td>
     *                 <p>围栏个数达到上限</p>
     *             </td>
     *             <td>
     *                 <p>Key可创建的地理围栏的数量，已达上限。</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>40002</p>
     *             </td>
     *             <td>
     *                 <p>SERVICE_EXPIRED</p>
     *             </td>
     *             <td>
     *                 <p>购买服务到期</p>
     *             </td>
     *             <td>
     *                 <p>所购买的服务期限已到，无法继续使用</p>
     *             </td>
     *         </tr>
     *         <tr>
     *             <td>
     *                 <p>40003</p>
     *             </td>
     *             <td>
     *                 <p>ABROAD_QUOTA_PLAN_RUN_OUT</p>
     *             </td>
     *             <td>
     *                 <p>海外服务余额耗尽</p>
     *             </td>
     *             <td>
     *                 <p>所购买服务的海外余额耗尽，无法继续使用服务</p>
     *             </td>
     *         </tr>
     *     </tbody>
     * </table>
     */
    private static final String AMAP_OK_POINT = "10000";

    @Resource
    private AmapConfig amapConfig;

    /**
     * Get the location of the child
     *
     * @param childId childId
     * @param request request
     * @return location
     * @throws IOException IOException
     */
    public Location position(ChildId childId, IotRequest request) throws IOException {
        LocationType locationType = LocationType.valueOfValue(request.accesstype());

        // Build request parameters
        Map<String, String> stringMap = new ObjectMapper().convertValue(request, ObjectMapperUtil.getValueTypeRef());
        stringMap.put("key", amapConfig.getKey());
        stringMap.put("show_fields", "addressComponent");
        // 设备唯一编号 用于问题排查，建议填充 imei、IDFA 等唯一标识，未填则后续无法排查问题
        stringMap.put("diu", request.ticket());

        RequestParametersBuilder builder = new RequestParametersBuilder(stringMap, "ticket", "traceId");
        String query = builder.queryEncode();
        String requestUrl = String.format("%s?%s", amapConfig.getUrl(), query);
        log.info("Amap request url: {}", requestUrl);

        Request get = new Request.Builder()
                .url(requestUrl)
                .post(RequestBody.create("{}".getBytes()))
                .build();
        OkHttpClient httpClient = new OkHttpClient();
        Response response = httpClient.newCall(get).execute();
        if (!response.isSuccessful() || response.body() == null) {
            throw new RuntimeException("Unexpected code " + response);
        }

        AmapResponse amapResponse = new ObjectMapper().readValue(response.body().string(), AmapResponse.class);
        if (!AMAP_SUCCESSFUL_STATUS.equals(amapResponse.getStatus())) {
            log.error("Amap request failed, response", new RuntimeException(amapResponse.toString()));
            throw Errors.LOCATION_IOT_AMAP_REQUEST_FAILED.getException();
        }

        if (!AMAP_OK_POINT.equals(amapResponse.getInfoCode())) {
            log.error("Amap request failed, response", new RuntimeException(amapResponse.toString()));
            throw Errors.LOCATION_IOT_AMAP_REQUEST_FAILED.getException();
        }

        AmapResponse.Position position = amapResponse.getPosition();
        AmapResponse.AddressComponent addressComponent = amapResponse.getAddressComponent();
        String[] points = LocationUtil.splitPoint(position.getLocation());
        // 上海市 浦东新区 规划一支路 靠近陆家嘴软件园
        // city district  road  poi

        String desc = "%s %s %s %s".formatted(
                addressComponent.getCity(),
                addressComponent.getDistrict(),
                addressComponent.getRoad(),
                addressComponent.getPoi());
        Address address = new Address()
                .setLongitude(points[0])
                .setLatitude(points[1])
                .setRadius(position.getRadius())
                .setDesc(desc)
                .setCountry(addressComponent.getCountry())
                .setProvince(addressComponent.getProvince())
                .setCity(addressComponent.getCity())
                .setCityCode(addressComponent.getCityCode())
                .setAdCode(addressComponent.getAdCode())
                .setRoad(addressComponent.getRoad())
                .setPoi(addressComponent.getPoi());
        Location location = new Location()
                .setChannel(TrajectoryChannel.AMAP)
                .setType(locationType)
                .setChildId(childId)
                .setTraceId(request.traceId())
                .setAddress(address);
        log.info("Amap coordinates address: {}", location.getAddress());

        // Transform coordinates
        CoordinateConverter coordinateConverter = CoordinateConverter.getInstance(CoordinateConverter.defaultPoints());
        GlobalCoordinates coordinates = location.getCoordinates();
        location.setCoordinates(coordinateConverter.GCJ02toWGS84(coordinates.getLongitude(), coordinates.getLatitude()));
        log.info("Amap transform coordinates after address: {}", location.getAddress());
        return location;
    }
}
