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

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.sikey.wa04.business.infrastructure.adapter.RequestParametersBuilder;
import com.sikey.wa04.business.infrastructure.adapter.push.template.NotificationMessageTemplate;
import com.sikey.wa04.business.infrastructure.configuration.PushConfig;
import com.sikey.wa04.common.enums.MessageType;
import com.sikey.wa04.common.utils.ObjectMapperUtil;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 华为V2推送通知，只会给HarmonyOS 3.x/4.x的系统版本推送通知
 *
 * @author luoyangwei
 */
@Setter
@Slf4j
public class HuaweiPushNotification implements AndroidPushNotification {
    private static final String AUTHENTICATE_REQUEST_URL = "https://oauth-login.cloud.huawei.com/oauth2/v3/token";

    /**
     * 请求华为接口https://oauth-login.cloud.huawei.com/oauth2/v3/token时使用
     */
    private static final String AUTHENTICATE_REQUEST_CONTENT_TYPE_TOKEN = "application/x-www-form-urlencoded";

    /**
     * 请求华为接口https://push-api.cloud.huawei.com/v2/[projectid]/messages:send时使用
     */
    private static final String AUTHENTICATE_REQUEST_CONTENT_TYPE_SEND = "application/json; charset=UTF-8";

    /**
     * 完成自分类权益申请后，用于标识消息类型，确定消息提醒方式，对特定类型消息加快发送<br/>
     * 默认使用：IM，如果不设置类别 Huawei 会认为是营销通知会做拦截
     */
    private static final String NOTIFICATION_CATEGORY_IM = "IM";

    /**
     * 当msgType消息类型是视频通话时，需要使用：VOIP
     */
    private static final String NOTIFICATION_CATEGORY_VOIP = "VOIP";

    /**
     * <h2> expires_in 令牌</h2>
     * <p>Access Token 的剩余有效期，单位：秒。</p>
     * 开发者应用在Access Token有效期（expires_in）内，请勿过于频繁请求，否则可能会被流控（当前应用级Access Token的流控阈值为1000个/5分钟， <br/>
     * 5分钟内申请Access Token超过1000个，后续将提示被流控，无法申请。5分钟后重置，可以再次申请）。
     * <br/>
     * Huawei 定义的是 3600 秒即 1 小时，但我们考虑到网络请求所需要的耗时。这里不能顶满 3600 秒，所以定义为 3000 秒
     */
    private static final Duration ACCESS_TOKEN_EXPIRATION = Duration.ofSeconds(3000);
    private static final Cache<String, AccessToken> accessTokenCache = CacheBuilder.newBuilder()
            .expireAfterWrite(ACCESS_TOKEN_EXPIRATION) // 设定过期策略，按照数据写入时间进行计算
            .build();

    private static final ObjectMapper objectMapper;

    static {
        objectMapper = new Jackson2ObjectMapperBuilder().build();
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
    }

    private PushConfig pushConfig;

    /**
     * 组装请求华为接口的推送消息message
     *
     * @param template  参数
     * @return HuaweiData
     * @throws JsonProcessingException
     *
     */
    @NotNull
    private static HuaweiData getHuaweiData(NotificationMessageTemplate template) throws JsonProcessingException {
        // 根据消息类型处理特殊逻辑
        Integer msgType = template.getMsgType();
        if (Objects.equals(msgType, MessageType.VIDEO_CALL_IN_NOTICE_MESSAGE.getValue()) || Objects.equals(msgType, MessageType.VIDEO_CALL_OUT_NOTICE_MESSAGE.getValue())) {
            HuaweiMessageAndroidVideo androidVideo = new HuaweiMessageAndroidVideo(NOTIFICATION_CATEGORY_VOIP);
            HuaweiMessageVideo huaweiMessageVideo = new HuaweiMessageVideo(androidVideo, List.of(template.getRegId()),
                    objectMapper.writeValueAsString(template.getParams()));
            return new HuaweiData(false, huaweiMessageVideo);
        }
        Integer notify_id = -1;
        if(MessageType.getChatMsgTypes().contains(msgType)) {
            notify_id =MessageType.CHAT_TEXT_MESSAGE.getValue();
        } else if (Objects.equals(msgType, MessageType.LOCATION_EXIT_SAFETY_FENCE_NOTICE_MESSAGE.getValue())) {
            notify_id = MessageType.LOCATION_EXIT_SAFETY_FENCE_NOTICE_MESSAGE.getValue();
        } else if (Objects.equals(msgType, MessageType.LOCATION_ENTER_DANGER_FENCE_NOTICE_MESSAGE.getValue())) {
            notify_id = MessageType.LOCATION_ENTER_DANGER_FENCE_NOTICE_MESSAGE.getValue();
        } else if (Objects.equals(msgType, MessageType.DEVICE_LOW_BATTERY_NOTICE_MESSAGE.getValue())){
            notify_id = MessageType.DEVICE_LOW_BATTERY_NOTICE_MESSAGE.getValue();
        }


        AndroidNotificationClickAction clickAction = new AndroidNotificationClickAction(3);
        HuaweiMessageAndroidNotification notification =
                new HuaweiMessageAndroidNotification(template.getTitle(), template.getBody(), notify_id, clickAction);
        HuaweiMessageAndroid android = new HuaweiMessageAndroid(notification, NOTIFICATION_CATEGORY_IM);

        HuaweiMessage huaweiMessage = new HuaweiMessage(android, List.of(template.getRegId()),
                objectMapper.writeValueAsString(template.getParams()));
        return new HuaweiData(false, huaweiMessage);

    }

    @Override
    public AccessToken authenticate(PushConfig pushConfig) throws ExecutionException {
        setPushConfig(Objects.requireNonNull(pushConfig));
        return accessTokenCache.get(pushConfig.getHuawei().getClientId(), () -> {
            OAuth2RequestBody oAuth2RequestBody = new OAuth2RequestBody();
            oAuth2RequestBody.setClientId(pushConfig.getHuawei().getClientId());
            oAuth2RequestBody.setClientSecret(pushConfig.getHuawei().getClientSecret());

            Map<String, String> params = objectMapper.convertValue(oAuth2RequestBody, ObjectMapperUtil.getValueTypeRef());
            RequestParametersBuilder requestParametersBuilder = new RequestParametersBuilder(params);
            String bodyQuery = requestParametersBuilder.queryEncode();
            RequestBody requestBody = RequestBody.create(bodyQuery,
                    MediaType.parse(AUTHENTICATE_REQUEST_CONTENT_TYPE_TOKEN));

            Request request = new Request.Builder()
                    .url(AUTHENTICATE_REQUEST_URL)
                    .post(requestBody)
                    .build();
            HuaweiAuthenticateResponseBody responseBody = new HuaweiAuthenticateResponseBody();
            try {
                OkHttpClient httpClient = new OkHttpClient();
                Response response = httpClient.newCall(request).execute();
                if (response.body() != null) {
                    responseBody = objectMapper.readValue(response.body().string(), HuaweiAuthenticateResponseBody.class);
                }
            } catch (IOException e) {
                throw new RuntimeException("Failed to authenticate", e);
            }

            return new AccessToken().setValue(responseBody.getAccessToken());
        });
    }

    @Override
    public SendResult send(NotificationMessageTemplate template, AccessToken accessToken) throws IOException {
        log.info("[HuaweiPushNotification]send请求参数：template: {}; accessToken: {}", objectMapper.writeValueAsString(template), accessToken.toString());
        HuaweiData data = getHuaweiData(template);
        log.info("[HuaweiPushNotification]data: {}", objectMapper.writeValueAsString(data));
        RequestBody requestBody = RequestBody.create(objectMapper.writeValueAsString(data),
                MediaType.parse(AUTHENTICATE_REQUEST_CONTENT_TYPE_SEND));
        Request request = new Request.Builder()
                .post(requestBody)
                .addHeader("Authorization", String.format("Bearer %s", accessToken.getValue()))
                .url(String.format("https://push-api.cloud.huawei.com/v2/%s/messages:send", pushConfig.getHuawei().getProjectId()))
                .build();

        AtomicInteger retryNum = new AtomicInteger();
        final int maxRetry = 3;
        OkHttpClient httpClient = new OkHttpClient().newBuilder()
                .addInterceptor((chain) -> {
                    Response response = chain.proceed(chain.request());
                    while (!response.isSuccessful() && retryNum.get() < maxRetry) {
                        retryNum.getAndIncrement();
                        response = chain.proceed(chain.request());
                    }
                    return response;
                })
                .build();
        Response response = httpClient.newCall(request).execute();
        if (response.isSuccessful()) {
            String responseBody = response.body() != null ? response.body().string() : "";
            log.info("[HuaweiPushNotification]Send Huawei successfully, {}", responseBody);
        }
        return new SendResult()
                .setSuccessful(response.isSuccessful())
                .setRetryNum(retryNum.get());
    }

    @Getter
    private static class OAuth2RequestBody {
        private final String grantType = "client_credentials";
        @Setter
        private String clientId;
        @Setter
        private String clientSecret;
    }

    @Data
    private static class HuaweiAuthenticateResponseBody {
        private String accessToken;
        private String tokenType;
        private Integer expiresIn;
    }

    /**
     * 华为推送数据
     * @param validateOnly        是否仅验证
     * @param message             消息
     */
    private record HuaweiData(boolean validateOnly, HuaweiMessageBase message) {
    }

    // 公共接口
    private interface HuaweiMessageBase {}

    /**
     * 华为通知消息-视频通话类型时才传
     *
     * @param category     类别 默认使用 IM：即时聊天
     */
    private record HuaweiMessageAndroidVideo(String category) {
    }

    /**
     * 华为消息-视频通话类型时才传
     *
     * @param android Android Message
     * @param token   推送目标
     * @param data    数据 "{'param1':'value1','param2':'value2'}"
     */
    private record HuaweiMessageVideo(HuaweiMessageAndroidVideo android, List<String> token, String data) implements HuaweiMessageBase {
    }

    /**
     * 华为消息
     *
     * @param android Android Message
     * @param token   推送目标
     * @param data    数据 "{'param1':'value1','param2':'value2'}"
     */
    private record HuaweiMessage(HuaweiMessageAndroid android, List<String> token, String data) implements HuaweiMessageBase {} {
    }

    /**
     * 华为通知消息
     *
     * @param notification 通知
     * @param category     类别 默认使用 IM：即时聊天
     */
    private record HuaweiMessageAndroid(HuaweiMessageAndroidNotification notification, String category) {
    }

    /**
     * 华为通知
     *
     * @param title       标题
     * @param body        内容
     * @param notify_id   每条消息在通知显示时的唯一标识。不携带或者设置-1时，推送服务自动为每条消息生成一个唯一标识；不同的通知栏消息可以拥有相同的notifyId，实现新消息覆盖旧消息功能
     * @param click_action 点击动作
     */
    private record HuaweiMessageAndroidNotification(String title, String body, Integer notify_id,
                                                    AndroidNotificationClickAction click_action) {
    }

    /**
     * 华为通知点击动作
     *
     * @param type 类型
     */
    private record AndroidNotificationClickAction(Integer type) {
    }
}
