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

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.ApnsPayloadBuilder;
import com.eatthepath.pushy.apns.util.SimpleApnsPayloadBuilder;
import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification;
import com.eatthepath.pushy.apns.util.TokenUtil;
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 jakarta.annotation.PreDestroy;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Setter
public class ANPsPushNotification implements IOSPushNotification {
    /**
     * 缓存发送结果<br/>
     * 保证消息不会因为重试的原因重复发送
     */
    private static final Cache<Long, SendResult> cache = CacheBuilder
            .newBuilder()
            .initialCapacity(1000)
            .expireAfterWrite(60L, TimeUnit.SECONDS) // 设定写入过期时间
            .build();

    private static final Map<String, ApnsClient> apnsClientCache = new ConcurrentHashMap<>();
    private final ClassLoader classLoader = ANPsPushNotification.class.getClassLoader();

    /**
     * IOS default max retry number
     * <br/>
     * <dl>
     *     <dt>ERROR: </dt>
     *     <dd>io.netty.channel.AbstractChannel$AnnotatedNoRouteToHostException: No route to host: api.sandbox.push.apple.com/[2620:149:149:1022:0:0:0:15]:443 <br/></dd>
     * </dl>
     * APNs 是已知不定时的会提示错误，所以这里设置默认重试次数为5次
     */
    private static final Integer DEFAULT_MAX_RETRY = 5;
    private PushConfig pushConfig;

    /*public ApnsClient authenticate(PushConfig pushConfig) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
        setPushConfig(pushConfig);

        ClassLoader classLoader = ANPsPushNotification.class.getClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream(pushConfig.getIos().getFilename());
        return new ApnsClientBuilder()
                .setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
                .setSigningKey(
                        ApnsSigningKey.loadFromInputStream(
                                inputStream,
                                pushConfig.getIos().getTeamId(),
                                pushConfig.getIos().getKeyId()
                        )
                ).build();
    }*/

    public ApnsClient authenticate(PushConfig pushConfig) {
        setPushConfig(pushConfig);

        String keyId = pushConfig.getIos().getKeyId();
        String teamId = pushConfig.getIos().getTeamId();
        String filename = pushConfig.getIos().getFilename();

        return apnsClientCache.computeIfAbsent(keyId, k -> {
            try (InputStream is = classLoader.getResourceAsStream(filename)) {
                return new ApnsClientBuilder()
                        .setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
                        .setSigningKey(ApnsSigningKey.loadFromInputStream(is, teamId, keyId))
                        .setConnectionTimeout(Duration.ofSeconds(60))  // 延长连接超时
                        .build();
            } catch (Exception e) {
                throw new RuntimeException("APNs client init failed", e);
            }
        });
    }

    @PreDestroy
    public void destroy() {
        apnsClientCache.values().forEach(client -> {
            try {
                client.close();
            } catch (Exception e) {
                log.error("APNs client close failed", e);
            }
        });
        apnsClientCache.clear();
    }

    @Override
    public SendResult send(NotificationMessageTemplate message, ApnsClient apnsClient) throws ExecutionException {
        return cache.get(message.getMsgId(), () -> {
            ApnsPayloadBuilder payloadBuilder = new SimpleApnsPayloadBuilder()
                    .setAlertTitle(message.getTitle())
                    .setAlertBody(message.getBody())
                    .setSound("default");//设置为默认，会使用系统声音和震动

            SimpleApnsPushNotification notification = new SimpleApnsPushNotification(
                    TokenUtil.sanitizeTokenString(message.getRegId()),
                    pushConfig.getIos().getPackageName(),
                    payloadBuilder.build());

            PushNotificationResponse<SimpleApnsPushNotification> response;
            AtomicInteger retryNum = new AtomicInteger();
            int maxRetry = Optional.of(pushConfig.getIos().getMaxRetry()).orElse(DEFAULT_MAX_RETRY);
            while (retryNum.get() < maxRetry) {
                try {
                    response = proceed(apnsClient, notification);
                    if (response.isAccepted()) {
                      //  log.info("Send push notification successful. Retry number: {}", retryNum.get());
                        break;
                    }
                   // log.error("Failed to send push notification. Code: {}, Reason: {}", response.getStatusCode(), response.getRejectionReason().orElse("ERROR"));
                } catch (ExecutionException e) {
                   // log.warn("Failed to send push notification. {}", e.getMessage());
                } catch (Exception e) {
                   log.error("[IOS推送]Failed to send push notification.", e);
                }
                retryNum.getAndIncrement();
            }

            // The number of retry words has reached the limit
            if (retryNum.get() == maxRetry) {
             //   log.error("Failed to send push notification. Retry number: {}", retryNum.get());
                return new SendResult()
                        .setSuccessful(false)
                        .setRetryNum(retryNum.get());
            }
            return new SendResult()
                    .setSuccessful(true)
                    .setRetryNum(retryNum.get());
        });
    }

    private PushNotificationResponse<SimpleApnsPushNotification> proceed(ApnsClient apnsClient,
                                                                         SimpleApnsPushNotification notification)
            throws ExecutionException, InterruptedException, TimeoutException {
        return apnsClient.sendNotification(notification).get(10, TimeUnit.SECONDS);
    }
}


