package com.wecloud.rtc.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.collect.Lists;
import com.wecloud.im.entity.ImApplication;
import com.wecloud.im.entity.ImClient;
import com.wecloud.im.entity.ImRtcRecord;
import com.wecloud.im.mq.MqSender;
import com.wecloud.im.param.rtc.CandidateForwardParam;
import com.wecloud.im.param.rtc.CreateRtcChannelParam;
import com.wecloud.im.param.rtc.CreateRtcChannelResult;
import com.wecloud.im.param.rtc.JoinRtcChannelParam;
import com.wecloud.im.param.rtc.LeaveRtcChannelParam;
import com.wecloud.im.param.rtc.RejectRtcChannelParam;
import com.wecloud.im.param.rtc.SdpForwardParam;
import com.wecloud.im.sdk.enums.RtcStateEnum;
import com.wecloud.im.sdk.enums.SingleRtcOperateTypeEnum;
import com.wecloud.im.service.ContextService;
import com.wecloud.im.service.ImApplicationService;
import com.wecloud.im.service.ImClientBlacklistService;
import com.wecloud.im.service.ImClientService;
import com.wecloud.im.service.ImRtcRecordService;
import com.wecloud.im.ws.cache.UserStateCacheManager;
import com.wecloud.im.ws.cache.UserStateListener;
import com.wecloud.im.ws.model.request.PushVO;
import com.wecloud.pushserver.client.model.constant.MqConstant;
import com.wecloud.pushserver.client.model.dto.PushDTO;
import com.wecloud.rtc.entity.response.RtcCallResponse;
import com.wecloud.rtc.entity.response.RtcCandidateForwardResponse;
import com.wecloud.rtc.entity.response.RtcClientJoinResponse;
import com.wecloud.rtc.entity.response.RtcClientLeaveResponse;
import com.wecloud.rtc.entity.response.RtcClientRejectResponse;
import com.wecloud.rtc.entity.response.RtcSdpForwardResponse;
import com.wecloud.rtc.service.MangerRtcCacheService;
import com.wecloud.rtc.service.RtcService;
import com.wecloud.rtc.service.WsRtcWrite;
import com.wecloud.utils.SnowflakeUtil;
import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
import io.geekidea.springbootplus.framework.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Slf4j
@Service
public class RtcServiceImpl extends UserStateListener implements RtcService {

    private static final Integer MAX_OFFLINE_TIMES = 3;

    @Autowired
    private ImApplicationService imApplicationService;

    @Autowired
    private WsRtcWrite wsRtcWrite;

    /**
     * redis缓存
     */
    @Autowired
    private MangerRtcCacheService mangerRtcCacheService;

    @Autowired
    private ImClientService imClientService;

    @Autowired
    private UserStateCacheManager userStateCacheManager;

    @Autowired
    private ImClientBlacklistService imClientBlacklistService;

    @Autowired
    private ImRtcRecordService imRtcRecordService;

    @Autowired
    private MqSender mqSender;

    @Autowired
    private ContextService contextService;

    @Override
    public void onLineEvent(Long client, Integer platform, String longChannelId) {
        // nothing need to do
    }

    @Override
    public void offlineEvent(Long clientId, Integer platform, String longChannelId) {
        //  根据appKey查询appid
        ImClient client = imClientService.getCacheImClient(clientId);

        // 获取该客户端加入的频道ID
        Long listByClientId = mangerRtcCacheService.getRtcChannelIdListByClientId(client.getId());
        if (listByClientId == null) {
            return;
        }

        LeaveRtcChannelParam leaveRtcChannelParam = new LeaveRtcChannelParam();
        leaveRtcChannelParam.setChannelId(listByClientId);
        // websocket离线逻辑  服务端踢出频道
        this.leave(leaveRtcChannelParam);
    }

    @Override
    public CreateRtcChannelResult createAndCall(CreateRtcChannelParam createRtcChannelParam) {
        ImClient currentClient = imClientService.getCurrentClient();
        Long rtcChannelId = SnowflakeUtil.getId();
        ImClient toClient = imClientService.getCacheImClient(currentClient.getFkAppid(), createRtcChannelParam.getToClient());
        if (toClient == null) {
            throw new BusinessException("查无接收人信息");
        }
        ImRtcRecord imRtcRecord = imRtcRecordService.getOne(new LambdaQueryWrapper<ImRtcRecord>()
                .eq(ImRtcRecord::getFkAppid, currentClient.getFkAppid())
                .in(ImRtcRecord::getState, Lists.newArrayList(RtcStateEnum.CREATED.getCode(), RtcStateEnum.ING.getCode()))
                .and(o -> o.eq(ImRtcRecord::getToClientId, toClient.getClientId())
                        .or()
                        .eq(ImRtcRecord::getFromClientId, toClient.getClientId()))
        );
        if (imRtcRecord != null) {
            throw new BusinessException("对方忙线中");
        }
        // 拉黑逻辑
        black(currentClient, toClient);
        // 添加缓存
        mangerRtcCacheService.create(currentClient.getId(), toClient.getId(), rtcChannelId);

        CreateRtcChannelResult createRtcChannelResult = new CreateRtcChannelResult();
        createRtcChannelResult.setChannelId(rtcChannelId);

        // ws向接收方发送通知
        RtcCallResponse rtcCallResponse = new RtcCallResponse();
        rtcCallResponse.setCallType(createRtcChannelParam.getCallType());
        rtcCallResponse.setConversationId(createRtcChannelParam.getConversationId());
        rtcCallResponse.setChannelId(rtcChannelId);
        rtcCallResponse.setClientId(currentClient.getClientId());
        rtcCallResponse.setTimestamp(System.currentTimeMillis());

        wsRtcWrite.rtcCall(rtcCallResponse, toClient.getId());

        // 创建通话记录
        imRtcRecordService.createRtcRecord(createRtcChannelParam, createRtcChannelResult.getChannelId(), currentClient);

        // 异步推送系统通知消息
        ImApplication imApplication = contextService.getImApplicationIfNotNullOrThrow(currentClient.getFkAppid());
        PushDTO pushDTO = mqSender.buildPushDto(new PushVO("音视频通话", "点击查看", null), toClient.getId(), toClient.getClientId(), imApplication);
        if (pushDTO != null) {
            mqSender.orderSend(MqConstant.Topic.IM_ORDER_MSG_TOPIC, MqConstant.Tag.IM_ORDER_MSG_TAG, pushDTO);
        }
        return createRtcChannelResult;
    }

    @Override
    public Boolean join(JoinRtcChannelParam joinRtcChannelParam) {

        ImClient client = imClientService.getCurrentClient();

        // 修改缓存
        mangerRtcCacheService.join(client.getId(), joinRtcChannelParam.getChannelId());

        //获取频道内所有client
        List<String> clientListByRtcChannelId = mangerRtcCacheService.getClientListByRtcChannelId(joinRtcChannelParam.getChannelId());
        // 移除自己
        clientListByRtcChannelId.remove(client.getId().toString());

        for (String toClientId : clientListByRtcChannelId) {

            // ws向接收方发送通知
            RtcClientJoinResponse rtcSdpForwardResponse = new RtcClientJoinResponse();

            rtcSdpForwardResponse.setChannelId(joinRtcChannelParam.getChannelId());
            rtcSdpForwardResponse.setClientId(client.getClientId());
            rtcSdpForwardResponse.setTimestamp(System.currentTimeMillis());
            wsRtcWrite.clientJoin(rtcSdpForwardResponse, Long.valueOf(toClientId));
        }

        // 更新通话记录
        imRtcRecordService.updateRtcRecord(joinRtcChannelParam.getChannelId(), SingleRtcOperateTypeEnum.JOIN.getCode());

        return true;
    }

    @Override
    public Boolean reject(RejectRtcChannelParam rejectRtcChannelParam) {
        ImClient client = imClientService.getCurrentClient();
        // 修改缓存
        mangerRtcCacheService.leave(client.getId(), rejectRtcChannelParam.getChannelId());
        //获取频道内所有client
        List<String> clientListByRtcChannelId = mangerRtcCacheService.getClientListByRtcChannelId(rejectRtcChannelParam.getChannelId());

        for (String toClientId : clientListByRtcChannelId) {
            // ws向接收方发送通知
            RtcClientRejectResponse rtcClientRejectResponse = new RtcClientRejectResponse();

            rtcClientRejectResponse.setChannelId(rejectRtcChannelParam.getChannelId());
            rtcClientRejectResponse.setClientId(client.getClientId());
            rtcClientRejectResponse.setTimestamp(System.currentTimeMillis());
            wsRtcWrite.clientReject(rtcClientRejectResponse, Long.valueOf(toClientId));
        }

        // 判断频道内是否无其他人了
        if (CollectionUtils.isEmpty(clientListByRtcChannelId)) {
            // 移除频道信息
            mangerRtcCacheService.delChannelInfo(rejectRtcChannelParam.getChannelId());
        } else if (clientListByRtcChannelId.size() == 1) {
            // 频道内只有一个人了 -- 删除频道 并将这个人设为离开
            mangerRtcCacheService.leave(Long.valueOf(clientListByRtcChannelId.get(0)), rejectRtcChannelParam.getChannelId());
            mangerRtcCacheService.delChannelInfo(rejectRtcChannelParam.getChannelId());
        }

        // 更新通话记录
        imRtcRecordService.updateRtcRecord(rejectRtcChannelParam.getChannelId(), SingleRtcOperateTypeEnum.REJECT.getCode());

        return true;
    }

    @Override
    public Boolean leave(LeaveRtcChannelParam leaveRtcChannelParam) {
        ImClient currentClient = imClientService.getCurrentClient();
        this.leave(leaveRtcChannelParam, currentClient);

        // 更新通话记录
        imRtcRecordService.updateRtcRecord(leaveRtcChannelParam.getChannelId(), SingleRtcOperateTypeEnum.LEAVE.getCode());

        return true;
    }

    private void leave(LeaveRtcChannelParam leaveRtcChannelParam, ImClient currentClient) {
        // 修改缓存
        mangerRtcCacheService.leave(currentClient.getId(), leaveRtcChannelParam.getChannelId());

        //获取频道内所有client
        List<String> clientListByRtcChannelId = mangerRtcCacheService.getClientListByRtcChannelId(leaveRtcChannelParam.getChannelId());

        for (String toClientId : clientListByRtcChannelId) {
            // ws向接收方发送通知
            RtcClientLeaveResponse rtcClientLeaveResponse = new RtcClientLeaveResponse();

            rtcClientLeaveResponse.setChannelId(leaveRtcChannelParam.getChannelId());
            rtcClientLeaveResponse.setClientId(currentClient.getClientId());
            rtcClientLeaveResponse.setTimestamp(System.currentTimeMillis());
            wsRtcWrite.clientLeave(rtcClientLeaveResponse, Long.valueOf(toClientId));
        }

        // 判断频道内是否无其他人了
        if (CollectionUtils.isEmpty(clientListByRtcChannelId)) {
            // 移除频道信息
            mangerRtcCacheService.delChannelInfo(leaveRtcChannelParam.getChannelId());
        } else if (clientListByRtcChannelId.size() == 1) {
            // 频道内只有一个人了 -- 删除频道 并将这个人设为离开
            mangerRtcCacheService.leave(Long.valueOf(clientListByRtcChannelId.get(0)), leaveRtcChannelParam.getChannelId());
            mangerRtcCacheService.delChannelInfo(leaveRtcChannelParam.getChannelId());
        }
    }

    @Override
    public Boolean sdpForward(SdpForwardParam sdpForwardParam) {

        ImClient client = imClientService.getCurrentClient();
        Long rtcChannelId = sdpForwardParam.getChannelId();

        //  根据appKey查询appid
        ImApplication imApplication = imApplicationService.getCacheById(client.getFkAppid());

        //  判断发起方必须在线
        boolean onlineStatus = userStateCacheManager.isOnline(client.getId());
        if (!onlineStatus) {
            log.info("发起方必须在线" + imApplication.getAppKey() + client.getClientId());
            ApiResult.fail();
        }

        CreateRtcChannelResult createRtcChannelResult = new CreateRtcChannelResult();
        createRtcChannelResult.setChannelId(rtcChannelId);

        //获取频道内所有client
        List<String> clientListByRtcChannelId = mangerRtcCacheService.getClientListByRtcChannelId(sdpForwardParam.getChannelId());
        // 移除自己
        clientListByRtcChannelId.remove(client.getId().toString());

        for (String toClientId : clientListByRtcChannelId) {

            // ws向接收方发送通知
            RtcSdpForwardResponse rtcSdpForwardResponse = new RtcSdpForwardResponse();
            rtcSdpForwardResponse.setSdpData(sdpForwardParam.getSdpData());
            rtcSdpForwardResponse.setSdpType(sdpForwardParam.getSdpType());

            rtcSdpForwardResponse.setChannelId(rtcChannelId);
            rtcSdpForwardResponse.setClientId(client.getClientId());
            rtcSdpForwardResponse.setTimestamp(System.currentTimeMillis());
            wsRtcWrite.sdpForward(rtcSdpForwardResponse, Long.valueOf(toClientId));
        }


        return true;
    }

    @Override
    public Boolean candidateForward(CandidateForwardParam candidateForwardParam) {

        ImClient client = imClientService.getCurrentClient();
        Long rtcChannelId = candidateForwardParam.getChannelId();

        //  根据appKey查询appid
        ImApplication imApplication = imApplicationService.getCacheById(client.getFkAppid());

        //  判断发起方必须在线
        boolean onlineStatus = userStateCacheManager.isOnline(client.getId());
        if (!onlineStatus) {
            log.info("发起方必须在线" + imApplication.getAppKey() + client.getClientId());
            ApiResult.fail();
        }

        CreateRtcChannelResult createRtcChannelResult = new CreateRtcChannelResult();
        createRtcChannelResult.setChannelId(rtcChannelId);

        //获取频道内所有client
        List<String> clientListByRtcChannelId = mangerRtcCacheService.getClientListByRtcChannelId(candidateForwardParam.getChannelId());
        // 移除自己
        clientListByRtcChannelId.remove(client.getId().toString());

        for (String toClientId : clientListByRtcChannelId) {

            // ws向接收方发送通知
            RtcCandidateForwardResponse rtcCandidateForwardResponse = new RtcCandidateForwardResponse();

            rtcCandidateForwardResponse.setCandidateData(candidateForwardParam.getCandidateData());

            rtcCandidateForwardResponse.setChannelId(rtcChannelId);
            rtcCandidateForwardResponse.setClientId(client.getClientId());
            rtcCandidateForwardResponse.setTimestamp(System.currentTimeMillis());
            wsRtcWrite.candidateForward(rtcCandidateForwardResponse, Long.valueOf(toClientId));
        }

        return true;
    }

    @Override
    public void abnormalDisconnect() {
        List<ImRtcRecord> records = imRtcRecordService.list(new QueryWrapper<ImRtcRecord>().lambda()
                        .ne(ImRtcRecord::getState, 3));
        if (CollectionUtils.isEmpty(records)) {
            return;
        }
        for (ImRtcRecord record : records) {
            try {
                if (record.getOfflineTimes() >= MAX_OFFLINE_TIMES) {
                    // 最大离线次数到阈值，做离线处理
                    imRtcRecordService.updateRtcRecord(record.getChannelId(), SingleRtcOperateTypeEnum.ABNORMAL_DISCONNECT.getCode());
                } else {
                    // 判断频道内两个人是否离线 只要其中一人离线 则认定为离线  将该通话离线次数 +1
                    ImClient fromClient = imClientService.getCacheImClient(record.getFkAppid(), record.getFromClientId());
                    ImClient toClient = imClientService.getCacheImClient(record.getFkAppid(), record.getToClientId());
                    boolean isFromClientOnline = userStateCacheManager.isOnline(fromClient.getId());
                    boolean isToClientOnline = userStateCacheManager.isOnline(toClient.getId());
                    if (isFromClientOnline && isToClientOnline) {
                        // 双方均在线 跳过
                        continue;
                    }
                    record.setOfflineTimes(record.getOfflineTimes() + 1);
                    imRtcRecordService.updateById(record);
                }
            } catch (Exception e) {
                log.info("单人音视频 {} ，处理异常 ", JSON.toJSONString(record), e);
            }
        }
    }


    /**
     * 判断是否被拉黑
     *
     * @param currentClient
     * @param toClient
     * @return
     */
    private void black(ImClient currentClient, ImClient toClient) {
        // 判断是否被拉黑
        boolean beBlack = imClientBlacklistService.isBeBlack(toClient.getClientId(), currentClient.getClientId());
        if (beBlack) {
            throw new BusinessException(ApiCode.IS_BE_BLACK.getCode(), "被对方拉黑了");
        }
        // 是否把对方拉黑
        boolean black = imClientBlacklistService.isBeBlack(currentClient.getClientId(), toClient.getClientId());
        if (black) {
            throw new BusinessException(ApiCode.IS_TO_BLACK.getCode(), "你把对方拉黑了");
        }
    }
}
