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

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.push.template.NotificationMessageTemplate;
import com.sikey.wa04.business.infrastructure.configuration.PushConfig;
import com.sikey.wa04.common.enums.MessageType;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.PSSParameterSpec;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 华为V3推送通知，只会给HarmonyOS Next/5.x及之后的系统版本推送通知
 * https://push-api.cloud.huawei.com/v3/[projectId]/messages:send
 *
 * @author yuzenghui
 */
@Setter
@Slf4j
public class HuaweiV3PushNotification implements AndroidPushNotification {
    private static final String AUTHENTICATE_REQUEST_URL = "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
    private static final String AUTHENTICATE_REQUEST_CONTENT_TYPE = "application/x-www-form-urlencoded";

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

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

    /**
     * 服务账号密钥文件中的key_id
     */
    private static final String SERVICE_ACCOUNT_KEY_FILE_KEY_ID = "5b129827bfcb4647bdc1a42386adafbb";
    /**
     * 服务账号密钥文件中的sub_account
     */
    private static final String SERVICE_ACCOUNT_KEY_FILE_SUB_ACCOUNT = "115042337";

    /**
     * 服务账号密钥文件中的private_key
     */
    private static final String SERVICE_ACCOUNT_KEY_FILE_PRIVATE_KEY = "MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDMSpstJhA+OaCG3ul41ctHKN9X3RU9SEVCknV3WEz005KGmDtYYcP85O9okj41h3PBFjdh6Y/9ofkp4LeasHJ7g8itpRVTmpsYJQN8G/KPETq1QXCYFkUhIakEhZZJDFgbPqI6u2SUmlkulWVcEBizfMeuaYdndJhwGa/a3e+gSGNBsby4octvyCsKYs9+dx3aYUomNfvrBDMmSGi98SsPZkspp+uX8+AvWW6/Uqs67l8s8vC6CUWHNjeG04tyANvkUYK3nX9schNuKXsDsZkZI1zMt/2IhXybOc8FE3lQnHVJGvrQKljvjy2fi830CehTSmY17eTZqaZ20biTgbWpCbJaBA3m7O2THpxlnG7n1fOn7CgE8kDUy+3PO38hQb/XoocJoLsowyfFXVErWe8jOJ/Dc02qAYIRe1cW9Gg8MgN2fYjJVzUO600wJTtU//DRja8tko0lXFubP0IiAl4OaOO/JfGPfnce2gZ0Ge2ckRv/ehHpy1YmsuxmmecdSjrQDIR3gw8kYxWXRU1lW/H+a9kqG8tLJ2/sL76PUZcY+cNWJ8tQjMQmPqhDlAm4KZ8lyz/HPpwL1tTVleSo8Zo13GLdHaon3I05BmLIhFYyE62CYWmsJ0ChJUxDPxfFgZVCvqkz0eH428Nqt9yB+oOVz6iFxEuuPLYZhbXvm4hjkwIDAQABAoICAApXGmH0zmyqiQwFO2cGLJWrpXEH5oZ6t9AQpAkdkI5HKTWj00PRdansF+FrSY0huoOtnAcE2z4Yv95VRVB7RDLT7vPkal8YG704nmOz8cPjuNzUykirE+ZYkvqrck8R5LcYT/4jXwoV1+lqNBOurY65hE05LS/Y2yBixHUSYl8dWtzH4h1W9HpHdO+VJHB/wfnm478UYMMQAYYbXdCDZPe7aJbcAio89KjLi4tROWEwZePbYMDh2EGURnI4wehMJSp6JQt7LtMZNmTkjO6ZGNPeEi6Qf4Y0pM53OhnFLGZJTQxyulA6sdQKTg0Idq0L//+QlqTTJ5AMLLn7OoRI0/N6UakaIETSjaW2l+K5emgfGDEUAkZofM2ItiwxcHMGJySP6kfe31csPj2q6CjomkZfR66mfMb/EEBSrR/L0O5q1/Mw+raVRRyz94C8zRLC6X0dM5CX0mglRljI/8ZRDmizVB3BvHG0Kq2p7Up1QIatO/hfSqe9XfNrviji+fwCQiewtcsGhU9QxAZpi983CmxIKOwz0x74p/OI1AGh+ZB2vxQHS+O1PugkC1c7JxZI68m4qXI0MSRutDSnIXej49biRNNDgwclhEqIhBWCUieyoY6r6KZbnuETpRjlWbOAycwp7jHVjElzS8KWgsMO0sayzD4LaZRaCGFepW0WYsM5AoIBAQD5J/O9xtS7cIQMDVs09k6mn+UeySszsM1WLa5BMD25gDEDKREdSHCSzG/7pSJYfUgbHAr48wVv0uPNk4vJYg2DX6/jtp6Ik/9IERp55XTy8wDVOFuSb/mZzzMXy5yk+JLo27oxGYIix+emGnQOxbp6mhsYcAEi2Y0S5shrIXTHxcdSTqMP3OvgD0HlTGjWvhs19DO3SB+hkC5T4BbUIK0evdwd1qMPiwgahjZnPkJKmXsGs8rU8uno0R63U0zgjpZiFD3e3zWdMK2WvxCdVNHxtMo1oc6VpCeGmdmt6nsDuD8/PLsKa6hkgLw0IuUMR3r7r74lF5JdS9h/zL1RDybrAoIBAQDR5ytKjtJH74oLRWEEYcxtssz1/QaTQ7Bx4DcHMrkXYZdJPWTeAjwFmJQF1cZllwe3wYroAu996+KA9JPAeve2fvyW+Q7o8YEnqatTS09z9hU/4NxdCCS5G6nXY9icQBWjL3zH6brPyhXa+2R4QXjQoKngPBnD19lu64XjVNFUNEnv7MQnpHOLWJ0AB06xJI5w53FTNpcbtr9UTyk4ff03D7TyBnQCdKdSfY8V5qFveVPVtIaVKQbGuN3HItnR+5NGn28TS59IHNvxmtdllZMO+tgV0A0WWHgjjUnTp7NinEfvCqIJKq8Cxn3SiBZowwRC/djLSUXYzHqKp1KxYlv5AoIBAQCHx6kCHBAkkd20P7cbtrsH/Q086L59bfWI6F4VMngA1oZfRYsJHUgUCAti5QxBkwyvkWm9X5rv5Mq2ZpkAR0o79PKCsyvUHGs7V+7IzS4dlCY5DHyC4jjxBETAy/pywPA2rD/csB0GXVC1aacjwcetNrxbZfaIimxiB5jjIRlmITdNeINOisOUmFv93NUpafKkRY/Fay2pu0wz82QdeC1AGpGRRT4Zo1foNIoDW59+zgOIlISN5k9ozEXj/CZiHfKAQq2w/o1WO4fuJfOk+etrwG2OKr7NN+c+aCuDpswhK9Bz4uG/fQoIs0tdUkLmnTG9ZXnG9wCRjVrP0sC9Ive/AoIBAArQDbrZgJuUC1hpSoYfQ5ijDUverOgJJJ25HxyP0FkT/ecX8oo9MzpWkcl7Ka0yCoO8xJsPC6dA73oVH0dbDkeoYvqj+dCmIDEa3Gh+CKnAk3em7bMdKEcg1a7yxcXGx1aQq6zgrW7myhltuc84EDEvFZEnh2w/QkQkfdEoHWBpESX63F/uaMVU20GVHsihSXUndv8CYOZck56UNVtjohekB34wNumpeLUXIrfOGaLUbS0lD4m/g0nZXBNWCEJ44y1QKTilPJ9KXUok39Or0NeEtHzrKsFr7URnktYEXYlG4628rnwqZrmN4A1oVT8wTJfm27QdIiv43Jcn52qb1oECggEAW2r/36932mHOrHUr8TEsXV+Zklyfz/8GUlQgjzqF3To9aIR0AuywPmzT6muJuLAazssRwYCVLdBSs06uIe5c3lzbNxCIP+fjeLm3etaj+tflCGSsuYIrmGKSFpvSCdvqqprWRNy/uPAUYR5ErdQER5V15sNNK6a+/2Yh3NGh815JrpJPTOzzFjEBlr3yVkruZXxME7DhLKUdyYB+1854GyIMODMMXg+VQQRad3bBZfSkAVeJBO7pE2fNIs5mWwIU7tzgOrKDAQ/4qOjqOLcnk37RNSsWRovxXv4DOtcESvDdm4Wqy20nnot/aTQYAPR0RzcxZ1mIdGsQPO4AgZo8PA==";
    /**
     * <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;

    // 添加BouncyCastle作为安全提供者
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

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


    private PushConfig pushConfig;

    public static String createJwt() throws Exception {
        RSAPrivateKey privateKey = (RSAPrivateKey) generatePrivateKey(SERVICE_ACCOUNT_KEY_FILE_PRIVATE_KEY
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s", ""));
//        long iat = System.currentTimeMillis() / 1000;
//        long exp = iat + 3600;
        long iat = Instant.now().getEpochSecond();
        long exp = iat + 3600;

        //生成JWT Header数据。
        Map<String, Object> header = Map.of(
                "kid", SERVICE_ACCOUNT_KEY_FILE_KEY_ID,
                "typ", "JWT",
                "alg", "PS256");

        Map<String, Object> payload = Map.of(
                "aud", AUTHENTICATE_REQUEST_URL,
                "iss", SERVICE_ACCOUNT_KEY_FILE_SUB_ACCOUNT,
                "iat", iat,
                "exp", exp
        );

        // 编码Header和Payload
        String encodedHeader = base64UrlEncode(objectMapper.writeValueAsString(header).getBytes(StandardCharsets.UTF_8));
        String encodedPayload = base64UrlEncode(objectMapper.writeValueAsString(payload).getBytes(StandardCharsets.UTF_8));

        // 拼接签名内容
        String signingInput = encodedHeader + "." + encodedPayload;

        // 使用SHA256withRSA/PSS算法签名
        byte[] signature = signWithPSS(privateKey, signingInput.getBytes(StandardCharsets.UTF_8));

        // 构建完整的JWT
        return signingInput + "." + base64UrlEncode(signature);
    }

    private static PrivateKey generatePrivateKey(String base64Key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64Key.getBytes(StandardCharsets.UTF_8)));
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(keySpec);
    }

    private static byte[] signWithPSS(PrivateKey privateKey, byte[] data) throws Exception {
        // 使用SHA256withRSA/PSS算法
        Signature signature = Signature.getInstance("SHA256withRSAandMGF1", "BC");

        // 配置PSS参数
        PSSParameterSpec pssParameterSpec = new PSSParameterSpec(
                "SHA-256",
                "MGF1",
                MGF1ParameterSpec.SHA256,
                32,
                PSSParameterSpec.TRAILER_FIELD_BC
        );

        signature.setParameter(pssParameterSpec);
        signature.initSign(privateKey);
        signature.update(data);
        return signature.sign();
    }

    private static String base64UrlEncode(byte[] data) {
        return Base64.getUrlEncoder().withoutPadding().encodeToString(data);
    }

    @NotNull
    private static HuaweiData getHuaweiData(NotificationMessageTemplate template) throws JsonProcessingException {
        String title = template.getTitle();
        String body = template.getBody();
        String category = NOTIFICATION_CATEGORY_IM;
        Integer notify_id = -1;
        // 根据消息类型处理特殊逻辑
        Integer msgType = template.getMsgType();
        if (Objects.equals(msgType, MessageType.VIDEO_CALL_IN_NOTICE_MESSAGE.getValue())) {
            notify_id = MessageType.VIDEO_CALL_IN_NOTICE_MESSAGE.getValue();
            title = "视频通话";
            body = "点击查看";
            category =  NOTIFICATION_CATEGORY_VOIP;
        }else if(MessageType.getChatMsgTypes().contains(msgType)) {
            notify_id =MessageType.CHAT_TEXT_MESSAGE.getValue();
            title = "收到新语聊消息";
            body = "点击查看";
        }else if (Objects.equals(msgType, MessageType.LOCATION_EXIT_SAFETY_FENCE_NOTICE_MESSAGE.getValue())) {
            notify_id = MessageType.LOCATION_EXIT_SAFETY_FENCE_NOTICE_MESSAGE.getValue();
            title = "离开安全围栏";
            body = "点击查看";
        } else if (Objects.equals(msgType, MessageType.LOCATION_ENTER_DANGER_FENCE_NOTICE_MESSAGE.getValue())) {
            notify_id = MessageType.LOCATION_ENTER_DANGER_FENCE_NOTICE_MESSAGE.getValue();
            title = "进入危险围栏";
            body = "点击查看";
        }

        AndroidNotificationClickAction clickAction = new AndroidNotificationClickAction(0);
        HuaweiMessageAndroidNotification notification =
                new HuaweiMessageAndroidNotification(category, title, body, notify_id, clickAction);

        HuaweiMessagePayload payload = new HuaweiMessagePayload(notification, objectMapper.writeValueAsString(template.getParams()));
        return new HuaweiData(payload, List.of(template.getRegId()));
    }

    @Override
    public AccessToken authenticate(PushConfig pushConfig) throws ExecutionException {
        setPushConfig(Objects.requireNonNull(pushConfig));
        return accessTokenCache.get(pushConfig.getHuawei().getClientId(), () -> {
            String token = null;
            try {
                token = createJwt();
                log.info("createJwt: {}", token);
            } catch (Exception e) {
                log.error("createJwt error :", e);
            }
            return new AccessToken().setValue(token);
        });
    }

    @Override
    public SendResult send(NotificationMessageTemplate template, AccessToken accessToken) throws IOException {
        HuaweiData data = getHuaweiData(template);
        RequestBody requestBody = RequestBody.create(objectMapper.writeValueAsString(data),
                MediaType.parse(AUTHENTICATE_REQUEST_CONTENT_TYPE));
        Request request = new Request.Builder()
                .post(requestBody)
                .addHeader("Authorization", String.format("Bearer %s", accessToken.getValue()))
                .addHeader("push-type", "0")
                .url(String.format("https://push-api.cloud.huawei.com/v3/%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()) {
            log.info("Send Huawei notification message successfully, {}", response.body().string());
        }
        return new SendResult()
                .setSuccessful(response.isSuccessful())
                .setRetryNum(retryNum.get());
    }

    /**
     * 华为消息推送数据
     *
     * @param payload Android Message
     * @param target   推送目标
     */
    private record HuaweiData(HuaweiMessagePayload payload, List<String> target) {
    }

    /**
     * 华为通知消息
     *
     * @param notification 通知
     * @param extraData    数据 "{'param1':'value1','param2':'value2'}"
     *
     */
    private record HuaweiMessagePayload(HuaweiMessageAndroidNotification notification, String extraData) {
    }

    /**
     * 华为通知
     *
     * @param category     类别 默认使用 IM：即时聊天
     * @param title       标题
     * @param body        内容
     * @param notifyId    每条消息在通知显示时的唯一标识。不携带或者设置-1时，推送服务自动为每条消息生成一个唯一标识；不同的通知消息可以拥有相同的notifyId，实现新消息覆盖旧消息功能。若要用于消息撤回则必填，且范围为[0, 2147483647]，即非负值。
     *
     * @param clickAction 点击动作
     */
    private record HuaweiMessageAndroidNotification(String category, String title, String body, Integer notifyId,
                                                    AndroidNotificationClickAction clickAction) {
    }

    /**
     * 华为通知点击动作
     *
     * @param actionType 类型消息点击后的行为。
     * 0：打开应用首页
     * 1：打开应用自定义页面
     * 3：清除通知
     * 5：打开拨号界面
     *
     */
    private record AndroidNotificationClickAction(Integer actionType) {
    }
}
