package com.wecloud.im.ws.service.impl;

import com.wecloud.im.ws.cache.UserCache;
import com.wecloud.im.ws.model.ClientInfo;
import com.wecloud.im.ws.service.MangerChannelService;
import com.wecloud.rtc.service.RtcService;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

/**
 * @Description 维护netty用户channel对象
 * @Author hewei hwei1233@163.com
 * @Date 2019-12-03
 */
@Component
@Slf4j
public class MangerChannelServiceImpl implements MangerChannelService {

    @Autowired
    private RtcService rtcService;

//    private final static ThreadFactory NAMED_THREAD_FACTORY = new ThreadFactoryBuilder()
//            .setNamePrefix("rpcWrite-").build();
    /**
     * 远程调用ws下发数据线程池
     * 属于IO密集型任务
     * IO密集型任务线程并不是一直在执行任务，则应配置尽可能多的线程，如CPU核数*2
     * 某大厂设置策略：IO密集型时，大部分线程都阻塞，故需要多配置线程数： 公式：CPU核数/1-阻塞系数 阻塞系数：0.8-0.9
     * 比如8核CPU： 8/1-0.9 = 80 个线程数
     * <p>
     * 由于来自远程端调用下发数据  如果是群聊1000人群则调用1000次 为不要占用太多资源 需要排队下发
     * 经过并发测试  200并发1000人群消息  需要调用200x1000=20w次 考虑单机cpu性能还要顾及本机api业务 设置阻塞队列
     * 为避免过多占用本地io线程导致response慢，设置LinkedBlockingQueue数量多可以避免抢占， （队列数量需要测试调试到最优数量 ）
     * 最大线程数量不要设置太多 数量、优先级一定要比本地io线程低级
     *
     *
     * <p>
     * 后续优化待完善：消息发送投递至MQ， 消费方从MQ队列获取下发任务 本地队列不宜缓存太多(机器死机则会全丢失) 堆积的请求处理队列可能会耗费非常大的内存甚至死机
     */
//    private final static ExecutorService THREAD_POOL_RPC_WRITE_EXECUTOR = new ThreadPoolExecutor(
//            WsConstants.CPU_PROCESSORS * 100,
//            WsConstants.CPU_PROCESSORS * 100,
//            1L, TimeUnit.MILLISECONDS,
//            new LinkedBlockingQueue<Runnable>(), NAMED_THREAD_FACTORY, new ThreadPoolExecutor.CallerRunsPolicy());


    @Autowired
    private UserCache userCache;


    @Override
    public NioSocketChannel get(String appKey, String clientId) {

        return this.CHANNEL_MAP.get(appKey + clientId);
    }

    @Override
    public void online(String appKey, String clientId, NioSocketChannel channel) {

        // 断掉旧链接
        NioSocketChannel nioSocketChannel = get(appKey, clientId);
        if (null != nioSocketChannel) {
            log.info("put新连接关掉旧链接:" + appKey + "," + clientId + ",\nchannelId:" + nioSocketChannel.id().asLongText());
            nioSocketChannel.close();
        }

        this.CHANNEL_MAP.put(appKey + clientId, channel);

        String longChannelId = channel.id().asLongText();
        this.putClientsMap(appKey, clientId, longChannelId);
        this.putSessionInfoMap(longChannelId, channel);

        userCache.online(appKey, clientId, longChannelId);
    }

    void putSessionInfoMap(String longChannelId, NioSocketChannel channel) {
        ClientInfo clientInfo = new ClientInfo();
        clientInfo.setDeviceId("");
        clientInfo.setNioSocketChannel(channel);
        clientInfo.setToken("");
        this.SESSION_INFO_MAP.put(longChannelId, clientInfo);
    }

    void putClientsMap(String appKey, String clientId, String longChannelId) {
        Set<String> set = this.CLIENTS_MAP.get(appKey + ":" + clientId);
        if (set == null || set.isEmpty()) {
            HashSet<String> hashSet = new HashSet<>();
            hashSet.add(longChannelId);
            this.CLIENTS_MAP.put(appKey + ":" + clientId, hashSet);
        } else {
            set.add(longChannelId);
        }
    }

    void delSessionInfoMap(String longChannelId) {
        this.SESSION_INFO_MAP.remove(longChannelId);
    }

    void delClientsMap(String appKey, String clientId, String longChannelId) {
        Set<String> set = this.CLIENTS_MAP.get(appKey + ":" + clientId);
        if (set != null) {
            set.remove(longChannelId);
        }
    }

    @Override
    public void offline(ChannelHandlerContext channelHandlerContext) {

        String appKey = channelHandlerContext.channel().attr(MangerChannelService.APP_KEY).get();
        String clientId = channelHandlerContext.channel().attr(MangerChannelService.CLIENT_ID).get();

        String userIdByChannelString = this.getStringInfoByChannel(channelHandlerContext);

        String longChannelId = channelHandlerContext.channel().id().asLongText();
        log.info("uid:" + userIdByChannelString + "," + "handlerRemoved" + ",channelId:" + longChannelId);

        // 关掉连接
        channelHandlerContext.close();

        // rtc清空缓存
        rtcService.clientOffline(appKey, clientId);

        // 移除本地维护的channel
        delSessionInfoMap(longChannelId);
        delClientsMap(appKey, clientId, longChannelId);

        userCache.offline(appKey, clientId, longChannelId);

    }

    @Override
    public String getStringInfoByChannel(ChannelHandlerContext channelHandlerContext) {
        return "APP_KEY:" + channelHandlerContext.channel().attr(MangerChannelService.APP_KEY).get()
                + ",CLIENT_ID:" + channelHandlerContext.channel().attr(MangerChannelService.CLIENT_ID).get();
    }

//    @Override
//    public Boolean isOnLocal(Long userId) {
//        NioSocketChannel nioSocketChannel = this.get(String.valueOf(userId));
//        return null != nioSocketChannel;
//    }

    /**
     * 获取用户在线状态
     *
     * @param toAppKey
     * @param toClientId
     * @return true:在线, false 不在线
     */
    @Override
    public boolean getOnlineStatus(String toAppKey, String toClientId) {
        NioSocketChannel nioSocketChannel = get(toAppKey, toClientId);
        if (null == nioSocketChannel) {
            if (log.isDebugEnabled()) {
                log.info("writeData 不存在 连接为空:" + toAppKey + toClientId);
            }
            return false;
        }
        // 判断连接是否断开
        if (nioSocketChannel.isShutdown()) {
            if (log.isDebugEnabled()) {
                log.info("writeData连接断开:" + toAppKey + toClientId + "channelId:" + nioSocketChannel.id().asLongText());
            }
            return false;
        }
        return true;
    }


    @Override
    public boolean writeData(String msg, String toAppKey, String toClientId) {

        NioSocketChannel nioSocketChannel = get(toAppKey, toClientId);
        if (null == nioSocketChannel) {
                log.info("writeData连接为空:" + toAppKey + toClientId + "," + msg);
            return false;
        }
        // 判断连接是否断开
        if (nioSocketChannel.isShutdown()) {
                log.info("writeData连接断开:" + toAppKey + toClientId + "," + msg + ",\nchannelId:" + nioSocketChannel.id().asLongText());
            return false;
        }

        log.info("writeData:" + toAppKey + "," + toClientId + "," + msg + ",\nchannelId:" + nioSocketChannel.id().asLongText());

        ChannelFuture channelFuture = nioSocketChannel.writeAndFlush(new TextWebSocketFrame(msg));
        channelFuture.addListener(
                //执行后回调的方法
                (ChannelFutureListener) channelFuture1 -> {
                    if (log.isDebugEnabled()) {
                        log.info("netty线程异步执行结果:" + channelFuture1.isDone() + ",业务执行结果:" + channelFuture1.isSuccess()
                                + "；\nwriteData:" + toAppKey + toClientId + "," + msg + ",channelId:" + nioSocketChannel.id().asLongText());
                    }
                });
        return true;
    }

}
