package ink.wgink.module.instantmessage.websocket.handler.text;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.exceptions.websocket.*;
import ink.wgink.exceptions.websocket.useroffline.TextUserOfflineException;
import ink.wgink.exceptions.websocket.useroffline.RtcUserOfflineException;
import ink.wgink.module.instantmessage.service.IMessageService;
import ink.wgink.module.instantmessage.service.IWebSocketTextCustomService;
import ink.wgink.module.instantmessage.websocket.enums.MessageSendStatusEnum;
import ink.wgink.module.instantmessage.websocket.enums.MessageTypeEnum;
import ink.wgink.module.instantmessage.websocket.manager.WebSocketChannelManager;
import ink.wgink.module.instantmessage.websocket.pojo.WebSocketClientMessage;
import ink.wgink.module.instantmessage.websocket.pojo.body.IdsBody;
import ink.wgink.module.instantmessage.websocket.pojo.body.RegisterBody;
import ink.wgink.module.instantmessage.websocket.pojo.body.MessageSendStatusBody;
import ink.wgink.pojo.session.WebSocketSession;
import ink.wgink.properties.websocket.WebSocketProperties;
import ink.wgink.util.AesUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.apache.commons.compress.utils.Sets;
import org.apache.commons.lang3.StringUtils;
import org.omg.CORBA.UserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @ClassName: WebSocketTextHandler
 * @Description: WebSocket文本处理器
 * @Author: wanggeng
 * @Date: 2021/9/12 8:08 下午
 * @Version: 1.0
 */
public class WebSocketTextHandler {

    private static final Logger LOG = LoggerFactory.getLogger(WebSocketTextHandler.class);
    private IWebSocketTextCustomService IWebSocketTextCustomService;
    private IMessageService messageService;
    private WebSocketProperties webSocketProperties;

    public void handler(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame) {
        WebSocketClientMessage clientSocketMessage = null;
        MessageSendStatusBody messageSendStatusBody = null;
        try {
            if (webSocketProperties.getEncrypt()) {
                String decodeMessage = AesUtil.aesCommonDecoder(webSocketProperties.getAesKey(), textWebSocketFrame.text());
                clientSocketMessage = JSONObject.parseObject(decodeMessage, WebSocketClientMessage.class);
            } else {
                clientSocketMessage = JSONObject.parseObject(textWebSocketFrame.text(), WebSocketClientMessage.class);
            }
            if (clientSocketMessage.getType() == null) {
                throw new TypeException("Type类型不能为空");
            }
            if (StringUtils.isBlank(clientSocketMessage.getBody())) {
                throw new BodyException("Body主体不能为空");
            }
            if (MessageTypeEnum.REGISTER.getValue() == clientSocketMessage.getType()) {
                // 客户端注册消息
                messageSendStatusBody = clientRegisterSession(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE.getValue() == clientSocketMessage.getType()) {
                sendText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE_HREF.getValue() == clientSocketMessage.getType()) {
                sendText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE_IMAGE.getValue() == clientSocketMessage.getType()) {
                sendText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE_FILE.getValue() == clientSocketMessage.getType()) {
                sendText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE_AUDIO.getValue() == clientSocketMessage.getType()) {
                sendText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE_VIDEO.getValue() == clientSocketMessage.getType()) {
                sendText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE_GROUP.getValue() == clientSocketMessage.getType()) {
                // 群发消息
                sendGroupText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE_GROUP_HREF.getValue() == clientSocketMessage.getType()) {
                sendGroupText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE_GROUP_IMAGE.getValue() == clientSocketMessage.getType()) {
                sendGroupText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE_GROUP_FILE.getValue() == clientSocketMessage.getType()) {
                sendGroupText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE_GROUP_AUDIO.getValue() == clientSocketMessage.getType()) {
                sendGroupText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.MESSAGE_GROUP_VIDEO.getValue() == clientSocketMessage.getType()) {
                sendGroupText(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.SEARCH_ONLINE_USER.getValue() == clientSocketMessage.getType()) {
                // 用户在线状态
                listOnlineUser(ctx.channel(), clientSocketMessage);
            } else if (MessageTypeEnum.STATUS_RECEIVE.getValue() == clientSocketMessage.getType()) {
                // 消息接收状态
                sendReceiveStatus(clientSocketMessage);
            } else {
                if (IWebSocketTextCustomService != null) {
                    // 自定义文本处理
                    IWebSocketTextCustomService.handle(ctx.channel(), clientSocketMessage);
                } else {
                    throw new TypeException("请求类型错误");
                }
            }
        } catch (BaseSocketException e) {
            LOG.error(e.getMessage(), e);
            if (e instanceof SessionException) {
                // 没有登录时，返回内容，关闭连接
                messageSendStatusBody = new MessageSendStatusBody(MessageSendStatusEnum.SESSION_ERROR.getValue(), MessageSendStatusEnum.SESSION_ERROR, e.getMessage());
                clientSocketMessage.setBody(JSONObject.toJSONString(messageSendStatusBody));
                WebSocketChannelManager.getInstance().sendText(ctx.channel(), clientSocketMessage);
                ctx.close();
            } else if (e instanceof TypeException) {
                messageSendStatusBody = new MessageSendStatusBody(MessageSendStatusEnum.TYPE_ERROR.getValue(), MessageSendStatusEnum.TYPE_ERROR, e.getMessage());
            } else if (e instanceof BodyException) {
                messageSendStatusBody = new MessageSendStatusBody(MessageSendStatusEnum.BODY_ERROR.getValue(), MessageSendStatusEnum.BODY_ERROR, e.getMessage());
            } else if (e instanceof ToException) {
                messageSendStatusBody = new MessageSendStatusBody(MessageSendStatusEnum.TO_ERROR.getValue(), MessageSendStatusEnum.TO_ERROR, e.getMessage());
            } else if (e instanceof UserOfflineException) {
                if (e instanceof TextUserOfflineException) {
                    messageSendStatusBody = new MessageSendStatusBody(MessageSendStatusEnum.TEXT_MESSAGE_USER_OFFLINE.getValue(), MessageSendStatusEnum.TEXT_MESSAGE_USER_OFFLINE, e.getMessage());
                } else if (e instanceof RtcUserOfflineException) {
                    messageSendStatusBody = new MessageSendStatusBody(MessageSendStatusEnum.RTC_MESSAGE_USER_OFFLINE.getValue(), MessageSendStatusEnum.RTC_MESSAGE_USER_OFFLINE, e.getMessage());
                } else {
                    messageSendStatusBody = new MessageSendStatusBody(MessageSendStatusEnum.USER_OFFLINE.getValue(), MessageSendStatusEnum.USER_OFFLINE, e.getMessage());
                }
            } else {
                messageSendStatusBody = new MessageSendStatusBody(MessageSendStatusEnum.CUSTOM_HANDLE_ERROR.getValue(), MessageSendStatusEnum.CUSTOM_HANDLE_ERROR, e.getMessage());
            }
        } catch (AesUtil.AesEncodeException e) {
            LOG.error(e.getMessage(), e);
            clientSocketMessage = new WebSocketClientMessage();
            messageSendStatusBody = new MessageSendStatusBody(MessageSendStatusEnum.MESSAGE_ENCODE_ERROR.getValue(), MessageSendStatusEnum.MESSAGE_ENCODE_ERROR, e.getMessage());
        } catch (AesUtil.AesDecodeException e) {
            LOG.error(e.getMessage(), e);
            clientSocketMessage = new WebSocketClientMessage();
            messageSendStatusBody = new MessageSendStatusBody(MessageSendStatusEnum.MESSAGE_DECODE_ERROR.getValue(), MessageSendStatusEnum.MESSAGE_DECODE_ERROR, e.getMessage());
        } catch (JSONException e) {
            LOG.error(e.getMessage(), e);
            clientSocketMessage = new WebSocketClientMessage();
            messageSendStatusBody = new MessageSendStatusBody(MessageSendStatusEnum.TO_ERROR.getValue(), MessageSendStatusEnum.MESSAGE_ERROR, e.getMessage());
        } finally {
            if (messageSendStatusBody != null && ctx.channel().isOpen()) {
                clientSocketMessage.setType(MessageTypeEnum.STATUS_SEND.getValue());
                clientSocketMessage.setSystem(true);
                clientSocketMessage.setFrom(WebSocketChannelManager.FORM_SYSTEM);
                clientSocketMessage.setBody(JSONObject.toJSONString(messageSendStatusBody));
                WebSocketChannelManager.getInstance().sendText(ctx.channel(), clientSocketMessage);
            }
        }
    }

    /**
     * 发送接收状态
     *
     * @param webSocketClientMessage 客户端消息
     */
    private void sendReceiveStatus(WebSocketClientMessage webSocketClientMessage) {
        List<String> toUserIds = new ArrayList<>(Sets.newHashSet(webSocketClientMessage.getTo().split(",")));
        List<WebSocketSession> webSocketSessions = WebSocketChannelManager.getInstance().listOnlineUser(toUserIds);
        for (WebSocketSession webSocketSession : webSocketSessions) {
            WebSocketChannelManager.getInstance().sendText(webSocketSession.getChannel(), webSocketClientMessage);
        }
    }

    /**
     * 客户端会话注册
     *
     * @param channel             会话通道
     * @param clientSocketMessage 客户端信息
     * @return
     * @throws SessionException
     */
    private MessageSendStatusBody clientRegisterSession(Channel channel, WebSocketClientMessage clientSocketMessage) throws SessionException {
        RegisterBody registerBody = JSONObject.parseObject(clientSocketMessage.getBody(), RegisterBody.class);
        // 更新通道
        WebSocketSession webSocketSession = WebSocketChannelManager.getInstance().addChannel(registerBody.getSessionId(), clientSocketMessage.getFrom(), channel);
        sendHistory(webSocketSession);
        return new MessageSendStatusBody(MessageSendStatusEnum.SUCCESS.getValue(), MessageSendStatusEnum.SUCCESS, "OK");
    }

    /**
     * 发送文字消息
     *
     * @param channel                通道
     * @param webSocketClientMessage 客户端消息
     * @throws ToException
     */
    private void sendText(Channel channel, WebSocketClientMessage webSocketClientMessage) throws ToException, TextUserOfflineException, AesUtil.AesEncodeException {
        if (StringUtils.isBlank(webSocketClientMessage.getTo())) {
            throw new ToException("To 值不能为空");
        }
        webSocketClientMessage.setSystem(false);
        List<WebSocketSession> webSocketSessions = WebSocketChannelManager.getInstance().listOnlineUser(webSocketClientMessage.getTo());
        // 返回失败结果
        if (webSocketSessions.isEmpty()) {
            throw new TextUserOfflineException("用户离线");
        }
        // 发送消息
        for (WebSocketSession webSocketSession : webSocketSessions) {
            WebSocketChannelManager.getInstance().sendText(webSocketSession.getChannel(), webSocketClientMessage);
        }
    }

    /**
     * 群发文字消息
     *
     * @param fromChannel            发送通道
     * @param webSocketClientMessage 客户端消息
     * @throws ToException
     * @throws TextUserOfflineException
     */
    private void sendGroupText(Channel fromChannel, WebSocketClientMessage webSocketClientMessage) throws ToException, TextUserOfflineException, AesUtil.AesEncodeException {
        if (StringUtils.isBlank(webSocketClientMessage.getTo())) {
            throw new ToException("To 值不能为空");
        }
        webSocketClientMessage.setSystem(false);
        List<String> toUserIds = new ArrayList<>(Sets.newHashSet(webSocketClientMessage.getTo().split(",")));
        List<WebSocketSession> webSocketSessions = WebSocketChannelManager.getInstance().listOnlineUser(toUserIds);
        // 返回失败结果
        if (webSocketSessions.isEmpty()) {
            throw new TextUserOfflineException("无用户在线");
        }
        // 发送消息
        for (WebSocketSession webSocketSession : webSocketSessions) {
            WebSocketChannelManager.getInstance().sendText(webSocketSession.getChannel(), webSocketClientMessage);
        }
    }

    /**
     * 在线用户列表
     *
     * @param fromChannel            通道
     * @param webSocketClientMessage 消息体
     * @throws UserException
     */
    private void listOnlineUser(Channel fromChannel, WebSocketClientMessage webSocketClientMessage) throws BodyException, ToException, UserOfflineException {
        webSocketClientMessage.setSystem(true);
        webSocketClientMessage.setTo(webSocketClientMessage.getFrom());
        IdsBody idsBody = JSONObject.parseObject(webSocketClientMessage.getBody(), IdsBody.class);
        if (idsBody.getIds() == null || idsBody.getIds().isEmpty()) {
            throw new BodyException("用户ID列表为空");
        }
        List<WebSocketSession> webSocketSessions = WebSocketChannelManager.getInstance().listOnlineUser(idsBody.getIds());
        Set<String> idSets = new HashSet<>();
        for (WebSocketSession webSocketSession : webSocketSessions) {
            idSets.add(webSocketSession.getUserId());
        }
        idsBody = new IdsBody();
        idsBody.setIds(new ArrayList<>(idSets));
        webSocketClientMessage.setType(MessageTypeEnum.STATUS_SEND_ONLINE.getValue());
        webSocketClientMessage.setBody(JSONObject.toJSONString(idsBody));
        sendText(fromChannel, webSocketClientMessage);
    }

    /**
     * 发送历史
     *
     * @param webSocketSession
     */
    private void sendHistory(WebSocketSession webSocketSession) {
        // 3s之后更新，更新历史通知
        messageService.noticeUnSendByUserId(webSocketSession);
    }

    public void setWebSocketTextCustomHandler(IWebSocketTextCustomService IWebSocketTextCustomService) {
        this.IWebSocketTextCustomService = IWebSocketTextCustomService;
    }

    public void setMessageService(IMessageService messageService) {
        this.messageService = messageService;
    }

    public void setWebSocketProperties(WebSocketProperties webSocketProperties) {
        this.webSocketProperties = webSocketProperties;
    }
}
