package com.wecloud.multimeet.service.impl;

import com.alibaba.fastjson.JSON;
import com.wecloud.im.service.ImClientBlacklistService;
import io.geekidea.springbootplus.framework.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.List;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.google.common.collect.Lists;
import com.wecloud.im.entity.ImClient;
import com.wecloud.im.entity.ImMultiRtcRoom;
import com.wecloud.im.entity.ImMultiRtcRoomMember;
import com.wecloud.im.param.AgreeToMultiMeetParam;
import com.wecloud.im.param.HeartbeatMultiMeetParam;
import com.wecloud.im.param.InviteToMultiMeetParam;
import com.wecloud.im.param.LeaveFromMultiMeetParam;
import com.wecloud.im.param.NotAnsweredMultiMeetParam;
import com.wecloud.im.param.RejectToMultiMeetParam;
import com.wecloud.im.sdk.enums.MultiRtcMemberStateEnum;
import com.wecloud.im.sdk.enums.MultiRtcRoomStateEnum;
import com.wecloud.im.service.ImClientService;
import com.wecloud.im.service.ImMultiRtcRoomMemberService;
import com.wecloud.im.service.ImMultiRtcRoomService;
import com.wecloud.im.ws.cache.UserStateCacheManager;
import com.wecloud.multimeet.entity.dto.RoomMemberDto;
import com.wecloud.multimeet.entity.response.MultiMeetAgreeResponse;
import com.wecloud.multimeet.entity.response.MultiMeetBusyResponse;
import com.wecloud.multimeet.entity.response.MultiMeetInviteResponse;
import com.wecloud.multimeet.entity.response.MultiMeetLeaveResponse;
import com.wecloud.multimeet.entity.response.MultiMeetRejectResponse;
import com.wecloud.multimeet.service.MultiMeetService;
import com.wecloud.multimeet.service.WsMultiMeetWrite;
import com.wecloud.utils.SnowflakeUtil;

/**
 * @Author wenzhida
 * @Date 2022/3/7 18:43
 * @Description 多人音视频接口实现
 */
@Slf4j
@Service
public class MultiMeetServiceImpl implements MultiMeetService {

    @Autowired
    private WsMultiMeetWrite wsMultiMeetWrite;

    @Autowired
    private ImClientService imClientService;

    @Autowired
    private UserStateCacheManager userStateCacheManager;

    @Autowired
    private ImMultiRtcRoomService imMultiRtcRoomService;

    @Autowired
    private ImMultiRtcRoomMemberService imMultiRtcRoomMemberService;

    @Autowired
    private ImClientBlacklistService imClientBlacklistService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void invite(InviteToMultiMeetParam param) {
        ImClient currentClient = imClientService.getCurrentClient();
        //  判断发起方必须在线
        boolean onlineStatus = userStateCacheManager.isOnline(currentClient.getId());
        if (!onlineStatus) {
            log.info("发起方必须在线" + currentClient.getFkAppid() + currentClient.getClientId());
        }
        List<RoomMemberDto> members = Lists.newArrayList();
        ImMultiRtcRoom rtcRoom = imMultiRtcRoomService.getCachedMultiRtcRoom(currentClient.getFkAppid(), param.getRoomId());
        List<ImMultiRtcRoomMember> roomMembersToSave = Lists.newArrayList();
        if (rtcRoom == null) {
            rtcRoom = new ImMultiRtcRoom();
            rtcRoom.setId(SnowflakeUtil.getId());
            rtcRoom.setFkAppid(currentClient.getFkAppid());
            rtcRoom.setRoomId(param.getRoomId());
            rtcRoom.setState(MultiRtcRoomStateEnum.CREATED.getCode());
            rtcRoom.setCreateTime(new Date());
            rtcRoom.setUpdateTime(new Date());
            imMultiRtcRoomService.save(rtcRoom);
            // 将邀请人存入房间成员
            ImMultiRtcRoomMember roomMember = new ImMultiRtcRoomMember();
            roomMember.setId(SnowflakeUtil.getId());
            roomMember.setFkRtcRoomId(rtcRoom.getId());
            roomMember.setRoomId(param.getRoomId());
            roomMember.setFkClientId(currentClient.getId());
            roomMember.setClientId(currentClient.getClientId());
            roomMember.setState(MultiRtcMemberStateEnum.ANSWERED.getCode());
            roomMember.setCallTime(new Date());
            roomMember.setHeartbeatTime(new Date());
            roomMember.setCreateTime(new Date());
            roomMember.setUpdateTime(new Date());
            roomMembersToSave.add(roomMember);
        } else {
            // 取得历史成员列表
            List<ImMultiRtcRoomMember> rtcRoomMemberList = imMultiRtcRoomMemberService.list(
                    new QueryWrapper<ImMultiRtcRoomMember>().lambda()
                            .eq(ImMultiRtcRoomMember::getFkRtcRoomId, rtcRoom.getId())
                            .ne(ImMultiRtcRoomMember::getClientId, currentClient.getClientId())
            );
            for (ImMultiRtcRoomMember imMultiRtcRoomMember : rtcRoomMemberList) {
                RoomMemberDto roomMemberDto = new RoomMemberDto();
                roomMemberDto.setFkClientId(imMultiRtcRoomMember.getFkClientId());
                roomMemberDto.setClientId(imMultiRtcRoomMember.getClientId());
                roomMemberDto.setState(imMultiRtcRoomMember.getState());
                members.add(roomMemberDto);
            }
        }
        for (String toClientId : param.getToClients()) {
            ImClient toClient = imClientService.getCacheImClient(currentClient.getFkAppid(), toClientId);
            if (toClient == null) {
                log.info("查无接收人信息 {}", toClientId);
                continue;
            }
            // 拉黑逻辑
            if (black(currentClient, toClient)) {
                continue;
            }
            // 占线 通知邀请方
            List<ImMultiRtcRoomMember> busyMember = imMultiRtcRoomMemberService.list(new QueryWrapper<ImMultiRtcRoomMember>().lambda()
                    .eq(ImMultiRtcRoomMember::getClientId, toClientId)
                    .in(ImMultiRtcRoomMember::getState, Lists.newArrayList(MultiRtcMemberStateEnum.ANSWERED.getCode())));
            if (CollectionUtils.isNotEmpty(busyMember)) {
                MultiMeetBusyResponse multiMeetBusyResponse = new MultiMeetBusyResponse();
                multiMeetBusyResponse.setConversationId(param.getConversationId());
                multiMeetBusyResponse.setRoomId(param.getRoomId());
                multiMeetBusyResponse.setClientId(currentClient.getClientId());
                multiMeetBusyResponse.setBusyClientId(busyMember.get(0).getClientId());
                multiMeetBusyResponse.setTimestamp(System.currentTimeMillis());
                wsMultiMeetWrite.busy(multiMeetBusyResponse, currentClient.getId());
                continue;
            }
            ImMultiRtcRoomMember rtcRoomMember = imMultiRtcRoomMemberService.getOne(new QueryWrapper<ImMultiRtcRoomMember>().lambda()
                    .eq(ImMultiRtcRoomMember::getClientId, toClientId)
                    .eq(ImMultiRtcRoomMember::getRoomId, param.getRoomId()));
            if (rtcRoomMember != null) {
                // 该房间已邀请过一次，再次发起邀请
                rtcRoomMember.setState(MultiRtcMemberStateEnum.CALLING.getCode());
                rtcRoomMember.setCallTime(new Date());
                imMultiRtcRoomMemberService.updateById(rtcRoomMember);
                continue;
            }
            ImMultiRtcRoomMember roomMember = new ImMultiRtcRoomMember();
            roomMember.setId(SnowflakeUtil.getId());
            roomMember.setFkRtcRoomId(rtcRoom.getId());
            roomMember.setRoomId(param.getRoomId());
            roomMember.setFkClientId(toClient.getId());
            roomMember.setClientId(toClient.getClientId());
            roomMember.setState(MultiRtcMemberStateEnum.CALLING.getCode());
            roomMember.setCallTime(new Date());
            roomMember.setCreateTime(new Date());
            roomMember.setUpdateTime(new Date());
            roomMembersToSave.add(roomMember);
            RoomMemberDto roomMemberDto = new RoomMemberDto();
            roomMemberDto.setFkClientId(toClient.getId());
            roomMemberDto.setClientId(toClient.getClientId());
            roomMemberDto.setState(MultiRtcMemberStateEnum.CALLING.getCode());
            members.add(roomMemberDto);
        }
        imMultiRtcRoomMemberService.saveBatch(roomMembersToSave);
        for (RoomMemberDto member : members) {
            // ws向接收方发送通知
            MultiMeetInviteResponse multiMeetInviteResponse = new MultiMeetInviteResponse();
            multiMeetInviteResponse.setConversationId(param.getConversationId());
            multiMeetInviteResponse.setRoomId(param.getRoomId());
            multiMeetInviteResponse.setClientId(currentClient.getClientId());
            multiMeetInviteResponse.setTimestamp(System.currentTimeMillis());
            multiMeetInviteResponse.setMemberDtoList(members);
            wsMultiMeetWrite.invite(multiMeetInviteResponse, member.getFkClientId());

            // TODO 待开发 下发安卓和ios系统推送
        }
    }

    @Override
    public void reject(RejectToMultiMeetParam param) {
        ImClient currentClient = imClientService.getCurrentClient();
        imMultiRtcRoomMemberService.changeRoomMemberState(currentClient.getId(), param.getRoomId(), MultiRtcMemberStateEnum.REJECTED.getCode());
        List<ImMultiRtcRoomMember> rtcRoomMemberList = imMultiRtcRoomMemberService.list(
                new QueryWrapper<ImMultiRtcRoomMember>().lambda()
                        .eq(ImMultiRtcRoomMember::getRoomId, param.getRoomId())
                        .in(ImMultiRtcRoomMember::getState, Lists.newArrayList(MultiRtcMemberStateEnum.ANSWERED.getCode(), MultiRtcMemberStateEnum.CALLING.getCode()))
                        .ne(ImMultiRtcRoomMember::getClientId, currentClient.getClientId())
        );
        for (ImMultiRtcRoomMember imMultiRtcRoomMember : rtcRoomMemberList) {
            // ws向接收方发送通知
            MultiMeetRejectResponse multiMeetRejectResponse = new MultiMeetRejectResponse();
            multiMeetRejectResponse.setConversationId(param.getConversationId());
            multiMeetRejectResponse.setRoomId(param.getRoomId());

            multiMeetRejectResponse.setClientId(currentClient.getClientId());
            multiMeetRejectResponse.setTimestamp(System.currentTimeMillis());

            ImClient receiver = imClientService.getCacheImClient(currentClient.getFkAppid(), imMultiRtcRoomMember.getClientId());
            if (receiver == null) {
                log.info("查无接收人信息 {}", imMultiRtcRoomMember.getClientId());
                throw new BusinessException("查无发起会议人信息");
            }
            wsMultiMeetWrite.reject(multiMeetRejectResponse, receiver.getId());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void agree(AgreeToMultiMeetParam param) {
        ImClient currentClient = imClientService.getCurrentClient();
        ImMultiRtcRoom rtcRoom = imMultiRtcRoomService.getCachedMultiRtcRoom(currentClient.getFkAppid(), param.getRoomId());
        if (rtcRoom == null) {
            throw new BusinessException("查无多人音视频房间");
        }
        if (MultiRtcRoomStateEnum.CREATED.getCode().equals(rtcRoom.getState())) {
            imMultiRtcRoomService.deleteMultiRtcRoomCache(currentClient.getFkAppid(), param.getRoomId());
            imMultiRtcRoomService.updateMultiRtcRoomState(param.getRoomId(), MultiRtcRoomStateEnum.MEETING.getCode());
        }
        imMultiRtcRoomMemberService.changeRoomMemberState(currentClient.getId(), param.getRoomId(), MultiRtcMemberStateEnum.ANSWERED.getCode());
        List<ImMultiRtcRoomMember> rtcRoomMemberList = imMultiRtcRoomMemberService.list(
                new QueryWrapper<ImMultiRtcRoomMember>().lambda()
                        .eq(ImMultiRtcRoomMember::getRoomId, param.getRoomId())
                        .in(ImMultiRtcRoomMember::getState, Lists.newArrayList(MultiRtcMemberStateEnum.ANSWERED.getCode(), MultiRtcMemberStateEnum.CALLING.getCode()))
                        .ne(ImMultiRtcRoomMember::getClientId, currentClient.getClientId())
        );
        for (ImMultiRtcRoomMember imMultiRtcRoomMember : rtcRoomMemberList) {
            // ws向接收方发送通知
            MultiMeetAgreeResponse multiMeetAgreeResponse = new MultiMeetAgreeResponse();
            multiMeetAgreeResponse.setConversationId(param.getConversationId());
            multiMeetAgreeResponse.setRoomId(param.getRoomId());

            multiMeetAgreeResponse.setClientId(currentClient.getClientId());
            multiMeetAgreeResponse.setTimestamp(System.currentTimeMillis());
            ImClient receiver = imClientService.getCacheImClient(currentClient.getFkAppid(), imMultiRtcRoomMember.getClientId());
            if (receiver == null) {
                log.info("查无接收人信息 {}", imMultiRtcRoomMember.getClientId());
                throw new BusinessException("查无发起会议人信息");
            }
            wsMultiMeetWrite.agree(multiMeetAgreeResponse, receiver.getId());
        }

    }

    @Override
    public void leave(LeaveFromMultiMeetParam param) {
        ImClient currentClient = imClientService.getCurrentClient();
        imMultiRtcRoomMemberService.changeRoomMemberState(currentClient.getId(), param.getRoomId(), MultiRtcMemberStateEnum.LEAVE.getCode());
        List<ImMultiRtcRoomMember> rtcRoomMemberList = imMultiRtcRoomMemberService.list(
                new QueryWrapper<ImMultiRtcRoomMember>().lambda()
                        .eq(ImMultiRtcRoomMember::getRoomId, param.getRoomId())
                        .in(ImMultiRtcRoomMember::getState, Lists.newArrayList(MultiRtcMemberStateEnum.ANSWERED.getCode(), MultiRtcMemberStateEnum.CALLING.getCode()))
                        .ne(ImMultiRtcRoomMember::getClientId, currentClient.getClientId())
        );
        if (CollectionUtils.isEmpty(rtcRoomMemberList)) {
            // 房间中无已接听人员，将呼叫中的成员置为“未接听”
            imMultiRtcRoomMemberService.update(new UpdateWrapper<ImMultiRtcRoomMember>().lambda()
                    .eq(ImMultiRtcRoomMember::getRoomId, param.getRoomId())
                    .eq(ImMultiRtcRoomMember::getState, MultiRtcMemberStateEnum.CALLING.getCode())
                    .set(ImMultiRtcRoomMember::getState, MultiRtcMemberStateEnum.NOT_ANSWERED.getCode()));
        }
        for (ImMultiRtcRoomMember imMultiRtcRoomMember : rtcRoomMemberList) {
            // ws向接收方发送通知
            MultiMeetLeaveResponse multiMeetLeaveResponse = new MultiMeetLeaveResponse();
            multiMeetLeaveResponse.setConversationId(param.getConversationId());
            multiMeetLeaveResponse.setRoomId(param.getRoomId());

            multiMeetLeaveResponse.setClientId(currentClient.getClientId());
            multiMeetLeaveResponse.setTimestamp(System.currentTimeMillis());
            ImClient receiver = imClientService.getCacheImClient(currentClient.getFkAppid(), imMultiRtcRoomMember.getClientId());
            if (receiver == null) {
                log.info("查无接收人信息 {}", imMultiRtcRoomMember.getClientId());
                throw new BusinessException("查无发起会议人信息");
            }
            wsMultiMeetWrite.leave(multiMeetLeaveResponse, receiver.getId());
        }
    }

    @Override
    public void notAnswered(NotAnsweredMultiMeetParam param) {
        ImClient currentClient = imClientService.getCurrentClient();
        imMultiRtcRoomMemberService.changeRoomMemberState(currentClient.getId(), param.getRoomId(), MultiRtcMemberStateEnum.NOT_ANSWERED.getCode());
    }

    @Override
    public void heartbeat(HeartbeatMultiMeetParam param) {
        ImClient currentClient = imClientService.getCurrentClient();
        imMultiRtcRoomMemberService.update(new UpdateWrapper<ImMultiRtcRoomMember>().lambda()
                .eq(ImMultiRtcRoomMember::getRoomId, param.getRoomId())
                .eq(ImMultiRtcRoomMember::getFkClientId, currentClient.getId())
                .set(ImMultiRtcRoomMember::getHeartbeatTime, new Date()));
    }

    @Override
    public void isBeCalling() {
        ImClient currentClient = imClientService.getCurrentClient();
        // 查询当前用户是否被呼叫
        List<ImMultiRtcRoomMember> roomMembers = imMultiRtcRoomMemberService.list(
                new QueryWrapper<ImMultiRtcRoomMember>().lambda()
                        .lt(ImMultiRtcRoomMember::getClientId, currentClient.getClientId())
                        .eq(ImMultiRtcRoomMember::getState, MultiRtcMemberStateEnum.CALLING.getCode()));
        if (CollectionUtils.isNotEmpty(roomMembers)) {
            ImMultiRtcRoom room = imMultiRtcRoomService.getCachedMultiRtcRoom(currentClient.getFkAppid(), roomMembers.get(0).getRoomId());
            if (room == null) {
                return;
            }
            List<ImMultiRtcRoomMember> answeredMembers = imMultiRtcRoomMemberService.list(
                    new QueryWrapper<ImMultiRtcRoomMember>().lambda()
                            .lt(ImMultiRtcRoomMember::getClientId, currentClient.getClientId())
                            .in(ImMultiRtcRoomMember::getState, Lists.newArrayList(MultiRtcMemberStateEnum.ANSWERED.getCode(), MultiRtcMemberStateEnum.ANSWERED.getCode())));

            List<RoomMemberDto> members = Lists.newArrayList();
            if (CollectionUtils.isNotEmpty(answeredMembers)) {
                for (ImMultiRtcRoomMember answeredMember : answeredMembers) {
                    RoomMemberDto memberDto = new RoomMemberDto();
                    memberDto.setFkClientId(answeredMember.getFkClientId());
                    memberDto.setClientId(answeredMember.getClientId());
                    memberDto.setState(answeredMember.getState());
                    members.add(memberDto);
                }
            }
            // ws向接收方发送通知
            MultiMeetInviteResponse multiMeetInviteResponse = new MultiMeetInviteResponse();
            multiMeetInviteResponse.setRoomId(roomMembers.get(0).getRoomId());
            multiMeetInviteResponse.setClientId(currentClient.getClientId());
            multiMeetInviteResponse.setTimestamp(System.currentTimeMillis());
            multiMeetInviteResponse.setMemberDtoList(members);
            wsMultiMeetWrite.invite(multiMeetInviteResponse, roomMembers.get(0).getFkClientId());
        }
    }

    @Override
    public void callingTimeout() {
        // 60 秒之前的时间
        Date beforeTime = DateUtils.addSeconds(new Date(), -60);
        // 获取呼叫时间为120秒之前 并且状态为呼叫中的会议成员
        List<ImMultiRtcRoomMember> rtcRoomMemberList = imMultiRtcRoomMemberService.list(
                new QueryWrapper<ImMultiRtcRoomMember>().lambda()
                        .lt(ImMultiRtcRoomMember::getCallTime, beforeTime)
                        .eq(ImMultiRtcRoomMember::getState, MultiRtcMemberStateEnum.CALLING.getCode())
        );
        if (CollectionUtils.isEmpty(rtcRoomMemberList)) {
            return;
        }
        for (ImMultiRtcRoomMember roomMember : rtcRoomMemberList) {
            roomMember.setState(MultiRtcMemberStateEnum.NOT_ANSWERED.getCode());
        }
        imMultiRtcRoomMemberService.updateBatchById(rtcRoomMemberList);
    }

    @Override
    public void disconnect() {
        // 20 秒之前的时间
        Date beforeTime = DateUtils.addSeconds(new Date(), -20);
        // 获取最后心跳时间为20秒之前 并且状态为接听中的会议成员
        List<ImMultiRtcRoomMember> rtcRoomMemberList = imMultiRtcRoomMemberService.list(
                new QueryWrapper<ImMultiRtcRoomMember>().lambda()
                        .lt(ImMultiRtcRoomMember::getHeartbeatTime, beforeTime)
                        .eq(ImMultiRtcRoomMember::getState, MultiRtcMemberStateEnum.ANSWERED.getCode())
        );
        if (CollectionUtils.isEmpty(rtcRoomMemberList)) {
            return;
        }
        for (ImMultiRtcRoomMember roomMember : rtcRoomMemberList) {
            roomMember.setState(MultiRtcMemberStateEnum.DISCONNECT.getCode());
        }
        imMultiRtcRoomMemberService.updateBatchById(rtcRoomMemberList);
    }

    /**
     * 拉黑逻辑
     *
     * @param currentClient
     * @param toClient
     * @return
     */
    private boolean black(ImClient currentClient, ImClient toClient) {
        // 判断是否被拉黑
        boolean beBlack = imClientBlacklistService.isBeBlack(toClient.getClientId(), currentClient.getClientId());
        if (beBlack) {
            log.info("被对方拉黑了 currentClient: {} toClient: {} ", JSON.toJSONString(currentClient), JSON.toJSONString(toClient));
            return true;
        }
        // 是否把对方拉黑
        boolean black = imClientBlacklistService.isBeBlack(currentClient.getClientId(), toClient.getClientId());
        if (black) {
            log.info("你把对方拉黑了 currentClient: {} toClient: {} ", JSON.toJSONString(currentClient), JSON.toJSONString(toClient));
            return true;
        }
        return false;
    }

}
