package shop.alien.store.service.channel;

import com.alibaba.fastjson.JSONObject;
import com.eatthepath.pushy.apns.ApnsClient;
import com.eatthepath.pushy.apns.ApnsClientBuilder;
import com.eatthepath.pushy.apns.PushNotificationResponse;
import com.eatthepath.pushy.apns.auth.ApnsSigningKey;
import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification;
import com.eatthepath.pushy.apns.util.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import shop.alien.entity.store.CommonPushTask;
import shop.alien.store.dto.CommonPushTargetDto;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Apple APNs HTTP/2 推送客户端，支持 .p12 证书与 .p8 Token 两种鉴权。
 */
@Slf4j
@Component
public class ApnsPushClient {

    private static final int SEND_TIMEOUT_SECONDS = 10;

    public boolean send(JSONObject credential, CommonPushTask task, String deviceId) {
        if (credential == null || task == null || StringUtils.isBlank(deviceId)) {
            return false;
        }
        String token = normalizeDeviceToken(deviceId);
        if (StringUtils.isBlank(token)) {
            return false;
        }
        ApnsClient client = null;
        try {
            client = buildClient(credential);
            if (client == null) {
                return false;
            }
            String topic = resolveTopic(credential);
            SimpleApnsPushNotification notification = new SimpleApnsPushNotification(
                    token, topic, buildPayload(task));
            PushNotificationResponse<SimpleApnsPushNotification> response =
                    client.sendNotification(notification).get(SEND_TIMEOUT_SECONDS, TimeUnit.SECONDS);
            if (response.isAccepted()) {
                return true;
            }
            log.error("APNs 推送被拒, taskNo={}, deviceId={}, reason={}",
                    task.getTaskNo(), maskToken(token), response.getRejectionReason());
            return false;
        } catch (Exception e) {
            log.error("APNs 推送异常, taskNo={}, deviceId={}, err={}",
                    task.getTaskNo(), maskToken(deviceId), e.getMessage(), e);
            return false;
        } finally {
            closeClient(client);
        }
    }

    public boolean sendBroadcast(JSONObject credential, CommonPushTask task, List<CommonPushTargetDto> targets) {
        if (credential == null || task == null || targets == null || targets.isEmpty()) {
            return false;
        }
        ApnsClient client = null;
        int successCount = 0;
        int totalCount = 0;
        try {
            client = buildClient(credential);
            if (client == null) {
                return false;
            }
            String topic = resolveTopic(credential);
            String payload = buildPayload(task);
            for (CommonPushTargetDto target : targets) {
                if (target == null || StringUtils.isBlank(target.getDeviceId())) {
                    continue;
                }
                String token = normalizeDeviceToken(target.getDeviceId());
                if (StringUtils.isBlank(token)) {
                    continue;
                }
                totalCount++;
                try {
                    SimpleApnsPushNotification notification = new SimpleApnsPushNotification(token, topic, payload);
                    PushNotificationResponse<SimpleApnsPushNotification> response =
                            client.sendNotification(notification).get(SEND_TIMEOUT_SECONDS, TimeUnit.SECONDS);
                    if (response.isAccepted()) {
                        successCount++;
                    } else {
                        log.warn("APNs 全量推送被拒, taskNo={}, deviceId={}, reason={}",
                                task.getTaskNo(), maskToken(token), response.getRejectionReason());
                    }
                } catch (Exception e) {
                    log.warn("APNs 全量单条失败, taskNo={}, deviceId={}, err={}",
                            task.getTaskNo(), maskToken(token), e.getMessage());
                }
            }
            log.info("APNs 全量推送完成, taskNo={}, success={}/{}", task.getTaskNo(), successCount, totalCount);
            return successCount > 0;
        } catch (Exception e) {
            log.error("APNs 全量推送异常, taskNo={}, err={}", task.getTaskNo(), e.getMessage(), e);
            return false;
        } finally {
            closeClient(client);
        }
    }

    public boolean validateCredential(JSONObject credential) {
        if (credential == null) {
            return false;
        }
        if (StringUtils.isBlank(resolveTopic(credential))) {
            return false;
        }
        return hasP12Credential(credential) || hasTokenCredential(credential);
    }

    private ApnsClient buildClient(JSONObject credential) throws IOException, GeneralSecurityException {
        String apnsHost = isProduction(credential)
                ? ApnsClientBuilder.PRODUCTION_APNS_HOST
                : ApnsClientBuilder.DEVELOPMENT_APNS_HOST;
        if (hasP12Credential(credential)) {
            byte[] p12Bytes = decodeBase64(credential.getString("p12Base64"));
            if (p12Bytes == null) {
                log.error("APNs p12Base64 解码失败");
                return null;
            }
            String password = StringUtils.defaultString(credential.getString("p12Password"));
            try (InputStream inputStream = new ByteArrayInputStream(p12Bytes)) {
                return new ApnsClientBuilder()
                        .setApnsServer(apnsHost)
                        .setClientCredentials(inputStream, password)
                        .build();
            }
        }
        if (hasTokenCredential(credential)) {
            String pem = normalizePrivateKeyPem(credential.getString("privateKey"));
            if (StringUtils.isBlank(pem)) {
                log.error("APNs privateKey 为空或格式无法识别");
                return null;
            }
            try {
                ApnsSigningKey signingKey = ApnsSigningKey.loadFromInputStream(
                        new ByteArrayInputStream(pem.getBytes(StandardCharsets.UTF_8)),
                        credential.getString("teamId"),
                        credential.getString("keyId"));
                return new ApnsClientBuilder()
                        .setApnsServer(apnsHost)
                        .setSigningKey(signingKey)
                        .build();
            } catch (Exception e) {
                log.error("APNs privateKey 加载失败, teamId={}, keyId={}, err={}",
                        credential.getString("teamId"), credential.getString("keyId"), e.getMessage(), e);
                return null;
            }
        }
        log.error("APNs 凭证不完整，需配置 p12Base64+p12Password（证书方式）或 teamId+keyId+privateKey（Token 方式）");
        return null;
    }

    private boolean hasP12Credential(JSONObject credential) {
        return StringUtils.isNotBlank(credential.getString("p12Base64"));
    }

    private boolean hasTokenCredential(JSONObject credential) {
        return StringUtils.isNoneBlank(
                credential.getString("teamId"),
                credential.getString("keyId"),
                credential.getString("privateKey"));
    }

    private String resolveTopic(JSONObject credential) {
        return StringUtils.trimToNull(credential.getString("bundleId"));
    }

    private boolean isProduction(JSONObject credential) {
        if (credential.containsKey("production")) {
            return credential.getBooleanValue("production");
        }
        return true;
    }

    private String buildPayload(CommonPushTask task) {
        JSONObject alert = new JSONObject();
        alert.put("title", task.getTitle());
        alert.put("body", task.getContent());

        JSONObject aps = new JSONObject();
        aps.put("alert", alert);
        aps.put("sound", "default");

        JSONObject root = new JSONObject();
        root.put("aps", aps);
        if (StringUtils.isNotBlank(task.getJumpUrl())) {
            root.put("jumpUrl", task.getJumpUrl());
        }
        if (task.getId() != null) {
            root.put("p", task.getId());
        } else if (StringUtils.isNotBlank(task.getTaskNo())) {
            root.put("t", task.getTaskNo());
        }
        return root.toJSONString();
    }

    private String normalizeDeviceToken(String deviceId) {
        if (StringUtils.isBlank(deviceId)) {
            return null;
        }
        try {
            return TokenUtil.sanitizeTokenString(deviceId.trim());
        } catch (Exception e) {
            log.warn("APNs deviceToken 非法: {}", maskToken(deviceId));
            return null;
        }
    }

    private byte[] decodeBase64(String value) {
        if (StringUtils.isBlank(value)) {
            return null;
        }
        try {
            return Base64.getDecoder().decode(value.replaceAll("\\s", ""));
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

    /**
     * 将 credential_json 中的 privateKey 规范为 PEM 多行格式，供 {@link ApnsSigningKey#loadFromInputStream} 使用。
     * <p>支持：JSON 中 {@code \\n} 转义、整行无换行、标准多行 PEM。</p>
     * <p>格式转换示例（单行 → 多行）：</p>
     * <pre>
     * 转换前: -----BEGIN PRIVATE KEY-----MIGTAgEA...PCZ6m-----END PRIVATE KEY-----
     * 转换后:
     * -----BEGIN PRIVATE KEY-----
     * MIGTAgEA...（每 64 字符换行）
     * ...PCZ6m
     * -----END PRIVATE KEY-----
     * </pre>
     */
    private String normalizePrivateKeyPem(String rawKey) {
        if (StringUtils.isBlank(rawKey)) {
            return null;
        }
        String key = rawKey.trim()
                .replace("\\r", "")
                .replace("\\n", "\n");
        if (key.contains("\n")) {
            return key.endsWith("\n") ? key : key + "\n";
        }
        if (key.contains(PKCS8_BEGIN)) {
            return reformatSingleLinePem(key, PKCS8_BEGIN, PKCS8_END);
        }
        if (key.contains(EC_PKCS8_BEGIN)) {
            return reformatSingleLinePem(key, EC_PKCS8_BEGIN, EC_PKCS8_END);
        }
        log.warn("APNs privateKey 缺少 PEM 头尾标记 BEGIN/END PRIVATE KEY");
        return key;
    }

    private static final String PKCS8_BEGIN = "-----BEGIN PRIVATE KEY-----";
    private static final String PKCS8_END = "-----END PRIVATE KEY-----";
    private static final String EC_PKCS8_BEGIN = "-----BEGIN EC PRIVATE KEY-----";
    private static final String EC_PKCS8_END = "-----END EC PRIVATE KEY-----";

    private String reformatSingleLinePem(String singleLine, String beginMarker, String endMarker) {
        int beginIdx = singleLine.indexOf(beginMarker);
        int endIdx = singleLine.indexOf(endMarker);
        if (beginIdx < 0 || endIdx <= beginIdx) {
            return singleLine;
        }
        String base64Body = singleLine.substring(beginIdx + beginMarker.length(), endIdx)
                .replaceAll("\\s", "");
        if (StringUtils.isBlank(base64Body)) {
            log.warn("APNs privateKey PEM 主体为空");
            return singleLine;
        }
        StringBuilder pem = new StringBuilder();
        pem.append(beginMarker).append('\n');
        for (int offset = 0; offset < base64Body.length(); offset += 64) {
            pem.append(base64Body, offset, Math.min(offset + 64, base64Body.length())).append('\n');
        }
        pem.append(endMarker).append('\n');
        return pem.toString();
    }

    private void closeClient(ApnsClient client) {
        if (client == null) {
            return;
        }
        try {
            client.close().get(5, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.warn("关闭 APNs 客户端异常: {}", e.getMessage());
        }
    }

    private String maskToken(String token) {
        if (StringUtils.isBlank(token) || token.length() <= 8) {
            return "****";
        }
        return token.substring(0, 4) + "****" + token.substring(token.length() - 4);
    }

    public static List<CommonPushTargetDto> filterApnsTargets(List<CommonPushTargetDto> channelTargets,
                                                               List<CommonPushTargetDto> allTargets) {
        if (channelTargets != null && !channelTargets.isEmpty()) {
            return channelTargets;
        }
        List<CommonPushTargetDto> result = new ArrayList<>();
        if (allTargets == null) {
            return result;
        }
        for (CommonPushTargetDto target : allTargets) {
            if (target == null) {
                continue;
            }
            if (isIosPlatform(target.getPlatform()) || isLikelyApnsToken(target.getDeviceId())) {
                result.add(target);
            }
        }
        return result;
    }

    private static boolean isIosPlatform(String platform) {
        if (StringUtils.isBlank(platform)) {
            return false;
        }
        String normalized = StringUtils.lowerCase(platform.trim());
        return normalized.contains("ios") || "iphone".equals(normalized);
    }

    static boolean isLikelyApnsToken(String deviceId) {
        if (StringUtils.isBlank(deviceId)) {
            return false;
        }
        String normalized = deviceId.trim().replace(" ", "").replace("<", "").replace(">", "");
        return normalized.matches("^[a-fA-F0-9]+$") && normalized.length() >= 64;
    }
}
