package com.ruoyi.framework.webSocket;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.system.domain.CommunityChatMsg;
import com.ruoyi.system.service.ICommunityChatMsgService;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;

/**
 * websocket 消息处理
 *
 * @author stronger
 */
@Component
@ServerEndpoint("/websocket/message")
public class WebSocketServer {
    /*========================声明类变量，意在所有实例共享=================================================*/
    /**
     * WebSocketServer 日志控制器
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);

    /**
     * 默认最多允许同时在线人数100
     */
    public static int socketMaxOnlineCount = 100;

    private static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount);

    HashedWheelTimer timer = new HashedWheelTimer(1, TimeUnit.SECONDS, 8);
    /**
     * concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。
     */
    private static final CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
    /**
     * 连接数
     */
    private static final AtomicInteger count = new AtomicInteger();

    /*========================声明实例变量，意在每个实例独享=======================================================*/
    /**
     * 与某个客户端的连接会话，需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 用户id
     */
    private String sid = "";

    public static ICommunityChatMsgService chatMsgService;

    @Autowired
    public void setChatMsgService(ICommunityChatMsgService chatMsgService) {
        WebSocketServer.chatMsgService = chatMsgService;
    }

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) throws Exception {
        // 尝试获取信号量
        boolean semaphoreFlag = SemaphoreUtils.tryAcquire(socketSemaphore);
        if (!semaphoreFlag) {
            // 未获取到信号量
            LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount);
            // 给当前Session 登录用户发送消息
            WebSocketUsers.sendMessageToUserByText(session, "当前在线人数超过限制数：" + socketMaxOnlineCount);
            session.close();
        } else {
            // 解析查询参数
            Map<String, String> queryParams = getQueryParams(session.getRequestURI().getQuery());
            String type = queryParams.get("type");
            // 返回此会话的经过身份验证的用户，如果此会话没有经过身份验证的用户,则返回null
            Authentication authentication = (Authentication) session.getUserPrincipal();
            SecurityUtils.setAuthentication(authentication);
            Long userId = SecurityUtils.getUserId();
            this.session = session;
            String sidStr = type + "_" + userId;
            //如果存在就先删除一个，防止重复推送消息
            for (WebSocketServer webSocket : webSocketSet) {
                if (webSocket.sid.equals(sidStr)) {
                    webSocketSet.remove(webSocket);
                    count.getAndDecrement();
                }
            }
            count.getAndIncrement();
            webSocketSet.add(this);
            this.sid = sidStr;
            LOGGER.info("\n 当前人数 - {}", count);
            WebSocketUsers.sendMessageToUserByText(session, "连接成功");
        }
    }

    /**
     * 连接关闭时处理
     */
    @OnClose
    public void onClose(Session session) {
        LOGGER.info("\n 关闭连接 - {}", session);
        // 移除用户
        webSocketSet.remove(session);
        // 获取到信号量则需释放
        SemaphoreUtils.release(socketSemaphore);
    }

    /**
     * 抛出异常时处理
     */
    @OnError
    public void onError(Session session, Throwable exception) throws Exception {
        if (session.isOpen()) {
            // 关闭连接
            session.close();
        }
        String sessionId = session.getId();
        LOGGER.info("\n 连接异常 - {}", sessionId);
        LOGGER.info("\n 异常信息 - {}", exception);
        // 移出用户
        webSocketSet.remove(session);
        // 获取到信号量则需释放
        SemaphoreUtils.release(socketSemaphore);
    }

    /**
     * 服务器接收到客户端消息时调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        Authentication authentication = (Authentication) session.getUserPrincipal();
        LOGGER.info("收到来自" + sid + "的信息:" + message);
        // 实时更新
        this.refresh(sid, authentication, message);
        WebSocketUsers.sendMessageToUserByText(session, "成功发送一条消息");
    }

    /**
     * 刷新定时任务，发送信息
     */
    private void refresh(String userId, Authentication authentication, String message) {
        JSONObject jsonObject = JSONObject.parseObject(message);
        String type = jsonObject.getString("type");
        // 判断用户是否在线，不在线则不用处理，因为在内部无法关闭该定时任务，所以通过返回值在外部进行判断。
        if (WebSocketServer.isConn(userId)) {
            // 因为这里是长链接，不会和普通网页一样，每次发送http 请求可以走拦截器【doFilterInternal】续约，所以需要手动续约
            SecurityUtils.setAuthentication(authentication);
            // 从数据库或者缓存中获取信息，构建自定义的Bean
            CommunityChatMsg chatMsg = saveChat(message);
            // TODO判断数据是否有更新
            // 发送最新数据给前端
            String receiverSid = type + "_" + chatMsg.getReceiverId();
            if (WebSocketServer.isConn(receiverSid)) {
                WebSocketServer.sendInfo("JSON", chatMsg, receiverSid);
            }
        }
    }

    private void start(long delay, Function<Timeout, Boolean> function) {
        timer.newTimeout(t -> {
            // 获取返回值，判断是否执行
            Boolean result = function.apply(t);
            if (result) {
                timer.newTimeout(t.task(), delay, TimeUnit.MILLISECONDS);
            }
        }, delay, TimeUnit.MILLISECONDS);
    }

    /**
     * 判断是否有链接
     *
     * @return
     */
    public static boolean isConn(String sid) {
        for (WebSocketServer item : webSocketSet) {
            if (item.sid.equals(sid)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 群发自定义消息
     * 或者指定用户发送消息
     */
    public static void sendInfo(String type, Object data, @PathParam("sid") String sid) {
        // 遍历WebSocketServer对象集合，如果符合条件就推送
        for (WebSocketServer item : webSocketSet) {
            try {
                //这里可以设定只推送给这个sid的，为null则全部推送
                if (sid == null) {
                    item.sendMessage(type, data);
                } else if (item.sid.equals(sid)) {
                    item.sendMessage(type, data);
                }
            } catch (IOException ignored) {
            }
        }
    }

    /**
     * 实现服务器主动推送
     */
    private void sendMessage(String type, Object data) throws IOException {
        Map<String, Object> result = new HashMap<>();
        result.put("type", type);
        result.put("data", data);
        this.session.getAsyncRemote().sendText(JSON.toJSONString(result));
    }


    /**
     * 保存聊天记录
     *
     * @param message 消息
     * @return 聊天记录
     */
    public static CommunityChatMsg saveChat(String message) {
        JSONObject jsonObject = JSONObject.parseObject(message);
        Long senderId = jsonObject.getLong("senderId");
        Long receiverId = jsonObject.getLong("receiverId");
        String type = jsonObject.getString("type");     //消息分类 chat
        int messageType = jsonObject.getInteger("messageType");     //消息类型
//        JSONObject sendText = jsonObject.getJSONObject("sendText");
        CommunityChatMsg chatMsg = null;
        if (type.equals(MessageType.CHAT.getType())) {
            LOGGER.debug("聊天消息推送");
//            sendToUser(String.valueOf(receiveUserId), JSONObject.toJSONString(jsonObject));

            chatMsg = new CommunityChatMsg();
            chatMsg.setSenderId(senderId);
            chatMsg.setReceiverId(receiverId);
            chatMsg.setCreateBy(senderId);
            chatMsg.setRead(false);
            chatMsg.setType(type);
            chatMsg.setMessageType(messageType);
            chatMsg.setCreateTime(DateUtils.parseDate(DateUtils.getTime()));
            switch (messageType) {
                case 0:
                    //文字
                    String context = jsonObject.getString("context");
                    //存储历史消息
                    chatMsg.setContent(context);
                    break;
                case 1:
                    //图片
                    chatMsg.setFileUrl(jsonObject.getString("fileUrl"));
                    break;
                case 2:
                    //语音
                    chatMsg.setFileUrl(jsonObject.getString("fileUrl"));
                    chatMsg.setTime(jsonObject.getInteger("time"));
                    break;
                case 3:
                    //定位
                    String address = jsonObject.getString("address");
                    Double latitude = jsonObject.getDouble("latitude");       //纬度
                    Double longitude = jsonObject.getDouble("longitude");     //经度
                    chatMsg.setAddress(address);
                    chatMsg.setLatitude(latitude);
                    chatMsg.setLongitude(longitude);
                    break;
                case 4:
                    //视频
                    chatMsg.setFileUrl(jsonObject.getString("fileUrl"));
                    break;

            }
            chatMsgService.save(chatMsg);
        }
        return chatMsg;
    }

    /**
     * 工具方法：解析查询参数
     */
    private Map<String, String> getQueryParams(String query) {
        Map<String, String> params = new HashMap<>();
        if (query != null) {
            String[] pairs = query.split("&");
            for (String pair : pairs) {
                String[] keyValue = pair.split("=");
                if (keyValue.length == 2) {
                    params.put(keyValue[0], keyValue[1]);
                }
            }
        }
        return params;
    }
}
