package com.wecloud.im.ws.sender;

import com.turo.pushy.apns.ApnsClient;
import com.turo.pushy.apns.ApnsClientBuilder;
import com.turo.pushy.apns.DeliveryPriority;
import com.turo.pushy.apns.PushNotificationResponse;
import com.turo.pushy.apns.PushType;
import com.turo.pushy.apns.util.ApnsPayloadBuilder;
import com.turo.pushy.apns.util.SimpleApnsPushNotification;
import com.turo.pushy.apns.util.TokenUtil;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.io.InputStream;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @Description 基于APNs最新HTTP/2接口实现iOS的高性能消息推送pushy(服务端篇)
 * 通过Semaphore来进行流控，防止缓存过大，内存不足；
 * 通过CountDownLatch来标记消息是否发送完成；
 * 使用AtomicLong完成匿名内部类operationComplete方法中的计数；
 * 使用Netty的Future对象进行消息推送结果的判断。
 */
@Slf4j
public class IosPush {

    private final static EventLoopGroup EVENT_LOOP_GROUP = new NioEventLoopGroup(4);

    /**
     * Semaphore又称信号量，是操作系统中的一个概念，在Java并发编程中，信号量控制的是线程并发的数量。
     */
    private static final Semaphore SEMAPHORE = new Semaphore(10000);


//    private static ApnsClient apnsClient = null;
//
//    public static ApnsClient getLocalAPNSConnect() {
//        if (apnsClient == null) {
//            try {
//                EventLoopGroup eventLoopGroup = new NioEventLoopGroup(4);
//                apnsClient = new ApnsClientBuilder().setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
//                        .setClientCredentials(new File("G:/java/push_file/xt_aillo_test_apns.p12"), "123456")
//                        .setConcurrentConnections(4).setEventLoopGroup(eventLoopGroup).build();
//            } catch (Exception e) {
//                logger.error("ios get pushy apns client failed!");
//                e.printStackTrace();
//            }
//        }
//        return apnsClient;
//    }


//    public static void push(String apnsCertificatePath, Boolean productFlag, final String deviceToken, String alertTitle, String alertBody,
//                            int badge, String topicBundleId,
//                            String sound) {
//    }

    /**
     * @param inputStream      证书输入流
     * @param productFlag      环境,测试=Boolean.FALSE,正式=Boolean.TRUE
     * @param deviceToken      设备token
     * @param alertTitle       标题
     * @param alertBody        副标题
     * @param contentAvailable Boolean.FALSE
     * @param customProperty   自定义属性
     * @param badge            角标数量
     * @param priority         DeliveryPriority.IMMEDIATE
     * @param pushType         PushType.ALERT
     * @param topicBundleId    undleId
     * @param sound            rtc= "call.caf"; 否则为default
     */
    public static void push(String certificatePassword, InputStream inputStream, Boolean productFlag, final String deviceToken, String alertTitle, String alertBody,
                            boolean contentAvailable, Map<String, Object> customProperty,
                            int badge, DeliveryPriority priority, PushType pushType, String topicBundleId,
                            String sound) {

        if (deviceToken == null || "".equals(deviceToken)) {
            log.error("deviceToken=null");
            return;
        }

        long startTime = System.currentTimeMillis();

        //每次完成一个任务（不一定需要线程走完），latch减1，直到所有的任务都完成，就可以执行下一阶段任务，可提高性能
        final CountDownLatch latch = new CountDownLatch(1);
        //线程安全的计数器
        final AtomicLong successCnt = new AtomicLong(0);
        ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder();
        if (alertTitle != null) {
            payloadBuilder.setAlertTitle(alertTitle);
        }
        if (alertBody != null) {
            payloadBuilder.setAlertBody(alertBody);
        }
        payloadBuilder.setMutableContent(true);
        //如果badge小于0，则不推送这个右上角的角标，主要用于消息盒子新增或者已读时，更新此状态
        if (badge > 0) {
            payloadBuilder.setBadgeNumber(badge);
        }
        if (!StringUtils.isEmpty(sound)) {
            payloadBuilder.setSound(sound);
        }
        //将所有的附加参数全部放进去
        if (customProperty != null) {
            for (Map.Entry<String, Object> map : customProperty.entrySet()) {
                payloadBuilder.addCustomProperty(map.getKey(), map.getValue());
            }
        }
        payloadBuilder.setContentAvailable(contentAvailable);
        String payload = payloadBuilder.buildWithDefaultMaximumLength();
        final String token = TokenUtil.sanitizeTokenString(deviceToken);
        SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(token, topicBundleId, payload, new Date(), priority, pushType);

        try {
            //从信号量中获取一个允许机会
            SEMAPHORE.acquire();
        } catch (Exception e) {
            //线程太多了，没有多余的信号量可以获取了
            log.error("ios push get semaphore failed, deviceToken:{}", deviceToken);
            log.error("ios push get semaphore failed", e);
        }

        log.debug("token={},payload={}", token, payload);
        ApnsClient apnsClient = getAPNSConnect(inputStream, productFlag, certificatePassword);
        final Future<PushNotificationResponse<SimpleApnsPushNotification>> future = apnsClient.sendNotification(pushNotification);
        future.addListener((GenericFutureListener<Future<PushNotificationResponse>>) pushNotificationResponseFuture -> {
            if (future.isSuccess()) {
                final PushNotificationResponse<SimpleApnsPushNotification> response = future.getNow();
                if (response.isAccepted()) {
                    log.debug("success token{}", token);
                    successCnt.incrementAndGet();
                } else {
                    Date invalidTime = response.getTokenInvalidationTimestamp();
                    log.error("Notification rejected by the APNs gateway: " + response.getRejectionReason() + ",payload:" + payload +
                                    "\n,deviceToken:" + deviceToken
                            , ",alertBody:" + alertBody + ",sanitizeTokenString:" + token);
                    if (invalidTime != null) {
                        log.error("\t…and the token is invalid as of " + response.getTokenInvalidationTimestamp());
                    }
                }
            } else {
                log.error("send notification device token={} is failed {} ", token, future.cause().getMessage());
            }
            latch.countDown();
            //释放允许，将占有的信号量归还
            SEMAPHORE.release();
        });
        try {
            latch.await(20, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error("ios push latch await failed!", e);
        }
        long endPushTime = System.currentTimeMillis();
        log.debug("pushMessage success. [成功{}个,耗时{}ms]", successCnt.get(), endPushTime - startTime);
    }


    /**
     * @param apnsCertificatePath 证书
     * @param productFlag         环境
     * @param deviceToken         设备token
     * @param alertTitle          标题
     * @param alertBody           副标题
     * @param contentAvailable    Boolean.FALSE
     * @param customProperty      自定义属性
     * @param badge               角标数量
     * @param priority            DeliveryPriority.IMMEDIATE
     * @param pushType            PushType.ALERT
     * @param topicBundleId       undleId
     * @param sound               rtc= "call.caf"; 否则为default
     */
    public static void push(String apnsCertificatePath, Boolean productFlag, final String deviceToken, String alertTitle, String alertBody,
                            boolean contentAvailable, Map<String, Object> customProperty,
                            int badge, DeliveryPriority priority, PushType pushType, String topicBundleId,
                            String sound) {

        String certificatePassword = "123456";

        if (deviceToken == null || "".equals(deviceToken)) {
            log.error("deviceToken=null");
            return;
        }

        long startTime = System.currentTimeMillis();

        //每次完成一个任务（不一定需要线程走完），latch减1，直到所有的任务都完成，就可以执行下一阶段任务，可提高性能
        final CountDownLatch latch = new CountDownLatch(1);
        //线程安全的计数器
        final AtomicLong successCnt = new AtomicLong(0);
        ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder();
        if (alertTitle != null) {
            payloadBuilder.setAlertTitle(alertTitle);
        }
        if (alertBody != null) {
            payloadBuilder.setAlertBody(alertBody);
        }
        payloadBuilder.setMutableContent(true);
        //如果badge小于0，则不推送这个右上角的角标，主要用于消息盒子新增或者已读时，更新此状态
        if (badge > 0) {
            payloadBuilder.setBadgeNumber(badge);
        }
        if (!StringUtils.isEmpty(sound)) {
            payloadBuilder.setSound(sound);
        }
        //将所有的附加参数全部放进去
        if (customProperty != null) {
            for (Map.Entry<String, Object> map : customProperty.entrySet()) {
                payloadBuilder.addCustomProperty(map.getKey(), map.getValue());
            }
        }
        payloadBuilder.setContentAvailable(contentAvailable);
        String payload = payloadBuilder.buildWithDefaultMaximumLength();
        final String token = TokenUtil.sanitizeTokenString(deviceToken);
        SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(token, topicBundleId, payload, new Date(), priority, pushType);

        try {
            //从信号量中获取一个允许机会
            SEMAPHORE.acquire();
        } catch (Exception e) {
            //线程太多了，没有多余的信号量可以获取了
            log.error("ios push get semaphore failed, deviceToken:{}", deviceToken);
            log.error("ios push get semaphore failed", e);
        }

        log.debug("token={},payload={}", token, payload);
        ApnsClient apnsClient = getAPNSConnect(apnsCertificatePath, productFlag, certificatePassword);
        final Future<PushNotificationResponse<SimpleApnsPushNotification>> future = apnsClient.sendNotification(pushNotification);
        future.addListener((GenericFutureListener<Future<PushNotificationResponse>>) pushNotificationResponseFuture -> {
            if (future.isSuccess()) {
                final PushNotificationResponse<SimpleApnsPushNotification> response = future.getNow();
                if (response.isAccepted()) {
                    log.debug("success token{}", token);
                    successCnt.incrementAndGet();
                } else {
                    Date invalidTime = response.getTokenInvalidationTimestamp();
                    log.error("Notification rejected by the APNs gateway: " + response.getRejectionReason() + ",payload:" + payload +
                                    "\n,deviceToken:" + deviceToken
                            , ",alertBody:" + alertBody + ",sanitizeTokenString:" + token);
                    if (invalidTime != null) {
                        log.error("\t…and the token is invalid as of " + response.getTokenInvalidationTimestamp());
                    }
                }
            } else {
                log.error("send notification device token={} is failed {} ", token, future.cause().getMessage());
            }
            latch.countDown();
            //释放允许，将占有的信号量归还
            SEMAPHORE.release();
        });
        try {
            latch.await(20, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error("ios push latch await failed!", e);
        }
        long endPushTime = System.currentTimeMillis();
        log.debug("pushMessage success. [成功{}个,耗时{}ms]", successCnt.get(), endPushTime - startTime);
    }

//    public static void main(String[] args) {
//        Map<String, Object> customProperty = new HashMap<String, Object>(5);
//        //alert
//        push(getLocalAPNSConnect(), "edb79e259a9523894da68328bca050c4e22f8188e45bcd6f15a5498cf1f0ab12", "123", "789",
//                false, customProperty, 0, DeliveryPriority.IMMEDIATE, PushType.ALERT, "com.xteng.ailloTest", null);
//        // 静默
//        push(getLocalAPNSConnect(), "edb79e259a9523894da68328bca050c4e22f8188e45bcd6f15a5498cf1f0ab12", null, null,
//                true, customProperty, 0, DeliveryPriority.CONSERVE_POWER, PushType.BACKGROUND, "com.xteng.ailloTest", null);
//    }


    /**
     * 获取apns推送连接
     *
     * @param inputStream         证书文件
     * @param productFlag         环境:  true=正式, false测试
     * @param certificatePassword ios证书通用密码
     * @return
     */
    public static ApnsClient getAPNSConnect(InputStream inputStream, Boolean productFlag, String certificatePassword) {
        ApnsClient apnsXTClient = null;
        try {
            String environmentHost = productFlag ? ApnsClientBuilder.PRODUCTION_APNS_HOST : ApnsClientBuilder.DEVELOPMENT_APNS_HOST;
            apnsXTClient = new ApnsClientBuilder().setApnsServer(environmentHost)
                    .setClientCredentials(inputStream, certificatePassword)
                    .setConcurrentConnections(4).setEventLoopGroup(EVENT_LOOP_GROUP).build();
        } catch (Exception e) {
            log.error("ios get push apns client failed!", e);
        }
        return apnsXTClient;
    }

    /**
     * 获取apns推送连接
     *
     * @param apnsCertificatePath 证书文件
     * @param productFlag         环境:  true=正式, false测试
     * @param certificatePassword ios证书通用密码
     * @return
     */
    public static ApnsClient getAPNSConnect(String apnsCertificatePath, Boolean productFlag, String certificatePassword) {
        ApnsClient apnsXTClient = null;
        try {
            String environmentHost = productFlag ? ApnsClientBuilder.PRODUCTION_APNS_HOST : ApnsClientBuilder.DEVELOPMENT_APNS_HOST;
            InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream(apnsCertificatePath);
            apnsXTClient = new ApnsClientBuilder().setApnsServer(environmentHost)
                    .setClientCredentials(resourceAsStream, certificatePassword)
                    .setConcurrentConnections(4).setEventLoopGroup(EVENT_LOOP_GROUP).build();
        } catch (Exception e) {
            log.error("ios get push apns client failed!", e);
        }
        return apnsXTClient;
    }

    public static InputStream getApnsCertificate(String apnsCertificatePath) {
        return ClassLoader.getSystemClassLoader().getResourceAsStream(apnsCertificatePath);
    }


}
