package com.fs.live.websocket.handle;

import com.alibaba.fastjson.JSONObject;
import com.fs.his.domain.FsUser;
import com.fs.his.service.IFsUserService;
import com.fs.live.websocket.bean.SendMsgVo;
import com.fs.live.websocket.constant.AttrConstant;
import com.fs.common.core.domain.R;
import com.fs.common.utils.spring.SpringUtils;
import com.fs.live.domain.LiveMsg;
import com.fs.live.domain.LiveWatchUser;
import com.fs.live.service.ILiveMsgService;
import com.fs.live.service.ILiveService;
import com.fs.live.service.ILiveWatchUserService;
import com.fs.live.vo.LiveWatchUserVO;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

@Component
@ChannelHandler.Sharable
@Slf4j
public class LiveChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    // 容器
    private final static ConcurrentHashMap<Long, CopyOnWriteArrayList<Channel>> adminRooms = new ConcurrentHashMap<>();
    private final static ConcurrentHashMap<Long, ConcurrentHashMap<Long, Channel>> rooms = new ConcurrentHashMap<>();
    private final static ConcurrentHashMap<Long, ChannelGroup> roomGroups = new ConcurrentHashMap<>();
    private final static ILiveService liveService = SpringUtils.getBean(ILiveService.class);
    private final static ILiveWatchUserService liveWatchUserService = SpringUtils.getBean(ILiveWatchUserService.class);
    private final static ILiveMsgService liveMsgService = SpringUtils.getBean(ILiveMsgService.class);
    private final static IFsUserService fsUserService = SpringUtils.getBean(IFsUserService.class);

    /**
     * 处理握手
     * @param ctx   连接
     * @param evt   数据
     * @throws Exception    异常
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        log.debug("事件");
        // 处理 WebSocket 握手完成事件
        if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
            Long userId = ctx.channel().attr(AttrConstant.ATTR_USER_ID).get();
            Long liveId = ctx.channel().attr(AttrConstant.ATTR_LIVE_ID).get();
            Long userType = ctx.channel().attr(AttrConstant.ATTR_USER_TYPE).get();


            if (Objects.isNull(liveService.selectLiveByLiveId(liveId))) {
                ctx.channel().writeAndFlush(new TextWebSocketFrame("Error: 未找到直播间")).addListener(ChannelFutureListener.CLOSE);
                return;
            }

            Map<Long, Channel> room = getRoom(liveId);
            List<Channel> adminRoom = getAdminRoom(liveId);
            ChannelGroup roomGroup = getRoomGroup(liveId);
            roomGroup.add(ctx.channel());

            if (userType == 0) {
                // 加入房间
                LiveWatchUser liveWatchUser = liveWatchUserService.joinWithoutLocation(liveId, userId);
                room.put(userId, ctx.channel());

                FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
                if (Objects.isNull(fsUser)) {
                    ctx.channel().writeAndFlush(new TextWebSocketFrame("Error: 用户信息错误")).addListener(ChannelFutureListener.CLOSE);
                    return;
                }


                SendMsgVo sendMsgVo = new SendMsgVo();
                sendMsgVo.setLiveId(liveId);
                sendMsgVo.setUserId(userId);
                sendMsgVo.setUserType(userType);
                sendMsgVo.setCmd("entry");
                sendMsgVo.setMsg("用户进入");
                sendMsgVo.setData(JSONObject.toJSONString(liveWatchUser));
                sendMsgVo.setNickName(fsUser.getNickname());
                sendMsgVo.setAvatar(fsUser.getAvatar());

                // 广播连接消息
                broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
            } else if (userType == 1) {
                adminRoom.add(ctx.channel());
            }

            log.debug("加入webSocket liveId: {}, userId: {}, 直播间人数: {}", liveId, userId, room.size());
        }
    }

    /**
     * 获取房间
     * @param liveId 直播间ID
     * @return 容器
     */
    private CopyOnWriteArrayList<Channel> getAdminRoom(Long liveId) {
        return adminRooms.computeIfAbsent(liveId, k -> new CopyOnWriteArrayList<>());
    }

    /**
     * 获取房间
     * @param liveId 直播间ID
     * @return 容器
     */
    private ConcurrentHashMap<Long, Channel> getRoom(Long liveId) {
        return rooms.computeIfAbsent(liveId, k -> new ConcurrentHashMap<>());
    }

    /**
     * 获取房间用户组
     * @param liveId 直播间ID
     * @return  用户组
     */
    private ChannelGroup getRoomGroup(Long liveId) {
        return roomGroups.computeIfAbsent(liveId, k -> new DefaultChannelGroup(GlobalEventExecutor.INSTANCE));
    }

    /**
     * 发送广播
     * @param liveId    直播间ID
     * @param msg       消息
     */
    private void broadcastMessage(Long liveId, String msg) {
        getRoomGroup(liveId).writeAndFlush(new TextWebSocketFrame(msg));
    }

    /**
     * 发送指定消息
     * @param channel   连接
     * @param message   消息
     */
    private void sendMessage(Channel channel, String message) {
        channel.writeAndFlush(new TextWebSocketFrame(message));
    }

    /**
     * 接收消息
     * @param channelHandlerContext 连接
     * @param textWebSocketFrame    消息
     * @throws Exception    异常
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
        log.debug("接收到消息 data: {}", textWebSocketFrame.text());
        Long liveId = channelHandlerContext.channel().attr(AttrConstant.ATTR_LIVE_ID).get();
        Long userType = channelHandlerContext.channel().attr(AttrConstant.ATTR_USER_TYPE).get();

        SendMsgVo msg = JSONObject.parseObject( textWebSocketFrame.text(), SendMsgVo.class);
        if(msg.isOn()) return;
        try {
            switch (msg.getCmd()) {
                case "heartbeat":
                    sendMessage(channelHandlerContext.channel(), JSONObject.toJSONString(R.ok().put("data", msg)));
                    break;
                case "sendMsg":
                    LiveMsg liveMsg = new LiveMsg();
                    liveMsg.setLiveId(msg.getLiveId());
                    liveMsg.setUserId(msg.getUserId());
                    liveMsg.setNickName(msg.getNickName());
                    liveMsg.setAvatar(msg.getAvatar());
                    liveMsg.setMsg(msg.getMsg());
                    liveMsg.setCreateTime(new Date());

                    if (userType == 0) {
                        Map<String, Integer> liveFlagWithCache = liveWatchUserService.getLiveFlagWithCache(liveId);
                        LiveWatchUser liveWatchUser = liveWatchUserService.selectLiveWatchUserByFlag(msg.getLiveId(), msg.getUserId(), liveFlagWithCache.get("liveFlag"),  liveFlagWithCache.get("replayFlag"));
                        if(liveWatchUser != null && liveWatchUser.getMsgStatus() == 1){
                            sendMessage(channelHandlerContext.channel(), JSONObject.toJSONString(R.error("你以被禁言")));
                            return;
                        }

                        liveMsgService.insertLiveMsg(liveMsg);
                    }

                    msg.setOn(true);
                    msg.setData(JSONObject.toJSONString(liveMsg));

                    // 广播消息
                    broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", msg)));
                    break;
            }
        } catch (Exception e) {
            log.error("webSocket 消息处理失败 msg: {}", e.getMessage(), e);
        }
    }

    /**
     * 断开连接
     * @param ctx   连接
     * @throws Exception    异常
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.debug("断开连接");
        Long userId = ctx.channel().attr(AttrConstant.ATTR_USER_ID).get();
        Long liveId = ctx.channel().attr(AttrConstant.ATTR_LIVE_ID).get();
        Long userType = ctx.channel().attr(AttrConstant.ATTR_USER_TYPE).get();

        if (Objects.isNull(userId) || Objects.isNull(liveId) || Objects.isNull(userType)) {
            return;
        }

        Map<Long, Channel> room = getRoom(liveId);
        List<Channel> adminRoom = getAdminRoom(liveId);
        ChannelGroup roomGroup = getRoomGroup(liveId);

        if (userType == 0) {
            FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
            LiveWatchUser close = liveWatchUserService.close(liveId, userId);
            room.remove(userId);

            if (room.isEmpty()) {
                rooms.remove(liveId);
            }



            SendMsgVo sendMsgVo = new SendMsgVo();
            sendMsgVo.setLiveId(liveId);
            sendMsgVo.setUserId(userId);
            sendMsgVo.setUserType(userType);
            sendMsgVo.setCmd("out");
            sendMsgVo.setMsg("用户离开");
            sendMsgVo.setData(JSONObject.toJSONString(close));
            sendMsgVo.setNickName(fsUser.getNickname());
            sendMsgVo.setAvatar(fsUser.getAvatar());

            // 广播离开消息
            broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
        } else {
            adminRoom.remove(ctx.channel());
            if (adminRoom.isEmpty()) {
                adminRooms.remove(liveId);
            }
        }
        roomGroup.remove(ctx.channel());
        if (roomGroup.isEmpty()) {
            roomGroups.remove(liveId);
        }

        log.debug("断开webSocket liveId: {}, userId: {}, 直播间人数: {}", liveId, userId, room.size());

    }

    /**
     * 连接异常
     * @param ctx   连接
     * @param cause 原因
     * @throws Exception 异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("连接异常 msg: {}", cause.getMessage(), cause);
        ctx.close();
    }
}
