package com.wecloud.im.netty.handler;


import io.geekidea.springbootplus.config.constant.CommonConstant;
import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.service.ShiroLoginService;
import io.geekidea.springbootplus.framework.shiro.util.JwtUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.wecloud.im.entity.ImApplication;
import com.wecloud.im.entity.ImClient;
import com.wecloud.im.event.ClientOnlineStatusChangeEvent;
import com.wecloud.im.netty.core.WsReadHandler;
import com.wecloud.im.service.ImApplicationService;
import com.wecloud.im.service.ImClientService;
import com.wecloud.im.ws.manager.ChannelManager;
import com.wecloud.im.ws.model.WsConstants;
import com.wecloud.im.ws.utils.FullHttpRequestUtils;
import com.wecloud.utils.JsonUtils;

/**
 * @Description 聊天模块 http请求处理
 * @Author hewei hwei1233@163.com
 * @Date 2019-07-19
 */
@Component
@Slf4j
public class NettyApiRequest {

    @Autowired
    private ShiroLoginService shiroLoginService;

    @Resource
    private ChannelManager appUserChannelsService;

    @Resource
    private WsReadHandler appImReadHandler;

    @Autowired
    private ImClientService imClientService;

    @Autowired
    private ImApplicationService imApplicationService;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    /**
     * http请求接收
     *
     * @param ctx
     * @param msg
     * @param httpRequest
     * @throws Exception
     */
    public void handle(ChannelHandlerContext ctx, Object msg, FullHttpRequest httpRequest) throws Exception {
        if (!(msg instanceof FullHttpRequest)) {
            String context = JsonUtils.encodeJson(ApiResult.fail());
            FullHttpRequestUtils.send(ctx, context, HttpResponseStatus.OK);
            return;
        }
        String path = httpRequest.uri();
        String body = FullHttpRequestUtils.getBody(httpRequest);

        if (log.isDebugEnabled()) {
            log.info("httpRequest:\n" + httpRequest.toString() + "\n" + body);
        }

        if (path.contains(WsConstants.WS_URL)) {
            /*
              app聊天http升级webSocket
             */
            this.initWs(ctx, httpRequest);

        }
    }

    /**
     * 初始化websocket
     */
    private void initWs(ChannelHandlerContext ctx, FullHttpRequest httpRequest) throws Exception {
        Map<String, String> paramMap = FullHttpRequestUtils.parameterParse(httpRequest);
        String token = paramMap.get(WsConstants.TOKEN);
        if (StringUtils.isBlank(token)) {
            String context = JsonUtils.encodeJson(ApiResult.result(ApiCode.FAIL, "token不能为空", (Object) null));
            FullHttpRequestUtils.send(ctx, context, HttpResponseStatus.OK);
            return;
        }

        if (JwtUtil.isExpired(token)) {
            String context = JsonUtils.encodeJson(ApiResult.result(ApiCode.FAIL, "JWT Token已过期,token", (Object) null));
            FullHttpRequestUtils.send(ctx, context, HttpResponseStatus.OK);
            return;
        }

        DecodedJWT jwtInfo = JwtUtil.getJwtInfo(token);
        String payload = jwtInfo.getPayload();
        Base64.Decoder decoder = Base64.getDecoder();
        payload = new String(decoder.decode(payload), StandardCharsets.UTF_8);
        JSONObject jsonObject = JSONObject.parseObject(payload);
        String appKey = (String) jsonObject.get(CommonConstant.APP_KEY);
        String outClientId = (String) jsonObject.get(CommonConstant.CLIENT_ID);


        // 从redis获取jwt的token 验签token
        JwtToken jwtToken = shiroLoginService.getJwtTokenForRedis(token);

        if (jwtToken == null) {
            log.info("jwtToken == null ,token和redis不一致, outClientId:" + outClientId + ",token:" + token);
            String context = JsonUtils.encodeJson(ApiResult.result(ApiCode.FAIL, "jwtToken == null ,token和redis不一致", (Object) null));
            FullHttpRequestUtils.send(ctx, context, HttpResponseStatus.OK);
            return;
        }

        if ((!jwtToken.getClientId().equals(outClientId)) || (!jwtToken.getAppKey().equals(appKey))) {
            log.info("outClientId  appKey 不一致");
            String context = JsonUtils.encodeJson(ApiResult.result(ApiCode.FAIL, "outClientId  appKey 不一致", (Object) null));
            FullHttpRequestUtils.send(ctx, context, HttpResponseStatus.OK);
            return;
        }

        // 设置uri前缀
        httpRequest.setUri(WsConstants.WS_URL);

        // 保持当前连接
        ctx.fireChannelRead(httpRequest.retain());

//         添加长连接handler
        ctx.pipeline().addLast("appImHandler", appImReadHandler);

        ImApplication app = imApplicationService.getCacheAppByAppKey(appKey);
        if (app == null) {
            log.warn("根据appKey: {} 查找不到 imApplication！", appKey);
            return;
        }

        ImClient client = imClientService.getCacheImClient(app.getId(), outClientId);

        // 设置属性值 userid - channel
        ctx.channel().attr(ChannelManager.CLIENT_ID).set(client.getId());
        ctx.channel().attr(ChannelManager.PLATFORM).set(jwtToken.getPlatform());
        ctx.channel().attr(ChannelManager.APPLICATION_ID).set(app.getId());// 读空闲的计数=0
        ctx.channel().attr(ChannelManager.READ_IDLE_TIMES).set(0);// 读空闲的计数=0

        // 保存用户上下文对象
        appUserChannelsService.online(client.getId(), jwtToken.getPlatform(), (NioSocketChannel) ctx.channel());

        // 发布客户端在线状态变化-上线事件
        Long appId = app.getId();
        String clientId = client.getClientId();
        Integer platform = jwtToken.getPlatform();
        long time = System.currentTimeMillis();
        String clientIp = ctx.channel().remoteAddress().toString();
        ClientOnlineStatusChangeEvent clientOnlineStatusChangeEvent = new ClientOnlineStatusChangeEvent(appId,
                client.getId(), 1, platform, time, clientIp);
        eventPublisher.publishEvent(clientOnlineStatusChangeEvent);
        //移除当前api处理handler， 不再参与长连接处理
        ctx.pipeline().remove("SingleHttpRequestHandler");

    }


}
