package com.qiuguo.iot.box.websocket.api.handler;

import com.alibaba.fastjson.JSONObject;
import com.qiuguo.iot.base.annotation.WebSocketMapping;
import com.qiuguo.iot.base.enums.YesNo;
import com.qiuguo.iot.base.utils.WebClientUtils;
import com.qiuguo.iot.box.websocket.api.domain.BaseSession;
import com.qiuguo.iot.box.websocket.api.domain.user.UserTalkMessage;
import com.qiuguo.iot.box.websocket.api.filter.LogMdcConfiguration;
import com.qiuguo.iot.box.websocket.api.filter.LogWebFilter;
import com.qiuguo.iot.data.entity.device.DeviceUserBindEntity;
import com.qiuguo.iot.data.request.device.DeviceUserBindRequest;
import com.qiuguo.iot.data.service.device.DeviceUserBindService;
import com.qiuguo.iot.third.nlp.action.Actions;
import com.qiuguo.iot.third.service.NlpService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.HandshakeInfo;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@Component
@WebSocketMapping("/websocket/customer")
@Slf4j
public class CustomerWebSocketHandler extends BaseWebSocketProcess implements WebSocketHandler {
    @Value("${device.checkTimeout}")
    private Boolean checkTimeout;
    @Value("${device.timeout}")
    private Long timeOut;//2分钟

    @Resource
    protected DeviceUserBindService deviceUserBindService;

    @Resource
    private NlpService nlpService;

    @Value("${qiuguo.checktoken.url}")
    private String checkTokenUrl;


    @Override
    public Mono<Void> handle(WebSocketSession session) {
        HandshakeInfo handshakeInfo = session.getHandshakeInfo();
        HttpHeaders headers = handshakeInfo.getHeaders();
        //List<String> tokens = headers.get("token");
        String type = headers.get(apiType).get(0);
        String token = headers.get(apiToken).get(0);
        Long userId = Long.valueOf(headers.get("userId").get(0));
        Long linkTime = Long.parseLong(headers.get("time").get(0));
        if(checkTimeout && System.currentTimeMillis() - linkTime > timeOut){
            //关闭连接
            log.info("用户{}，请求数据已超时", userId);
            return session.close();
        }
        String ip = headers.get(LogWebFilter.HEAD_IP).get(0);
        Map<String, String> reqHead = new HashMap<>();
        reqHead.put(apiType, type);
        reqHead.put(apiToken, token);
        WebClientUtils.get(checkTokenUrl, reqHead).defaultIfEmpty(new JSONObject()).map(jsonObject -> {
            log.info("验签获取的数据{}", jsonObject);
            if(jsonObject.getInteger("code").equals(YesNo.YES.getCode())){
                Long userId1 = jsonObject.getJSONObject("data").getLong("id");
                if(userId1.equals(userId)){
                    log.info("验签成功{}", userId);
                    DeviceUserBindRequest request = new DeviceUserBindRequest();
                    request.setUserId(userId);
                    request.setIsMain(YesNo.YES.getCode());
                    deviceUserBindService.selectDeviceUserBindByRequest(request)
                            .defaultIfEmpty(new DeviceUserBindEntity())
                            .map(deviceUserBindEntity -> {
                                if(deviceUserBindEntity.getId() != null){
                                    log.info("用户绑定信息为{}", deviceUserBindEntity);
                                    BaseSession userSession = getUserSessionWithUserId(userId);
                                    if(userSession != null){
                                        userSession.setDeviceId(deviceUserBindEntity.getDeviceId());
                                        userSession.setSn(deviceUserBindEntity.getOtherDeviceId());
                                    }
                                }
                                return Mono.empty();
                            }).subscribe();
                    return Mono.empty();
                }

            }
            log.info("验签失败{}", userId);
            BaseSession userSession = getUserSessionWithUserId(userId);
            if(userSession != null){
                userSession.getSink().next(session.textMessage("非法登录"));
            }
            session.close().subscribe();
            return Mono.empty();
        }).subscribe();
        log.info("用户成功userId:{}", userId);
        Mono<Void> input = session.receive().map(webSocketMessage ->{
            //MDC.put(LogMdcConfiguration.PRINT_LOG_ID, getBoxSessionWithSn().getLogId());
            String text = webSocketMessage.getPayloadAsText();
            log.info("收到用户消息：{}", text);
            UserTalkMessage userTalkMessage = JSONObject.parseObject(text, UserTalkMessage.class);
            nlpService.getActionWithLacSingle(userTalkMessage.getMessage()).defaultIfEmpty(new Actions()).map(actions -> {
                BaseSession userSession = getUserSessionWithUserId(userTalkMessage.getUserId());
                //处理
                if(actions.getActions() == null || actions.getActions().size() == 0){
                    //调用千问回答
                    log.info("未匹配到自定义命令，调用千问");
                }else{
                    processAction(actions, userId, userSession);
                }

                return Mono.empty();
            }).subscribe();
            log.info("收到用户userId:{}，消息：{}", userTalkMessage.getUserId(), userTalkMessage.getMessage());
            //MDC.remove(LogMdcConfiguration.PRINT_LOG_ID);
            return Mono.empty();
        }).then();
        BaseSession userSession = new BaseSession();
        userSession.setUserId(userId);
        userSession.setSession(session);
        userSession.setCustomerIP(ip);
        userSession.setLogId(MDC.get(LogMdcConfiguration.PRINT_LOG_ID));
        userGroup.put(userId, userSession);
        Mono<Void> output = session.send(Flux.create(sink -> userSession.setSink(sink))).then();

//          Mono.zip() 会将多个 Mono 合并为一个新的 Mono，任何一个 Mono 产生 error 或 complete 都会导致合并后的 Mono
//          也随之产生 error 或 complete，此时其它的 Mono 则会被执行取消操作。

        return Mono.zip(input, output).doFinally(signalType -> {
            // MDC.put(LogMdcConfiguration.PRINT_LOG_ID, requestId);
            userGroup.remove(userSession.getUserId());//断链后及时移除
            log.info("用户断开连接SN：{}", userSession.getUserId());
            //  MDC.remove(LogMdcConfiguration.PRINT_LOG_ID);
        }).then();
    }




}
