package com.bcxin.tenant.open.domains.services.impls;

import com.bcxin.tenant.open.document.domains.documents.InstantActivityDataDocument;
import com.bcxin.tenant.open.document.domains.documents.NotifyMessageDocument;
import com.bcxin.tenant.open.document.domains.documents.RdEmployeeDocument;
import com.bcxin.tenant.open.document.domains.documents.RoomDocument;
import com.bcxin.tenant.open.document.domains.documents.messages.RoomUserActiveMessageContentDocument;
import com.bcxin.tenant.open.document.domains.repositories.InstantActivityDataDocumentRepository;
import com.bcxin.tenant.open.document.domains.repositories.NotifyMessageDocumentRepository;
import com.bcxin.tenant.open.document.domains.repositories.RdEmployeeDocumentRepository;
import com.bcxin.tenant.open.document.domains.repositories.RoomDocumentRepository;
import com.bcxin.tenant.open.domains.entities.RoomEntity;
import com.bcxin.tenant.open.domains.entities.RoomUserEntity;
import com.bcxin.tenant.open.domains.entities.TenantUserView;
import com.bcxin.tenant.open.domains.repositories.RoomRepository;
import com.bcxin.tenant.open.domains.repositories.TenantUserViewRepository;
import com.bcxin.tenant.open.domains.services.RoomService;
import com.bcxin.tenant.open.domains.services.commands.*;
import com.bcxin.tenant.open.infrastructures.TenantContext;
import com.bcxin.tenant.open.infrastructures.TenantEmployeeContext;
import com.bcxin.tenant.open.infrastructures.UnitWork;
import com.bcxin.tenant.open.infrastructures.components.IdWorker;
import com.bcxin.tenant.open.infrastructures.components.JsonProvider;
import com.bcxin.tenant.open.infrastructures.configs.TenantSystemConfig;
import com.bcxin.tenant.open.infrastructures.constants.KafkaConstants;
import com.bcxin.tenant.open.infrastructures.enums.DispatchReasonType;
import com.bcxin.tenant.open.infrastructures.enums.InstantDataType;
import com.bcxin.tenant.open.infrastructures.enums.NotifyMessageType;
import com.bcxin.tenant.open.infrastructures.exceptions.BadTenantException;
import com.bcxin.tenant.open.infrastructures.exceptions.NoFoundTenantException;
import com.bcxin.tenant.open.infrastructures.exceptions.UnAuthorizedTenantException;
import com.bcxin.tenant.open.infrastructures.snapshoots.RoomDispatchSnapshoot;
import com.bcxin.tenant.open.infrastructures.utils.StringUtil;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class RoomServiceImpl implements RoomService {
    private static final Logger logger = LoggerFactory.getLogger(RoomServiceImpl.class);

    private final RdEmployeeDocumentRepository rdEmployeeDocumentRepository;
    private final NotifyMessageDocumentRepository notifyMessageDocumentRepository;
    private final RoomDocumentRepository roomDocumentRepository;
    private final RoomRepository roomRepository;
    private final IdWorker idWorker;
    private final UnitWork unitWork;
    private final RedisTemplate redisTemplate;
    private final TenantUserViewRepository tenantUserViewRepository;
    private final JsonProvider jsonProvider;
    private final KafkaTemplate kafkaTemplate;
    private final TenantSystemConfig tenantSystemConfig;
    private final InstantActivityDataDocumentRepository instantActivityDataDocumentRepository;


    public RoomServiceImpl(RdEmployeeDocumentRepository rdEmployeeDocumentRepository,
                           NotifyMessageDocumentRepository notifyMessageDocumentRepository,
                           RoomDocumentRepository roomDocumentRepository,
                           RoomRepository roomRepository,
                           IdWorker idWorker, UnitWork unitWork,
                           RedisTemplate redisTemplate,
                           TenantUserViewRepository tenantUserViewRepository,
                           JsonProvider jsonProvider,
                           KafkaTemplate kafkaTemplate,
                           TenantSystemConfig tenantSystemConfig,
                           InstantActivityDataDocumentRepository instantActivityDataDocumentRepository) {
        this.rdEmployeeDocumentRepository = rdEmployeeDocumentRepository;
        this.notifyMessageDocumentRepository = notifyMessageDocumentRepository;
        this.roomDocumentRepository = roomDocumentRepository;
        this.roomRepository = roomRepository;
        this.idWorker = idWorker;
        this.unitWork = unitWork;
        this.redisTemplate = redisTemplate;
        this.tenantUserViewRepository = tenantUserViewRepository;
        this.jsonProvider = jsonProvider;
        this.kafkaTemplate = kafkaTemplate;
        this.tenantSystemConfig = tenantSystemConfig;
        this.instantActivityDataDocumentRepository = instantActivityDataDocumentRepository;
    }

    @Override
    public CreateRoomCommandResponse dispatch(CreateRoomCommand command) {
        TenantEmployeeContext.TenantUserModel userModel = TenantContext.getInstance().getUserContext().get();
        if (userModel == null) {
            throw new UnAuthorizedTenantException();
        }

        String tranId = this.unitWork.beginTransaction();
        Long roomId = getNextId();
        Collection<CreateRoomCommandResponse.CreateRoomCommandItem> roomItems = new ArrayList<>();
        try {
            Collection<String> employeeIds
                    = command.getRoomUsers().stream().map(ix -> ix.getEmployeeId())
                    .collect(Collectors.toList());
            Collection<RdEmployeeDocument> employeeDocuments
                    = this.rdEmployeeDocumentRepository.findAllById(employeeIds);

            Collection<RoomDocument.RoomUserDocument> roomUserDocuments = new ArrayList<>();
            Collection<NotifyMessageDocument> notifyMessageDocuments = new ArrayList<>();
            RoomEntity room = RoomEntity.create(
                    roomId,
                    command.getCommunicatedType(),
                    command.getYardmanType(),
                    userModel.getEmployeeId(),
                    userModel.getOrganizationId(),
                    command.getReferenceType(),
                    command.getReferenceNumber(),
                    command.getDeskType()
            );

            Collection<String> tenantUserIds =
                    employeeDocuments.stream().map(ix -> ix.getTenantUserId())
                            .distinct()
                            .collect(Collectors.toList());
            Collection<TenantUserView> tenantUserViews =
                    tenantUserViewRepository.getAllByIds(tenantUserIds);

            command.getRoomUsers().forEach(ru -> {
                addRoomUsers(
                        notifyMessageDocuments, employeeDocuments,
                        roomUserDocuments, room, tenantUserViews, ru,
                        userModel
                );
            });

            handleInvalidRoomUsers(roomUserDocuments);

            RoomDocument document = RoomDocument.create(roomId,
                    room.getDeskType(),
                    room.getOrganizationId(),
                    room.getEmployeeId(),
                    room.getCommunicatedType(),
                    room.getYardmanType(),
                    room.getReferenceType(),
                    room.getReferenceNumber(),
                    roomUserDocuments
            );

            /**
             * 更新激活的房间文档
             */
            this.notifyMessageDocumentRepository.saveAll(notifyMessageDocuments);
            this.roomDocumentRepository.save(document);
            this.roomRepository.insert(room);

            /**
             * 针对督导点名的时候; 通过消息队列来触发标记
             */
            if(room.getReferenceType()== DispatchReasonType.RollCall) {
                /**
                 * 针对督导点名的时候; 将该数据放入redis中; 当用户收到的时候; 系统自动清除
                 */
                String referenceNumber = String.valueOf(room.getId());
                String rollCallId = room.getReferenceNumber();

                Collection<InstantActivityDataDocument> activityDataDocuments =
                        roomUserDocuments.stream().map(ii ->
                                        InstantActivityDataDocument.create(InstantDataType.Dispatch, referenceNumber, ii.getEmployeeId(), rollCallId))
                                .collect(Collectors.toList());
                instantActivityDataDocumentRepository.saveAll(activityDataDocuments);

                /**
                 * 针对督导的情况, 宁可让督导数据失败也要保证督导操作是正常的.
                 */
                try {
                    RoomDispatchSnapshoot roomDispatchPoJo
                            = RoomDispatchSnapshoot.create(
                            room.getId(),
                            room.getReferenceType(),
                            room.getReferenceNumber(),
                            roomUserDocuments.stream().map(ii -> ii.getEmployeeId()).collect(Collectors.toList()));
                    String rollCallRoomJson = this.jsonProvider.getJson(roomDispatchPoJo);
                    String rollCallRoomJsonKey = String.valueOf(roomDispatchPoJo.getReferenceNumber());
                    int partition = Math.abs(rollCallRoomJsonKey.hashCode() % KafkaConstants.PARTITION_COUNT);

                    this.kafkaTemplate.send(KafkaConstants.TOPIC_DISPATCH_ROLL_CALL_EMPLOYEE_ROOM, partition, rollCallRoomJsonKey, rollCallRoomJson);
                } catch (Exception ex) {
                    logger.error("Failed to dispatch TOPIC_DISPATCH_ROLL_CALL_EMPLOYEE_ROOM({}) message to topic", roomId, ex);
                }
            }

            this.unitWork.commit(tranId);

            roomItems = roomUserDocuments.stream().map(ii ->
                    {
                        CreateRoomCommandResponse.CreateRoomCommandItem commandItem =
                                CreateRoomCommandResponse.CreateRoomCommandItem.create(
                                        ii.getEmployeeId(),
                                        ii.getTenantUserId(),
                                        ii.getCid());
                        return commandItem;
                    }
            ).collect(Collectors.toList());
        } catch (Exception ex) {
            this.unitWork.rollback(tranId);
            throw ex;
        }

        return CreateRoomCommandResponse.create(roomId, roomItems);
    }

    @Override
    public InviteRoomUserCommandResponse dispatch(InviteRoomUserCommand command) {
        TenantEmployeeContext.TenantUserModel userModel = TenantContext.getInstance().getUserContext().get();
        if (userModel == null) {
            throw new UnAuthorizedTenantException();
        }
        Optional<RoomDocument> roomDocumentOptional = roomDocumentRepository.findById(command.getRoomId());
        if (!roomDocumentOptional.isPresent()) {
            throw new NoFoundTenantException("房间无效!");
        }

        String tranId = this.unitWork.beginTransaction();

        RoomDocument roomDocument = roomDocumentOptional.get();
        Collection<InviteRoomUserCommandResponse.InviteUsersCommandResponseItem>
                roomUserItems =
                new ArrayList<>();
        try {
            Collection<String> employeeIds
                    = command.getRoomUsers().stream().map(ix -> ix.getEmployeeId()).collect(Collectors.toList());
            Collection<RdEmployeeDocument> employeeDocuments
                    = this.rdEmployeeDocumentRepository.findAllById(employeeIds);

            Collection<RoomDocument.RoomUserDocument> roomUserDocuments = new ArrayList<>();
            Collection<NotifyMessageDocument> notifyMessageDocuments = new ArrayList<>();


            Collection<String> tenantUserIds =
                    employeeDocuments.stream().map(ix -> ix.getTenantUserId())
                            .distinct()
                            .collect(Collectors.toList());
            Collection<TenantUserView> tenantUserViews =
                    tenantUserViewRepository.getAllByIds(tenantUserIds);

            for(InviteRoomUserCommand.InviteUserCommandItem ru:command.getRoomUsers()) {
                addInviteRoomUsers(notifyMessageDocuments, employeeDocuments,
                        roomUserDocuments, roomDocument, tenantUserViews, ru, userModel.getTencentUserId()
                );
            }

            handleInvalidRoomUsers(roomUserDocuments);

            /**
             * RoomUserDocument存储的是房间内部的人员列表; 因此, 同一个人只存储一条数据; 该数据将定期通过TTL自动清除,
             * 因此, 不是非常重要; 主要用于调度的时候显示当前会议的人员列表
             */
            Collection<RoomDocument.RoomUserDocument> newInviteRoomUserDoc =
                    roomUserDocuments.stream()
                            .filter(ix -> (roomDocument.getRoomUsers().stream().allMatch(rd -> !rd.getEmployeeId().equals(ix.getEmployeeId()))))
                            .collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(newInviteRoomUserDoc)) {
                roomDocument.getRoomUsers().addAll(newInviteRoomUserDoc);
                this.roomDocumentRepository.save(roomDocument);
            }

            /**
             * 所有再次邀请的人员全部都加入到房间中; 后续用于计算加入但是未接听的人员信息
             */
            Collection<RoomUserEntity> roomUserEntities =
                    roomUserDocuments.stream()
                            .map(ix -> RoomUserEntity.create(
                                    command.getRoomId(),
                                    idWorker.getNextId(),
                                    ix.getEmployeeId(),
                                    ix.getTenantUserId(),
                                    ix.getCid(),
                                    ix.getEmployeeName(),
                                    ix.getOrganizationId(),
                                    ix.getOrganizationName(),
                                    ix.getTencentUserId(),
                                    ix.getSecurityStationId(),
                                    ix.getSecurityStationName(),
                                    ix.getSuperviseDepartId(),
                                    ix.isSponsor(),
                                    false)
                            ).collect(Collectors.toList());
            this.roomRepository.addRoomUsers(roomUserEntities);

            this.notifyMessageDocumentRepository.saveAll(notifyMessageDocuments);

            this.unitWork.commit(tranId);

            roomUserItems = roomUserDocuments.stream().map(ii ->
                    {
                        InviteRoomUserCommandResponse.InviteUsersCommandResponseItem commandItem =
                                InviteRoomUserCommandResponse.InviteUsersCommandResponseItem.create(
                                        ii.getEmployeeId(),
                                        ii.getEmployeeName(),
                                        ii.getTenantUserId(),
                                        ii.getCid());
                        return commandItem;
                    }
            ).collect(Collectors.toList());

        } catch (Exception ex) {
            this.unitWork.rollback(tranId);
            throw ex;
        }

        return InviteRoomUserCommandResponse.create(command.getRoomId(), roomUserItems);
    }

    @Override
    public void dispatch(CloseRoomCommand command) {
        Optional<RoomDocument> roomDocumentOptional =
                this.roomDocumentRepository.findById(command.getId());

        if (!roomDocumentOptional.isPresent()) {
            throw new NoFoundTenantException("该房间无效");
        }

        RoomDocument document = roomDocumentOptional.get();
        if (!command.getOrganizationId().equalsIgnoreCase(document.getOrganizationId())) {
            throw new NoFoundTenantException("该企业未创建该房间");
        }

        document.leave();
        this.roomDocumentRepository.save(document);

        RoomEntity room = this.roomRepository.getById(command.getId());
        if (room != null) {
            String tranId = this.unitWork.beginTransaction();
            try {
                room.leave();

                this.roomRepository.update(room);
                this.unitWork.commit(tranId);
            } catch (Exception ex) {
                this.unitWork.rollback(tranId);
                logger.error("解散房间发生异常:{}", ex);
            }
        }
    }

    @Override
    public void dispatch(RemoveUserCommand command) {
        Optional<RoomDocument> roomDocumentOptional =
                this.roomDocumentRepository.findById(command.getRoomId());

        if (!roomDocumentOptional.isPresent()) {
            throw new NoFoundTenantException("该房间无效");
        }
        RoomDocument roomDocument = roomDocumentOptional.get();
        if(CollectionUtils.isEmpty(roomDocument.getRoomUsers())){
            throw new NoFoundTenantException("该房间无效，无参会人员");
        }
        Collection<RoomDocument.RoomUserDocument> roomUserDocuments = roomDocument.getRoomUsers();
        if(!roomUserDocuments.stream().anyMatch(ix-> ix.getTencentUserId().equals(command.getTencentUserId()))){
            throw new NoFoundTenantException("该人员不在会议房间内");
        }
        Optional<RoomDocument.RoomUserDocument> roomUserOptional = roomUserDocuments.stream().filter(ix-> ix.getTencentUserId().equals(command.getTencentUserId())).findFirst();
        if(roomUserOptional.isPresent()){
            String transId = this.unitWork.beginTransaction();
            try{
                this.roomRepository.removeUsers(command.getRoomId(), Lists.newArrayList(command.getTencentUserId()));
                roomUserDocuments.remove(roomUserOptional.get());
                this.roomDocumentRepository.save(roomDocument);
                this.unitWork.commit(transId);
            }catch (Exception ex){
                this.unitWork.rollback(transId);
                logger.error("移除人员异常：{}",ex);
            }

        }
    }

    private Long getNextId() {
        String prefixRoomNo = tenantSystemConfig.getPrefixRoomNo();
        SimpleDateFormat dateFormat = new SimpleDateFormat("MMdd");
        String value = dateFormat.format(new Date());
        String key = String.format("room-%s-%s", prefixRoomNo, value);
        Long incValue =
                this.redisTemplate
                        .opsForValue()
                        .increment(key);
        if (incValue == 1) {
            this.redisTemplate.expire(key, 2, TimeUnit.DAYS);
        }
        String finalValue = value.concat(StringUtil.leftPad(incValue.toString(), 3));
        return Long.parseLong(String.format("1%s%s", prefixRoomNo, finalValue));
    }

    private void handleInvalidRoomUsers(Collection<RoomDocument.RoomUserDocument> roomUserDocuments) {
        Collection<RoomDocument.RoomUserDocument> invalidRoomUsers =
                roomUserDocuments.stream()
                        .filter(ix -> !StringUtils.hasLength(ix.getTencentUserId()))
                        .collect(Collectors.toList());

        if (!CollectionUtils.isEmpty(invalidRoomUsers)) {
            if (invalidRoomUsers.stream().anyMatch(ix -> ix.isSponsor())) {
                throw new BadTenantException(String.format("该用户(%s)无调度权限",
                        invalidRoomUsers.stream().map(ix -> ix.getEmployeeName())
                                .findFirst().get()));
            }

            String dispatchedUserNames =
                    invalidRoomUsers.stream()
                            .map(ii -> String.format("%s-%s(%s)",
                                    ii.getOrganizationName(), ii.getEmployeeName(),
                                    (ii.isSponsor() ? "调度台" : "被调度人")
                            ))
                            .collect(Collectors.joining(";"));
            if (StringUtils.hasLength(dispatchedUserNames)) {
                throw new BadTenantException(String.format("%s 未登入过app, 因此, 无法进行调度", dispatchedUserNames));
            }
        }
    }

    private void addRoomUsers(
            Collection<NotifyMessageDocument> notifyMessageDocuments,
            Collection<RdEmployeeDocument> employeeDocuments,
            Collection<RoomDocument.RoomUserDocument> roomUserDocuments,
            RoomEntity room, Collection<TenantUserView> tenantUserViews,
            CreateRoomCommand.CreateRoomUserCommand ru,
            TenantEmployeeContext.TenantUserModel userModel) {
        Optional<RdEmployeeDocument> employeeDocumentOptional =
                employeeDocuments.stream()
                        .filter(ix -> ix.getId().equals(ru.getEmployeeId()))
                        .findFirst();

        String employeeName = "#";
        String companyName = "#";
        String stationName = "#";
        String cid = "#";
        String tenantUserId = "#";
        if (employeeDocumentOptional.isPresent()) {
            RdEmployeeDocument employeeDocument = employeeDocumentOptional.get();
            employeeName = employeeDocument.getName();
            companyName = employeeDocument.getCompanyName();
            stationName = employeeDocument.getSecurityStationName();
            tenantUserId = employeeDocument.getTenantUserId();

            Optional<TenantUserView> selectedTenantUserViewOptional
                    = tenantUserViews.stream()
                    .filter(tu -> tu.getId().equalsIgnoreCase(employeeDocument.getTenantUserId()))
                    .findFirst();

            cid = employeeDocument.getCId();
            /**
             * 先忽略非互联网的情况; 这时候只能取互联网的值
             */
            if (selectedTenantUserViewOptional.isPresent() &&
                    !StringUtil.isEmpty(selectedTenantUserViewOptional.get().getCid())) {
                cid = selectedTenantUserViewOptional.get().getCid();
            }
        } else {
            if (ru.isSponsor()) {
                cid = "Sponsor调度人无需Cid";
            } else {
                throw new BadTenantException(String.format("找不到该用户:%s", ru.getEmployeeId()));
            }
        }

        if(StringUtil.isEmpty(cid)) {
            cid = "-";
        }

        String tencentUserId = userModel.getTencentUserId();
        boolean isSuperviseDepartRole = false;
        if (ru.isSponsor()) {
            isSuperviseDepartRole = userModel.isSuperviseDepartRole();
        }

        room.addUser(
                idWorker.getNextId(),
                ru.getEmployeeId(),
                tenantUserId,
                employeeName,
                ru.getOrganizationId(),
                companyName,
                ru.getTencentUserId(),
                ru.getSecurityStationId(),
                stationName,
                ru.getSuperviseDepartId(),
                cid,
                ru.isSponsor(),
                isSuperviseDepartRole
        );

        roomUserDocuments.add(
                RoomDocument.RoomUserDocument.create(
                        ru.getEmployeeId(),
                        tenantUserId,
                        employeeName,
                        ru.getOrganizationId(), companyName, ru.getTencentUserId(), ru.getSecurityStationId(),
                        stationName, ru.getSuperviseDepartId(),
                        cid,
                        ru.isSponsor()
                ));

        /**
         * 发起调度的人不要加入该激活列表; 避免APP不必要的响铃
         */
        if (!ru.isSponsor()) {
            RoomUserActiveMessageContentDocument data =
                    RoomUserActiveMessageContentDocument.create(
                            idWorker.getNextId(),
                            tencentUserId,
                            ru.getEmployeeId(),
                            ru.getTencentUserId(),
                            employeeName,
                            ru.getOrganizationId(),
                            String.valueOf(room.getId()),
                            room.getCommunicatedType(),
                            room.getYardmanType()
                    );

            NotifyMessageDocument messageDocument =
                    NotifyMessageDocument.<RoomUserActiveMessageContentDocument>create(
                            jsonProvider,
                            NotifyMessageType.Dispatch,
                            String.valueOf(data.getId()),
                            String.valueOf(data.getRoomNo()),
                            ru.getEmployeeId(),
                            data
                    );
            notifyMessageDocuments.add(messageDocument);
        }
    }

    private void addInviteRoomUsers(
            Collection<NotifyMessageDocument> notifyMessageDocuments,
            Collection<RdEmployeeDocument> employeeDocuments,
            Collection<RoomDocument.RoomUserDocument> roomUserDocuments,
            RoomDocument room, Collection<TenantUserView> tenantUserViews,
            InviteRoomUserCommand.InviteUserCommandItem ru,
            String tencentUserId) {
        RdEmployeeDocument document = employeeDocuments.stream()
                .filter(ix -> ix.getId().equals(ru.getEmployeeId()))
                .findFirst().orElse(null);
        if (document == null) {
            throw new BadTenantException(String.format("找不到该用户:%s", ru.getEmployeeId()));
        }

        String employeeName = document.getName();
        String companyName = document.getCompanyName();
        String stationName = document.getSecurityStationName();
        String tenantUserId = document.getTenantUserId();

        TenantUserView selectedTenantUserView = tenantUserViews.stream().filter(tu -> tu.getId().equalsIgnoreCase(document.getTenantUserId()))
                .findFirst().orElse(null);

        if (selectedTenantUserView == null) {
            throw new BadTenantException(String.format("系统异常, 请联系用户(%s) 登入APP后在重新调度", document.getName()));
        }

        String cid = selectedTenantUserView.getCid();
        roomUserDocuments.add(RoomDocument.RoomUserDocument.create(
                ru.getEmployeeId(),
                tenantUserId,
                employeeName,
                ru.getOrganizationId(), companyName, ru.getTencentUserId(), ru.getSecurityStationId(),
                stationName, ru.getSuperviseDepartId(),
                cid,
                false
        ));

        Long id = idWorker.getNextId();
        notifyMessageDocuments.add(
                NotifyMessageDocument.create(
                        jsonProvider,
                        NotifyMessageType.Dispatch,
                        String.valueOf(id),
                        String.valueOf(room.getId()),
                        ru.getEmployeeId(),
                        RoomUserActiveMessageContentDocument.create(
                                id,
                                tencentUserId,
                                ru.getEmployeeId(),
                                ru.getTencentUserId(),
                                employeeName,
                                ru.getOrganizationId(),
                                String.valueOf(room.getId()),
                                room.getCommunicatedType(),
                                room.getYardmanType()
                        )
                ));
    }
}
