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

import ink.wgink.module.instantmessage.service.IMessageService;
import ink.wgink.module.instantmessage.service.IWebSocketTextCustomService;
import ink.wgink.module.instantmessage.websocket.handler.text.WebSocketTextHandler;
import ink.wgink.module.instantmessage.websocket.manager.WebSocketChannelManager;
import ink.wgink.properties.websocket.WebSocketProperties;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;

/**
 * @ClassName: WebSocketHandler
 * @Description: 1.web发起一次类似是http的请求，并在channelRead0方法中进行处理，并通过instanceof去判断帧对象是FullHttpRequest还是WebSocketFrame，建立连接是时候会是FullHttpRequest
 * 2.在handleHttpRequest方法中去创建websocket，首先是判断Upgrade是不是websocket协议，若不是则通过sendHttpResponse将错误信息返回给客户端，紧接着通过WebSocketServerHandshakerFactory创建socket对象并通过handshaker握手创建连接
 * 3.在连接创建好后的所以消息流动都是以WebSocketFrame来体现
 * 4.在handlerWebSocketFrame去处理消息，也可能是客户端发起的关闭指令，ping指令等等
 * @Author: wanggeng
 * @Date: 2021/9/11 11:40 上午
 * @Version: 1.0
 */
public class WebSocketHandler extends SimpleChannelInboundHandler<Object> {
    private static final Logger LOG = LoggerFactory.getLogger(WebSocketHandler.class);
    private WebSocketProperties webSocketProperties;
    private WebSocketServerHandshaker webSocketServerHandshaker;
    private IWebSocketTextCustomService IWebSocketTextCustomService;
    private IMessageService messageService;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        LOG.debug("收到消息：" + msg);
        if (msg instanceof FullHttpRequest) {
            //以http请求形式接入，但是走的是websocket
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            //处理websocket客户端的消息
            handlerWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        LOG.debug("新客户端链接：" + ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //断开连接
        LOG.debug("客户端断开连接：" + ctx.channel());
        WebSocketChannelManager.getInstance().removeChannel(ctx.channel());
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 判断是否关闭链路的指令
        if (frame instanceof CloseWebSocketFrame) {
            webSocketServerHandshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        // 判断是否ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 文本消息
        if (frame instanceof TextWebSocketFrame) {
            WebSocketTextHandler webSocketTextHandler = new WebSocketTextHandler();
            webSocketTextHandler.setWebSocketTextCustomHandler(IWebSocketTextCustomService);
            webSocketTextHandler.setMessageService(messageService);
            webSocketTextHandler.setWebSocketProperties(webSocketProperties);
            webSocketTextHandler.handler(ctx, (TextWebSocketFrame) frame);
            return;
        }
        // 二进制文件，暂不处理
        if (frame instanceof BinaryWebSocketFrame) {
            return;
        }
    }

    /**
     * 唯一的一次http请求，用于创建websocket
     */
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        //要求Upgrade为websocket，过滤掉get/Post
        if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) {
            //若不是websocket方式，则创建BAD_REQUEST的req，返回给客户端
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(String.format("%s://%s:%s/%s", webSocketProperties.getScheme(), webSocketProperties.getUrl(), webSocketProperties.getPort(), webSocketProperties.getContext()), null, false);
        webSocketServerHandshaker = wsFactory.newHandshaker(req);
        if (webSocketServerHandshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            webSocketServerHandshaker.handshake(ctx.channel(), req);
        }
    }

    /**
     * 拒绝不合法的请求，并返回错误信息
     */
    private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) {
        // 返回应答给客户端
        if (res.status().code() != HttpStatus.OK.value()) {
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        // 如果是非Keep-Alive，关闭连接
        if (!isKeepAlive(req) || res.status().code() != HttpStatus.OK.value()) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 判断是不是长连接
     *
     * @param req
     * @return
     */
    private boolean isKeepAlive(FullHttpRequest req) {
        String connection = req.headers().get("Connection");
        if (StringUtils.isBlank(connection)) {
            return false;
        }
        if (!StringUtils.equals("keep-alive", connection)) {
            return false;
        }
        return true;
    }

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

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

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