package shop.alien.store.service.channel;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSONArray;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import shop.alien.entity.store.CommonPushChannelConfig;
import shop.alien.entity.store.CommonPushTask;
import shop.alien.store.config.CommonPushProperties;
import shop.alien.store.dto.CommonPushChannelStatsDto;
import shop.alien.store.dto.CommonPushTargetDto;
import shop.alien.store.dto.CommonPushVendorSendResult;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;

/**
 * 各厂商推送 HTTP 客户端，凭证从配置表 credential_json 读取。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class CommonPushVendorHttpClient {

    private static final int TIMEOUT_MS = 8000;
    /** OPPO call_back_parameter 最大 100 字符 */
    private static final int OPPO_CALLBACK_PARAM_MAX_LEN = 100;

    private static final int HUAWEI_BATCH_TOKEN_LIMIT = 1000;
    /** 华为 OAuth 换 token（与推送 API 域名不同） */
    private static final String HUAWEI_OAUTH_URL = "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
    /** 华为 Push Kit 下行消息 API */
    private static final String HUAWEI_PUSH_API_BASE = "https://push-api.cloud.huawei.com";
    /** 荣耀 Push API */
    private static final String HONOR_PUSH_API_BASE = "https://push-api.cloud.honor.com";
    private static final String HONOR_AUTH_URL =
            "https://iam.developer.honor.com/auth/token";
    /** 华为推送成功响应码 */
    private static final String HUAWEI_SUCCESS_CODE = "80000000";

    private final CommonPushProperties commonPushProperties;
    private final ApnsPushClient apnsPushClient;

    private static final RestTemplate restTemplate = new RestTemplate();

    /**
     * OPPO 全量广播推送（target_type=1），用于 targetType=1 全量场景。
     */
    public CommonPushVendorSendResult sendOppoBroadcast(CommonPushChannelConfig config, CommonPushTask task) {
        if (config == null || task == null) {
            return CommonPushVendorSendResult.fail();
        }
        JSONObject credential = parseCredential(config.getCredentialJson());
        if (credential == null) {
            return CommonPushVendorSendResult.fail();
        }
        try {
            String authToken = obtainOppoAuthToken(credential);
            if (StringUtils.isBlank(authToken)) {
                return CommonPushVendorSendResult.fail();
            }
            String messageId = saveOppoMessageContent(authToken, task, null);
            if (StringUtils.isBlank(messageId)) {
                return CommonPushVendorSendResult.fail();
            }
            String taskId = broadcastOppoMessage(authToken, messageId);
            if (StringUtils.isBlank(taskId)) {
                return CommonPushVendorSendResult.fail();
            }
            return CommonPushVendorSendResult.ok(taskId, messageId);
        } catch (Exception e) {
            log.error("OPPO 广播推送失败, taskNo={}, err={}", task.getTaskNo(), e.getMessage(), e);
            return CommonPushVendorSendResult.fail();
        }
    }

    /**
     * 小米全量广播推送，用于 targetType=1 全量场景。
     */
    public CommonPushVendorSendResult sendXiaomiBroadcast(CommonPushChannelConfig config, CommonPushTask task) {
        if (config == null || task == null) {
            return CommonPushVendorSendResult.fail();
        }
        JSONObject credential = parseCredential(config.getCredentialJson());
        if (credential == null) {
            return CommonPushVendorSendResult.fail();
        }
        String appSecret = credential.getString("appSecret");
        if (StringUtils.isBlank(appSecret)) {
            return CommonPushVendorSendResult.fail();
        }
        if (!validateXiaomiCredential(credential, task.getTaskNo())) {
            return CommonPushVendorSendResult.fail();
        }
        try {
            MultiValueMap<String, String> form = buildXiaomiMessageForm(credential, task, null);
            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "key=" + appSecret);
            String resp = postForm("https://api.xmpush.xiaomi.com/v3/message/all", form, headers);
            return parseXiaomiSendResult(resp, task.getTaskNo(), "全量广播");
        } catch (Exception e) {
            log.error("小米广播推送失败, taskNo={}, err={}", task.getTaskNo(), e.getMessage(), e);
            return CommonPushVendorSendResult.fail();
        }
    }

    /**
     * vivo 全量广播推送，用于 targetType=1 全量场景。
     */
    public CommonPushVendorSendResult sendVivoBroadcast(CommonPushChannelConfig config, CommonPushTask task) {
        if (config == null || task == null) {
            return CommonPushVendorSendResult.fail();
        }
        JSONObject credential = parseCredential(config.getCredentialJson());
        if (credential == null) {
            return CommonPushVendorSendResult.fail();
        }
        try {
            String authToken = obtainVivoAuthToken(credential);
            if (StringUtils.isBlank(authToken)) {
                return CommonPushVendorSendResult.fail();
            }
            JSONObject sendBody = buildVivoNotificationBody(credential, task, null);
            Map<String, String> headers = new HashMap<>();
            headers.put("authToken", authToken);
            String sendResp = postJson("https://api-push.vivo.com.cn/message/all", sendBody.toJSONString(), headers);
            return parseVivoSendResult(sendResp, task.getTaskNo(), "全量广播");
        } catch (Exception e) {
            log.error("vivo 广播推送失败, taskNo={}, err={}", task.getTaskNo(), e.getMessage(), e);
            return CommonPushVendorSendResult.fail();
        }
    }

    /**
     * 华为全量推送：官方无 /all 接口，按 token 批量下发（每批最多 1000 个）。
     */
    public CommonPushVendorSendResult sendHuaweiBroadcast(CommonPushChannelConfig config, CommonPushTask task,
                                       List<CommonPushTargetDto> targets) {
        if (config == null || task == null) {
            return CommonPushVendorSendResult.fail();
        }
        JSONObject credential = parseCredential(config.getCredentialJson());
        if (credential == null) {
            return CommonPushVendorSendResult.fail();
        }
        List<String> deviceIds = extractDeviceIds(targets);
        if (deviceIds.isEmpty()) {
            log.warn("华为全量推送无可用 deviceId, taskNo={}", task.getTaskNo());
            return CommonPushVendorSendResult.fail();
        }
        try {
            int successBatches = 0;
            int totalBatches = 0;
            String requestId = null;
            for (List<String> batch : partitionList(deviceIds, HUAWEI_BATCH_TOKEN_LIMIT)) {
                totalBatches++;
                CommonPushVendorSendResult batchResult = sendHuaweiLike(credential, task, batch);
                if (batchResult.isSuccess()) {
                    successBatches++;
                    if (requestId == null) {
                        requestId = batchResult.getVendorTaskId();
                    }
                }
            }
            log.info("华为全量批量推送完成, taskNo={}, devices={}, batches={}/{}",
                    task.getTaskNo(), deviceIds.size(), successBatches, totalBatches);
            return successBatches > 0 ? CommonPushVendorSendResult.ok(requestId) : CommonPushVendorSendResult.fail();
        } catch (Exception e) {
            log.error("华为全量推送失败, taskNo={}, err={}", task.getTaskNo(), e.getMessage(), e);
            return CommonPushVendorSendResult.fail();
        }
    }

    /**
     * 荣耀全量推送：官方无 /all 接口，按 token 批量下发（每批最多 1000 个）。
     */
    public CommonPushVendorSendResult sendHonorBroadcast(CommonPushChannelConfig config, CommonPushTask task,
                                      List<CommonPushTargetDto> targets) {
        if (config == null || task == null) {
            return CommonPushVendorSendResult.fail();
        }
        JSONObject credential = parseCredential(config.getCredentialJson());
        if (credential == null) {
            return CommonPushVendorSendResult.fail();
        }
        List<String> deviceIds = extractDeviceIds(targets);
        if (deviceIds.isEmpty()) {
            log.warn("荣耀全量推送无可用 deviceId, taskNo={}", task.getTaskNo());
            return CommonPushVendorSendResult.fail();
        }
        try {
            int successBatches = 0;
            int totalBatches = 0;
            String requestId = null;
            for (List<String> batch : partitionList(deviceIds, HUAWEI_BATCH_TOKEN_LIMIT)) {
                totalBatches++;
                CommonPushVendorSendResult batchResult = sendHonorLike(credential, task, batch);
                if (batchResult.isSuccess()) {
                    successBatches++;
                    if (requestId == null) {
                        requestId = batchResult.getVendorTaskId();
                    }
                }
            }
            log.info("荣耀全量批量推送完成, taskNo={}, devices={}, batches={}/{}",
                    task.getTaskNo(), deviceIds.size(), successBatches, totalBatches);
            return successBatches > 0 ? CommonPushVendorSendResult.ok(requestId) : CommonPushVendorSendResult.fail();
        } catch (Exception e) {
            log.error("荣耀全量推送失败, taskNo={}, err={}", task.getTaskNo(), e.getMessage(), e);
            return CommonPushVendorSendResult.fail();
        }
    }

    /**
     * APNs 全量推送：按 iOS deviceToken 逐条下发（Apple 无 /all 接口）。
     */
    public CommonPushVendorSendResult sendApnsBroadcast(CommonPushChannelConfig config, CommonPushTask task,
                                     List<CommonPushTargetDto> targets) {
        if (config == null || task == null) {
            return CommonPushVendorSendResult.fail();
        }
        JSONObject credential = parseCredential(config.getCredentialJson());
        if (credential == null || !apnsPushClient.validateCredential(credential)) {
            return CommonPushVendorSendResult.fail();
        }
        List<CommonPushTargetDto> apnsTargets = ApnsPushClient.filterApnsTargets(targets, targets);
        if (apnsTargets.isEmpty()) {
            log.warn("APNs 全量推送无 iOS 设备 token, taskNo={}", task.getTaskNo());
            return CommonPushVendorSendResult.fail();
        }
        boolean ok = apnsPushClient.sendBroadcast(credential, task, apnsTargets);
        return ok ? CommonPushVendorSendResult.ok(task.getTaskNo()) : CommonPushVendorSendResult.fail();
    }

    public CommonPushVendorSendResult send(CommonPushChannelConfig config, CommonPushTask task, CommonPushTargetDto target) {
        if (config == null || task == null || target == null || StringUtils.isBlank(target.getDeviceId())) {
            return CommonPushVendorSendResult.fail();
        }
        String deviceId = target.getDeviceId();
        JSONObject credential = parseCredential(config.getCredentialJson());
        if (credential == null) {
            return CommonPushVendorSendResult.fail();
        }
        String channelCode = StringUtils.lowerCase(StringUtils.trim(config.getChannelCode()));
        try {
            switch (channelCode) {
                case "vivo":
                    return sendVivo(credential, task, target);
                case "xiaomi":
                    return sendXiaomi(credential, task, target);
                case "oppo":
                    return sendOppo(credential, task, target);
                case "samsung":
                    return sendSamsung(credential, task, target) ? CommonPushVendorSendResult.ok(task.getTaskNo())
                            : CommonPushVendorSendResult.fail();
                case "huawei":
                    return sendHuawei(credential, task, deviceId);
                case "honor":
                    return sendHonor(credential, task, deviceId);
                case "apns":
                    return sendApns(credential, task, deviceId);
                default:
                    log.warn("不支持的推送渠道: {}", channelCode);
                    return CommonPushVendorSendResult.fail();
            }
        } catch (Exception e) {
            log.error("渠道推送失败, channelCode={}, deviceId={}, err={}", channelCode, deviceId, e.getMessage(), e);
            return CommonPushVendorSendResult.fail();
        }
    }

    private CommonPushVendorSendResult sendVivo(JSONObject credential, CommonPushTask task, CommonPushTargetDto target) {
        String deviceId = target.getDeviceId();
        if (StringUtils.isBlank(deviceId)) {
            return CommonPushVendorSendResult.fail();
        }
        String authToken = obtainVivoAuthToken(credential);
        if (StringUtils.isBlank(authToken)) {
            return CommonPushVendorSendResult.fail();
        }
        JSONObject sendBody = buildVivoNotificationBody(credential, task, target);
        sendBody.put("regId", deviceId);
        Map<String, String> headers = new HashMap<>();
        headers.put("authToken", authToken);
        String sendResp = postJson("https://api-push.vivo.com.cn/message/send", sendBody.toJSONString(), headers);
        return parseVivoSendResult(sendResp, task.getTaskNo(), "单推");
    }

    private String obtainVivoAuthToken(JSONObject credential) {
        String appId = credential.getString("appId");
        String appKey = credential.getString("appKey");
        String appSecret = credential.getString("appSecret");
        if (StringUtils.isAnyBlank(appId, appKey, appSecret)) {
            return null;
        }
        long timestamp = System.currentTimeMillis();
        String sign = md5Hex(appId + appKey + timestamp + appSecret);
        JSONObject authBody = new JSONObject();
        authBody.put("appId", appId);
        authBody.put("appKey", appKey);
        authBody.put("timestamp", timestamp);
        authBody.put("sign", sign);
        String authResp = postJson("https://api-push.vivo.com.cn/message/auth", authBody.toJSONString(), null);
        JSONObject authJson = JSONObject.parseObject(authResp);
        if (authJson == null || authJson.getIntValue("result") != 0) {
            log.warn("vivo 鉴权失败: {}", authResp);
            return null;
        }
        return authJson.getString("authToken");
    }

    private JSONObject buildVivoNotificationBody(JSONObject credential, CommonPushTask task,
                                                    CommonPushTargetDto target) {
        JSONObject sendBody = new JSONObject();
        sendBody.put("appId", credential.getString("appId"));
        sendBody.put("notifyType", 1);
        sendBody.put("title", task.getTitle());
        sendBody.put("content", task.getContent());
        if (StringUtils.isNotBlank(task.getJumpUrl())) {
            sendBody.put("skipType", 2);
            sendBody.put("skipContent", task.getJumpUrl());
        } else {
            sendBody.put("skipType", 1);
        }
        sendBody.put("requestId", buildVivoRequestId(task));
        JSONObject extra = new JSONObject();
        extra.put("callback", resolveCallbackUrl());
        extra.put("callback.param", buildVivoCallbackParameter(task, target));
        sendBody.put("extra", extra);
        return sendBody;
    }

    private String buildVivoRequestId(CommonPushTask task) {
        String base = StringUtils.defaultIfBlank(task.getTaskNo(), "TASK");
        String requestId = base + "_" + System.currentTimeMillis();
        return requestId.length() > 64 ? requestId.substring(0, 64) : requestId;
    }

    private String buildVivoCallbackParameter(CommonPushTask task, CommonPushTargetDto target) {
        JSONObject param = new JSONObject(true);
        if (task.getId() != null) {
            param.put("p", task.getId());
        } else if (StringUtils.isNotBlank(task.getTaskNo())) {
            param.put("t", task.getTaskNo().trim());
        }
        if (target != null && target.getUserId() != null) {
            param.put("u", target.getUserId());
        }
        param.put("c", "vivo");
        return param.toJSONString();
    }

    private CommonPushVendorSendResult parseVivoSendResult(String resp, String taskNo, String scene) {
        log.info("vivo{}响应: {}", scene, resp);
        JSONObject json = JSONObject.parseObject(resp);
        if (json != null && json.getIntValue("result") == 0) {
            String taskId = StringUtils.defaultIfBlank(json.getString("taskId"), json.getString("task_id"));
            return CommonPushVendorSendResult.ok(taskId);
        }
        if (json != null) {
            log.error("vivo{}失败, taskNo={}, result={}, desc={}",
                    scene, taskNo, json.getIntValue("result"), json.getString("desc"));
        }
        return CommonPushVendorSendResult.fail();
    }

    private CommonPushVendorSendResult sendXiaomi(JSONObject credential, CommonPushTask task, CommonPushTargetDto target) {
        String deviceId = target.getDeviceId();
        String appSecret = credential.getString("appSecret");
        if (StringUtils.isAnyBlank(appSecret, deviceId)) {
            return CommonPushVendorSendResult.fail();
        }
        if (!validateXiaomiCredential(credential, task.getTaskNo())) {
            return CommonPushVendorSendResult.fail();
        }
        MultiValueMap<String, String> form = buildXiaomiMessageForm(credential, task, target);
        form.add("registration_id", deviceId);
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", "key=" + appSecret);
        String resp = postForm("https://api.xmpush.xiaomi.com/v3/message/regid", form, headers);
        return parseXiaomiSendResult(resp, task.getTaskNo(), "单推");
    }

    private boolean validateXiaomiCredential(JSONObject credential, String taskNo) {
        String channelId = resolveXiaomiChannelId(credential);
        if (StringUtils.isBlank(channelId)) {
            log.error("小米推送缺少 channelId, taskNo={}。小米运营平台已创建 channel 后，还需将 channel_id 写入本系统 common_push_channel_config.credentialJson.channelId",
                    taskNo);
            return false;
        }
        if (StringUtils.isBlank(credential.getString("packageName"))) {
            log.warn("小米推送未配置 packageName, taskNo={}, channelId={}，请确认与小米应用信息中的包名一致",
                    taskNo, channelId);
        }
        return true;
    }

    private String resolveXiaomiChannelId(JSONObject credential) {
        if (credential == null) {
            return null;
        }
        return StringUtils.defaultIfBlank(
                StringUtils.trimToNull(credential.getString("channelId")),
                StringUtils.trimToNull(credential.getString("channel_id")));
    }

    private CommonPushVendorSendResult parseXiaomiSendResult(String resp, String taskNo, String scene) {
        log.info("小米{}响应: {}", scene, resp);
        JSONObject json = JSONObject.parseObject(resp);
        if (json != null && "ok".equalsIgnoreCase(json.getString("result"))) {
            String msgId = extractXiaomiMessageId(json);
            return CommonPushVendorSendResult.ok(msgId);
        }
        if (json != null) {
            log.error("小米{}失败, taskNo={}, code={}, reason={}, description={}",
                    scene, taskNo, json.getString("code"), json.getString("reason"), json.getString("description"));
        }
        return CommonPushVendorSendResult.fail();
    }

    private String extractXiaomiMessageId(JSONObject json) {
        if (json == null) {
            return null;
        }
        JSONObject data = json.getJSONObject("data");
        if (data != null) {
            String id = StringUtils.defaultIfBlank(data.getString("id"), data.getString("msg_id"));
            if (StringUtils.isNotBlank(id)) {
                return id;
            }
        }
        return StringUtils.defaultIfBlank(json.getString("messageId"), json.getString("msg_id"));
    }

    private MultiValueMap<String, String> buildXiaomiMessageForm(JSONObject credential, CommonPushTask task,
                                                                   CommonPushTargetDto target) {
        MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
        form.add("title", task.getTitle());
        form.add("description", task.getContent());
        form.add("payload", buildPayload(task));
        String packageName = credential.getString("packageName");
        if (StringUtils.isNotBlank(packageName)) {
            form.add("restricted_package_name", packageName.trim());
        } else {
            log.warn("小米推送未配置 packageName，多包名应用可能返回 22022 或 27001");
        }
        appendXiaomiExtraParams(form, credential, task, target);
        return form;
    }

    /**
     * 小米 HTTP API 要求以 extra.xxx 形式传参（非 extra JSON 字符串）。
     */
    private void appendXiaomiExtraParams(MultiValueMap<String, String> form, JSONObject credential,
                                           CommonPushTask task, CommonPushTargetDto target) {
        String channelId = resolveXiaomiChannelId(credential);
        if (StringUtils.isNotBlank(channelId)) {
            form.add("extra.channel_id", channelId);
        }
        form.add("extra.callback", resolveCallbackUrl());
        form.add("extra.callback.param", buildXiaomiCallbackParameter(task, target));
        if (StringUtils.isNotBlank(task.getJumpUrl())) {
            form.add("extra.notify_effect", "3");
            form.add("extra.web_uri", task.getJumpUrl());
        }
    }

    private String buildXiaomiCallbackParameter(CommonPushTask task, CommonPushTargetDto target) {
        JSONObject param = new JSONObject(true);
        if (task.getId() != null) {
            param.put("p", task.getId());
        } else if (StringUtils.isNotBlank(task.getTaskNo())) {
            param.put("t", task.getTaskNo().trim());
        }
        if (target != null && target.getUserId() != null) {
            param.put("u", target.getUserId());
        }
        param.put("c", "xiaomi");
        return param.toJSONString();
    }

    /**
     * OPPO 厂商直连：使用 RegistrationID（非 UniPush CID），并设置送达回执回调。
     */
    private CommonPushVendorSendResult sendOppo(JSONObject credential, CommonPushTask task, CommonPushTargetDto target) {
        String deviceId = target.getDeviceId();
        String authToken = obtainOppoAuthToken(credential);
        if (StringUtils.isBlank(authToken)) {
            return CommonPushVendorSendResult.fail();
        }

        JSONObject notification = buildOppoNotification(task, target);
        JSONObject messageRoot = new JSONObject();
        messageRoot.put("target_type", 2);
        messageRoot.put("target_value", deviceId);
        messageRoot.put("notification", notification);

        MultiValueMap<String, String> sendForm = new LinkedMultiValueMap<>();
        sendForm.add("auth_token", authToken);
        sendForm.add("message", messageRoot.toJSONString());

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(sendForm, headers);
        try {
            ResponseEntity<String> response = restTemplate.postForEntity(
                    "https://api.push.oppomobile.com/server/v1/message/notification/unicast",
                    entity,
                    String.class
            );
            String body = response.getBody();
            log.info("OPPO 单推响应: {}", body);
            return parseOppoSendResult(body);
        } catch (Exception e) {
            log.error("OPPO 单推请求异常: {}", e.getMessage(), e);
            return CommonPushVendorSendResult.fail();
        }
    }

    private CommonPushVendorSendResult parseOppoSendResult(String resp) {
        JSONObject json = JSONObject.parseObject(resp);
        if (json == null || json.getIntValue("code") != 0) {
            return CommonPushVendorSendResult.fail();
        }
        JSONObject data = json.getJSONObject("data");
        if (data == null) {
            return CommonPushVendorSendResult.ok(null);
        }
        String taskId = StringUtils.defaultIfBlank(data.getString("task_id"), data.getString("message_id"));
        String messageId = StringUtils.defaultIfBlank(data.getString("message_id"), data.getString("messageId"));
        return CommonPushVendorSendResult.ok(taskId, messageId);
    }

    private String obtainOppoAuthToken(JSONObject credential) {
        String appKey = credential.getString("appKey");
        String masterSecret = credential.getString("appSecret");
        if (StringUtils.isAnyBlank(appKey, masterSecret)) {
            return null;
        }
        long timestamp = System.currentTimeMillis();
        String sign = cn.hutool.crypto.SecureUtil.sha256(appKey + timestamp + masterSecret);
        MultiValueMap<String, String> authForm = new LinkedMultiValueMap<>();
        authForm.add("app_key", appKey);
        authForm.add("timestamp", String.valueOf(timestamp));
        authForm.add("sign", sign);
        String authResp = postForm("https://api.push.oppomobile.com/server/v1/auth", authForm, null);
        JSONObject authJson = JSONObject.parseObject(authResp);
        if (authJson == null || authJson.getJSONObject("data") == null) {
            log.warn("OPPO 鉴权失败: {}", authResp);
            return null;
        }
        return authJson.getJSONObject("data").getString("auth_token");
    }

    private JSONObject buildOppoNotification(CommonPushTask task, CommonPushTargetDto target) {
        JSONObject notification = new JSONObject();
        notification.put("title", task.getTitle());
        notification.put("content", task.getContent());
        notification.put("call_back_url", resolveCallbackUrl());
        notification.put("call_back_parameter", buildCallbackParameter(task, target));
        if (StringUtils.isNotBlank(task.getJumpUrl())) {
            notification.put("click_action_url", task.getJumpUrl());
        }
        return notification;
    }

    /**
     * 保存 OPPO 广播消息体，返回 message_id。
     */
    private String saveOppoMessageContent(String authToken, CommonPushTask task, CommonPushTargetDto target) {
        MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
        form.add("auth_token", authToken);
        form.add("title", task.getTitle());
        form.add("content", task.getContent());
        form.add("call_back_url", resolveCallbackUrl());
        form.add("call_back_parameter", buildCallbackParameter(task, target));
        if (StringUtils.isNotBlank(task.getJumpUrl())) {
            form.add("click_action_url", task.getJumpUrl());
        }
        String resp = postForm("https://api.push.oppomobile.com/server/v1/message/notification/save_message_content", form, null);
        log.info("OPPO 保存广播消息体响应: {}", resp);
        JSONObject json = JSONObject.parseObject(resp);
        if (json == null || json.getJSONObject("data") == null) {
            return null;
        }
        return json.getJSONObject("data").getString("message_id");
    }

    /**
     * OPPO 广播下发，target_type=1 表示全量用户，返回 task_id。
     */
    private String broadcastOppoMessage(String authToken, String messageId) {
        MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
        form.add("auth_token", authToken);
        form.add("message_id", messageId);
        form.add("target_type", "1");
        String resp = postForm("https://api.push.oppomobile.com/server/v1/message/notification/broadcast", form, null);
        log.info("OPPO 广播推送响应: {}", resp);
        JSONObject json = JSONObject.parseObject(resp);
        if (json == null || json.getIntValue("code") != 0) {
            return null;
        }
        JSONObject data = json.getJSONObject("data");
        if (data == null) {
            return messageId;
        }
        return StringUtils.defaultIfBlank(data.getString("task_id"), messageId);
    }

    private String resolveCallbackUrl() {
        return StringUtils.defaultIfBlank(commonPushProperties.getCallbackUrl(),
                "https://frp-off.com:40279/alienStore/commonPushTaskUser/callback");
    }

    /**
     * OPPO call_back_parameter 限 100 字符，仅传 pushTaskId + userId 短键，deviceId 由回执 registrationIds 带回。
     */
    private String buildCallbackParameter(CommonPushTask task, CommonPushTargetDto target) {
        JSONObject param = new JSONObject(true);
        if (task.getId() != null) {
            param.put("p", task.getId());
        } else if (StringUtils.isNotBlank(task.getTaskNo())) {
            param.put("t", task.getTaskNo().trim());
        }
        if (target != null && target.getUserId() != null) {
            param.put("u", target.getUserId());
        }
        String json = param.toJSONString();
        if (json.length() > OPPO_CALLBACK_PARAM_MAX_LEN) {
            log.warn("OPPO call_back_parameter 超长({}), 降级为仅 pushTaskId/taskNo", json.length());
            JSONObject minimal = new JSONObject(true);
            if (task.getId() != null) {
                minimal.put("p", task.getId());
            } else if (StringUtils.isNotBlank(task.getTaskNo())) {
                String taskNo = task.getTaskNo().trim();
                int maxTaskNoLen = OPPO_CALLBACK_PARAM_MAX_LEN - 7;
                if (taskNo.length() > maxTaskNoLen) {
                    taskNo = taskNo.substring(0, maxTaskNoLen);
                }
                minimal.put("t", taskNo);
            }
            json = minimal.toJSONString();
        }
        if (json.length() > OPPO_CALLBACK_PARAM_MAX_LEN) {
            log.error("OPPO call_back_parameter 仍超长({}): {}", json.length(), json);
        }
        return json;
    }

    /**
     * 三星推送（Samsung Push Platform / SPP），根据 regID 前缀选择区域 RQM 节点。
     */
    private boolean sendSamsung(JSONObject credential, CommonPushTask task, CommonPushTargetDto target) {
        String deviceId = target.getDeviceId();
        String appId = StringUtils.defaultIfBlank(credential.getString("appId"), credential.getString("appID"));
        String appSecret = StringUtils.defaultIfBlank(credential.getString("appSecret"),
                credential.getString("app_secret"));
        if (StringUtils.isAnyBlank(appId, appSecret)) {
            log.warn("三星推送凭证不完整, deviceId={}", deviceId);
            return false;
        }
        JSONObject body = new JSONObject();
        body.put("regID", deviceId);
        body.put("requestID", StringUtils.defaultIfBlank(task.getTaskNo(), "REQ" + System.currentTimeMillis()));
        body.put("message", buildSamsungMessage(task));

        Map<String, String> headers = new HashMap<>();
        headers.put("appID", appId);
        headers.put("appSecret", appSecret);

        String url = resolveSamsungPushUrl(deviceId);
        String resp = postJson(url, body.toJSONString(), headers);
        log.info("三星推送响应: url={}, body={}", url, resp);
        JSONObject json = JSONObject.parseObject(resp);
        return json != null && json.getIntValue("statusCode") == 1000;
    }

    private String buildSamsungMessage(CommonPushTask task) {
        StringBuilder sb = new StringBuilder("action=ALERT");
        if (StringUtils.isNotBlank(task.getTitle())) {
            sb.append("&alertTitle=").append(urlEncode(task.getTitle()));
        }
        if (StringUtils.isNotBlank(task.getContent())) {
            sb.append("&alertMessage=").append(urlEncode(task.getContent()));
        }
        if (StringUtils.isNotBlank(task.getJumpUrl())) {
            sb.append("&uri=").append(urlEncode(task.getJumpUrl()));
        }
        return sb.toString();
    }

    private String resolveSamsungPushUrl(String regId) {
        if (StringUtils.isBlank(regId) || regId.length() < 2) {
            return "https://apchina.push.samsungosp.com.cn:8090/spp/pns/api/push";
        }
        switch (regId.substring(0, 2)) {
            case "00":
                return "https://useast.push.samsungosp.com:8090/spp/pns/api/push";
            case "02":
                return "https://apsoutheast.push.samsungosp.com:8090/spp/pns/api/push";
            case "03":
                return "https://euwest.push.samsungosp.com:8090/spp/pns/api/push";
            case "04":
                return "https://apnortheast.push.samsungosp.com:8090/spp/pns/api/push";
            case "05":
                return "https://apkorea.push.samsungosp.com:8090/spp/pns/api/push";
            case "06":
                return "https://apchina.push.samsungosp.com.cn:8090/spp/pns/api/push";
            case "50":
                return "https://useast.gateway.push.samsungosp.com:8090/spp/pns/api/push";
            case "52":
                return "https://apsoutheast.gateway.push.samsungosp.com:8090/spp/pns/api/push";
            case "53":
                return "https://euwest.gateway.push.samsungosp.com:8090/spp/pns/api/push";
            case "54":
                return "https://apnortheast.gateway.push.samsungosp.com:8090/spp/pns/api/push";
            case "55":
                return "https://apkorea.gateway.push.samsungosp.com:8090/spp/pns/api/push";
            case "56":
                return "https://apchina.gateway.push.samsungosp.com.cn:8090/spp/pns/api/push";
            default:
                return "https://apchina.push.samsungosp.com.cn:8090/spp/pns/api/push";
        }
    }

    private String urlEncode(String value) {
        try {
            return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
        } catch (Exception e) {
            return value;
        }
    }

    private CommonPushVendorSendResult sendHuawei(JSONObject credential, CommonPushTask task, String deviceId) {
        return sendHuaweiLike(credential, task, Collections.singletonList(deviceId));
    }

    private CommonPushVendorSendResult sendHonor(JSONObject credential, CommonPushTask task, String deviceId) {
        return sendHonorLike(credential, task, Collections.singletonList(deviceId));
    }

    /**
     * 华为 Push Kit 批量/单条下发：OAuth 换 token → 组装 message body → POST messages:send。
     * <p>格式转换 credential_json → 请求 URL：</p>
     * <pre>
     * 转换前 credential: { "clientId": "115095083", "clientSecret": "..." }
     * 转换后 URL: https://push-api.cloud.huawei.com/v1/115095083/messages:send
     * </pre>
     */
    private CommonPushVendorSendResult sendHuaweiLike(JSONObject credential, CommonPushTask task, List<String> deviceIds) {
        if (deviceIds == null || deviceIds.isEmpty()) {
            return CommonPushVendorSendResult.fail();
        }
        String pushClientId = resolveHuaweiOAuthClientId(credential);
        String projectId = resolveHuaweiProjectId(credential);
        if (StringUtils.isBlank(pushClientId)) {
            log.warn("华为推送缺少 clientId/appId, taskNo={}", task.getTaskNo());
            return CommonPushVendorSendResult.fail();
        }
        List<String> pushTokens = normalizeHuaweiPushTokens(deviceIds);
        if (pushTokens.isEmpty()) {
            log.warn("华为推送 token 为空, taskNo={}", task.getTaskNo());
            return CommonPushVendorSendResult.fail();
        }
        for (String pushToken : pushTokens) {
            if (!isLikelyHuaweiPushToken(pushToken)) {
                log.warn("华为推送 token 格式可疑, taskNo={}, token={}, 请确认 life_user.device_id 存的是 HMS PushKit token 而非 uni-push cid 或其他厂商 regId",
                        task.getTaskNo(), maskPushToken(pushToken));
            }
        }
        String accessToken = obtainHuaweiAccessToken(credential);
        if (StringUtils.isBlank(accessToken)) {
            return CommonPushVendorSendResult.fail();
        }

        JSONObject body = buildHuaweiMessageBody(task, pushTokens);
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", "Bearer " + accessToken);
        // v1 路径参数为 OAuth 2.0 client ID，与换 token 的 client_id 一致
        String sendUrl = HUAWEI_PUSH_API_BASE + "/v1/" + pushClientId + "/messages:send";
        log.info("华为推送请求, taskNo={}, tokenCount={}, sendUrl={}, sampleToken={}",
                task.getTaskNo(), pushTokens.size(), sendUrl, maskPushToken(pushTokens.get(0)));
        String sendResp = postJson(sendUrl, body.toJSONString(), headers);
        log.info("推送参数和结果为，accessToken={}, sendResp={}", accessToken, sendResp);
        return parseHuaweiSendResult(sendResp, task.getTaskNo(), pushTokens.size());
    }

    private String resolveHuaweiProjectId(JSONObject credential) {
        String projectId = StringUtils.trimToNull(credential.getString("projectId"));
        if (projectId != null) {
            return projectId;
        }
        return null;
    }

    private CommonPushVendorSendResult sendHonorLike(JSONObject credential, CommonPushTask task, List<String> deviceIds) {
        if (deviceIds == null || deviceIds.isEmpty()) {
            return CommonPushVendorSendResult.fail();
        }
        String appId = credential.getString("appId");
        String appSecret = credential.getString("appSecret");
        if (StringUtils.isAnyBlank(appId, appSecret)) {
            return CommonPushVendorSendResult.fail();
        }
        List<String> pushTokens = normalizeHuaweiPushTokens(deviceIds);
        if (pushTokens.isEmpty()) {
            log.warn("荣耀推送 token 为空, taskNo={}", task.getTaskNo());
            return CommonPushVendorSendResult.fail();
        }
        String accessToken = obtainHonorAccessToken(credential);
        if (StringUtils.isBlank(accessToken)) {
            return CommonPushVendorSendResult.fail();
        }

        JSONObject body = buildHuaweiMessageBody(task, pushTokens);
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", "Bearer " + accessToken);
        String sendResp = postJson(HONOR_PUSH_API_BASE + "/v1/" + appId + "/sendMessage",
                body.toJSONString(), headers);
        return parseHuaweiSendResult(sendResp, task.getTaskNo(), pushTokens.size());
    }

    /**
     * 华为推送 API URL 中的 App ID（AppGallery Connect 应用 ID）。
     */
    private String resolveHuaweiPushAppId(JSONObject credential) {
        String appId = StringUtils.trimToNull(credential.getString("appId"));
        if (appId != null) {
            return appId;
        }
        return StringUtils.trimToNull(credential.getString("clientId"));
    }

    /**
     * OAuth client_id：优先 clientId，否则与 pushAppId 一致。
     * 须与下发 URL 中的 appId 对应同一应用，否则返回 80200001。
     */
    private String resolveHuaweiOAuthClientId(JSONObject credential) {
        String clientId = StringUtils.trimToNull(credential.getString("clientId"));
        if (clientId != null) {
            return clientId;
        }
        return resolveHuaweiPushAppId(credential);
    }

    private String resolveHuaweiOAuthClientSecret(JSONObject credential) {
        String clientSecret = StringUtils.trimToNull(credential.getString("clientSecret"));
        if (clientSecret != null) {
            return clientSecret;
        }
        return StringUtils.trimToNull(credential.getString("appSecret"));
    }

    /**
     * 向华为 OAuth 服务换取 access_token（用于 Authorization: Bearer）。
     * <p>格式转换 credential → form 参数：</p>
     * <pre>
     * 转换前 credential: { "clientId": "115095083", "clientSecret": "xxx" }
     * 转换后 POST body: grant_type=client_credentials&client_id=115095083&client_secret=xxx
     * 响应: { "access_token": "AT_xxx", "expires_in": 3600 }
     * </pre>
     */
    private String obtainHuaweiAccessToken(JSONObject credential) {
        String clientId = resolveHuaweiOAuthClientId(credential);
        String clientSecret = resolveHuaweiOAuthClientSecret(credential);
        String pushAppId = resolveHuaweiPushAppId(credential);
        if (StringUtils.isAnyBlank(clientId, clientSecret)) {
            log.warn("华为 OAuth 凭证不完整, 需配置 appId+appSecret 或 clientId+clientSecret");
            return null;
        }
        if (pushAppId != null && !StringUtils.equals(clientId, pushAppId)) {
            log.warn("华为 OAuth clientId({}) 与 push appId({}) 不一致，若鉴权失败请改为同一应用 ID",
                    clientId, pushAppId);
        }
        MultiValueMap<String, String> tokenForm = new LinkedMultiValueMap<>();
        tokenForm.add("grant_type", "client_credentials");
        tokenForm.add("client_id", clientId);
        tokenForm.add("client_secret", clientSecret);
        try {
            String tokenResp = postForm(HUAWEI_OAUTH_URL, tokenForm, null);
            JSONObject tokenJson = JSONObject.parseObject(tokenResp);
            if (tokenJson == null || StringUtils.isBlank(tokenJson.getString("access_token"))) {
                log.warn("华为 OAuth 鉴权失败, clientId={}, resp={}", clientId, tokenResp);
                return null;
            }
            return tokenJson.getString("access_token");
        } catch (Exception e) {
            log.error("华为 OAuth 请求异常, clientId={}, err={}", clientId, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 解析华为 Push API 响应；成功码为 80000000。
     * <p>格式转换：</p>
     * <pre>
     * 转换前 JSON: { "code":"80000000", "requestId":"178169515953238426090801" }
     * 转换后 CommonPushVendorSendResult: ok, vendorTaskId=requestId
     * </pre>
     */
    private CommonPushVendorSendResult parseHuaweiSendResult(String resp, String taskNo, int deviceCount) {
        JSONObject sendJson = JSONObject.parseObject(resp);
        if (sendJson != null && HUAWEI_SUCCESS_CODE.equals(sendJson.getString("code"))) {
            String requestId = sendJson.getString("requestId");
            log.info("华为推送成功, taskNo={}, deviceCount={}, requestId={}", taskNo, deviceCount, requestId);
            return CommonPushVendorSendResult.ok(requestId);
        }
        if (sendJson != null && "80300007".equals(sendJson.getString("code"))) {
            log.error("华为推送 token 无效(80300007), taskNo={}, deviceCount={}, requestId={}, 请检查 token 是否由当前华为应用上报、是否过期、是否误用其他厂商或测试 token",
                    taskNo, deviceCount, sendJson.getString("requestId"));
        }
        log.error("华为推送失败, taskNo={}, deviceCount={}, resp={}", taskNo, deviceCount, resp);
        return CommonPushVendorSendResult.fail();
    }

    private String obtainHonorAccessToken(JSONObject credential) {
        String appId = credential.getString("appId");
        String appSecret = credential.getString("appSecret");
        MultiValueMap<String, String> tokenForm = new LinkedMultiValueMap<>();
        tokenForm.add("grant_type", "client_credentials");
        tokenForm.add("client_id", appId);
        tokenForm.add("client_secret", appSecret);
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/x-www-form-urlencoded");
        String tokenResp = postForm(HONOR_AUTH_URL, tokenForm, headers);
        JSONObject tokenJson = JSONObject.parseObject(tokenResp);
        if (tokenJson == null || StringUtils.isBlank(tokenJson.getString("access_token"))) {
            log.warn("荣耀鉴权失败: {}", tokenResp);
            return null;
        }
        return tokenJson.getString("access_token");
    }

    /**
     * 华为/荣耀下行消息体：token 必须与 android 同级，放在 message 对象内。
     * <p>格式转换 pushTokens → 华为 API JSON body：</p>
     * <pre>
     * 转换前 List: ["HUAWEI_CN_IQAAAACy1la8..."]
     * 转换后 body:
     * {
     *   "message": {
     *     "android": { "notification": { "title":"...", "body":"...", "click_action": {"type":3} } },
     *     "token": ["IQAAAACy1la8..."]
     *   }
     * }
     * </pre>
     *
     * @see <a href="https://developer.huawei.com/consumer/en/doc/HMSCore-References/https-send-api-0000001050986197">Huawei Push send API</a>
     */
    private JSONObject buildHuaweiMessageBody(CommonPushTask task, List<String> pushTokens) {
        JSONObject message = new JSONObject();
        JSONObject android = new JSONObject();
        JSONObject notification = new JSONObject();
        JSONObject clickAction = new JSONObject();
        notification.put("title", task.getTitle());
        notification.put("body", task.getContent());
        clickAction.put("type", 3);
        notification.put("click_action", clickAction);
        android.put("notification", notification);
        // TODO 设置 android 通知参数
        android.put("category","IM");
        message.put("android", android);
        List<String> huaweiTokens = new ArrayList<>();
        for (String pushToken : pushTokens) {
            String extracted = extractHuaweiPushToken(pushToken);
            if (StringUtils.isNotBlank(extracted)) {
                huaweiTokens.add(extracted);
            }
        }
        message.put("token", new JSONArray(huaweiTokens));

        JSONObject body = new JSONObject();
        body.put("message", message);
        return body;
    }

    /** 华为 Push Kit token 以 IQAAA 开头；客户端可能带 HUAWEI_CN_ 前缀。 */
    private static final String HUAWEI_PUSH_TOKEN_MARKER = "IQAAA";

    /**
     * 剥离客户端上报的厂商前缀，得到华为 Push Kit 可用 token。
     * <p>格式转换：</p>
     * <pre>
     * 转换前: "HUAWEI_CN_IQAAAACy1la8AAC0PfgZ4PM9..."
     * 转换后: "IQAAAACy1la8AAC0PfgZ4PM9..."
     * </pre>
     */
    private String extractHuaweiPushToken(String rawToken) {
        if (StringUtils.isBlank(rawToken)) {
            return null;
        }
        String trimmed = rawToken.trim();
        if (trimmed.startsWith(HUAWEI_PUSH_TOKEN_MARKER)) {
            return trimmed;
        }
        int markerIndex = trimmed.indexOf(HUAWEI_PUSH_TOKEN_MARKER);
        if (markerIndex >= 0) {
            return trimmed.substring(markerIndex);
        }
        if(trimmed.contains("HUAWEI_CN_")){
            return trimmed.substring(trimmed.indexOf("HUAWEI_CN_") + 10);
        }
        return trimmed;
    }

    private boolean isLikelyHuaweiPushToken(String token) {
        String extracted = extractHuaweiPushToken(token);
        return StringUtils.isNotBlank(extracted) && extracted.startsWith(HUAWEI_PUSH_TOKEN_MARKER);
    }

    private String maskPushToken(String token) {
        if (StringUtils.isBlank(token)) {
            return "";
        }
        String t = token.trim();
        if (t.length() <= 12) {
            return t.substring(0, Math.min(4, t.length())) + "***";
        }
        return t.substring(0, 8) + "..." + t.substring(t.length() - 4);
    }

    /**
     * 过滤空白、去重并剥离 HUAWEI_CN_ 前缀，单批最多 1000 个 token。
     * <p>格式转换 life_user.device_id 列表 → 华为 API token 列表（见 {@link #extractHuaweiPushToken}）。</p>
     */
    private List<String> normalizeHuaweiPushTokens(List<String> deviceIds) {
        if (deviceIds == null || deviceIds.isEmpty()) {
            return Collections.emptyList();
        }
        List<String> tokens = new ArrayList<>();
        Set<String> exists = new HashSet<>();
        for (String deviceId : deviceIds) {
            if (StringUtils.isBlank(deviceId)) {
                continue;
            }
            String token = extractHuaweiPushToken(deviceId);
            if (StringUtils.isBlank(token)) {
                continue;
            }
            if (exists.add(token)) {
                tokens.add(token);
            }
        }
        if (tokens.size() > HUAWEI_BATCH_TOKEN_LIMIT) {
            return tokens.subList(0, HUAWEI_BATCH_TOKEN_LIMIT);
        }
        return tokens;
    }

    private List<String> extractDeviceIds(List<CommonPushTargetDto> targets) {
        if (targets == null || targets.isEmpty()) {
            return Collections.emptyList();
        }
        List<String> deviceIds = new ArrayList<>();
        Set<String> exists = new HashSet<>();
        for (CommonPushTargetDto target : targets) {
            if (target == null || StringUtils.isBlank(target.getDeviceId())) {
                continue;
            }
            String deviceId = target.getDeviceId().trim();
            if (exists.add(deviceId)) {
                deviceIds.add(deviceId);
            }
        }
        return deviceIds;
    }

    private List<List<String>> partitionList(List<String> list, int batchSize) {
        List<List<String>> partitions = new ArrayList<>();
        if (list == null || list.isEmpty() || batchSize <= 0) {
            return partitions;
        }
        for (int i = 0; i < list.size(); i += batchSize) {
            partitions.add(list.subList(i, Math.min(i + batchSize, list.size())));
        }
        return partitions;
    }

    private CommonPushVendorSendResult sendApns(JSONObject credential, CommonPushTask task, String deviceId) {
        if (!apnsPushClient.validateCredential(credential)) {
            log.warn("APNs 凭证不完整, deviceId={}", deviceId);
            return CommonPushVendorSendResult.fail();
        }
        boolean ok = apnsPushClient.send(credential, task, deviceId);
        return ok ? CommonPushVendorSendResult.ok(task.getTaskNo()) : CommonPushVendorSendResult.fail();
    }

    private JSONObject parseCredential(String credentialJson) {
        if (StringUtils.isBlank(credentialJson)) {
            return null;
        }
        try {
            return JSONObject.parseObject(credentialJson);
        } catch (Exception e) {
            log.warn("credential_json 解析失败: {}", e.getMessage());
            return null;
        }
    }

    private String buildPayload(CommonPushTask task) {
        JSONObject payload = new JSONObject();
        payload.put("title", task.getTitle());
        payload.put("content", task.getContent());
        if (StringUtils.isNotBlank(task.getJumpUrl())) {
            payload.put("jumpUrl", task.getJumpUrl());
        }
        return payload.toJSONString();
    }

    /**
     * 查询 OPPO 广播推送统计（需 message_id + task_id）。
     */
    public CommonPushChannelStatsDto queryOppoStatistics(JSONObject credential, String messageId, String taskId) {
        if (credential == null || StringUtils.isAnyBlank(messageId, taskId)) {
            return null;
        }
        try {
            String authToken = obtainOppoAuthToken(credential);
            if (StringUtils.isBlank(authToken)) {
                return null;
            }
            MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
            form.add("auth_token", authToken);
            form.add("message_id", messageId);
            form.add("task_id", taskId);
            String resp = postForm("https://api.push.oppomobile.com/server/v1/message/statistics", form, null);
            log.info("OPPO 统计查询响应: messageId={}, taskId={}, resp={}", messageId, taskId, resp);
            JSONObject json = JSONObject.parseObject(resp);
            if (json == null || json.getIntValue("code") != 0) {
                return null;
            }
            JSONObject data = json.getJSONObject("data");
            if (data == null) {
                return null;
            }
            CommonPushChannelStatsDto stats = new CommonPushChannelStatsDto();
            stats.setRealSend(longToString(data.getLong("sendCount")));
            stats.setRealDelivered(longToString(data.getLong("arriveCount")));
            stats.setShowSum(longToString(data.getLong("showCount")));
            stats.setClickSum(longToString(data.getLong("openCount")));
            return stats;
        } catch (Exception e) {
            log.error("OPPO 统计查询失败, messageId={}, taskId={}, err={}", messageId, taskId, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 查询 vivo 推送统计。
     */
    public CommonPushChannelStatsDto queryVivoStatistics(JSONObject credential, String taskId) {
        if (credential == null || StringUtils.isBlank(taskId)) {
            return null;
        }
        try {
            String authToken = obtainVivoAuthToken(credential);
            if (StringUtils.isBlank(authToken)) {
                return null;
            }
            Map<String, String> headers = new HashMap<>();
            headers.put("authToken", authToken);
            String url = "https://api-push.vivo.com.cn/report/getStatistics?taskIds=" + urlEncode(taskId);
            String resp = getJson(url, headers);
            log.info("vivo 统计查询响应: taskId={}, resp={}", taskId, resp);
            JSONObject json = JSONObject.parseObject(resp);
            if (json == null || json.getIntValue("result") != 0) {
                return null;
            }
            com.alibaba.fastjson.JSONArray statistics = json.getJSONArray("statistics");
            if (statistics == null || statistics.isEmpty()) {
                return null;
            }
            JSONObject item = statistics.getJSONObject(0);
            CommonPushChannelStatsDto stats = new CommonPushChannelStatsDto();
            stats.setRealSend(longToString(item.getLong("send")));
            stats.setRealDelivered(longToString(item.getLong("receive")));
            stats.setShowSum(longToString(item.getLong("display")));
            stats.setClickSum(longToString(item.getLong("click")));
            return stats;
        } catch (Exception e) {
            log.error("vivo 统计查询失败, taskId={}, err={}", taskId, e.getMessage(), e);
            return null;
        }
    }

    /**
     * 查询小米推送统计（msg_id）。
     */
    public CommonPushChannelStatsDto queryXiaomiStatistics(JSONObject credential, String msgId) {
        if (credential == null || StringUtils.isBlank(msgId)) {
            return null;
        }
        String appSecret = credential.getString("appSecret");
        if (StringUtils.isBlank(appSecret)) {
            return null;
        }
        try {
            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", "key=" + appSecret);
            String url = "https://api.xmpush.xiaomi.com/v1/trace/message/status?msg_id=" + urlEncode(msgId);
            String resp = getJson(url, headers);
            log.info("小米统计查询响应: msgId={}, resp={}", msgId, resp);
            JSONObject json = JSONObject.parseObject(resp);
            if (json == null || !"ok".equalsIgnoreCase(json.getString("result"))) {
                return null;
            }
            JSONObject data = json.getJSONObject("data");
            if (data == null) {
                return null;
            }
            JSONObject status = data.getJSONObject(msgId);
            if (status == null && !data.isEmpty()) {
                status = data.getJSONObject(data.keySet().iterator().next());
            }
            if (status == null) {
                return null;
            }
            CommonPushChannelStatsDto stats = new CommonPushChannelStatsDto();
            stats.setExpectedSend(longToString(status.getLong("resolved")));
            stats.setRealSend(longToString(status.getLong("msg_send")));
            stats.setRealDelivered(longToString(status.getLong("delivered")));
            stats.setShowSum(longToString(status.getLong("msg_display")));
            stats.setClickSum(longToString(status.getLong("click")));
            return stats;
        } catch (Exception e) {
            log.error("小米统计查询失败, msgId={}, err={}", msgId, e.getMessage(), e);
            return null;
        }
    }

    private String longToString(Long value) {
        return value == null ? null : String.valueOf(value);
    }

    private String getJson(String url, Map<String, String> headers) {
        RestTemplate client = buildRestTemplate();
        HttpHeaders httpHeaders = new HttpHeaders();
        if (headers != null) {
            headers.forEach(httpHeaders::set);
        }
        ResponseEntity<String> response = client.exchange(url, org.springframework.http.HttpMethod.GET,
                new HttpEntity<>(httpHeaders), String.class);
        return response.getBody();
    }

    private String postJson(String url, String body, Map<String, String> headers) {
        RestTemplate restTemplate = buildRestTemplate();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        if (headers != null) {
            headers.forEach(httpHeaders::set);
        }
        ResponseEntity<String> response = restTemplate.postForEntity(url, new HttpEntity<>(body, httpHeaders), String.class);
        return response.getBody();
    }

    private String postForm(String url, MultiValueMap<String, String> form, Map<String, String> headers) {
        RestTemplate restTemplate = buildRestTemplate();
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        if (headers != null) {
            headers.forEach(httpHeaders::set);
        }
        ResponseEntity<String> response = restTemplate.postForEntity(url, new HttpEntity<>(form, httpHeaders), String.class);
        return response.getBody();
    }

    private RestTemplate buildRestTemplate() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(TIMEOUT_MS);
        factory.setReadTimeout(TIMEOUT_MS);
        return new RestTemplate(factory);
    }

    private String md5Hex(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }
}
