package com.zsc.edu.gateway.modules.message.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zsc.edu.gateway.framework.message.email.EmailSender;
import com.zsc.edu.gateway.framework.message.sms.SmsSender;
import com.zsc.edu.gateway.framework.security.UserDetailsImpl;
import com.zsc.edu.gateway.modules.message.dto.UserNoticeDto;
import com.zsc.edu.gateway.modules.message.entity.*;
import com.zsc.edu.gateway.modules.message.mapper.NoticeMapper;
import com.zsc.edu.gateway.modules.message.query.AdminNoticeQuery;
import com.zsc.edu.gateway.modules.message.query.UserNoticeQuery;
import com.zsc.edu.gateway.modules.message.repo.NoticeRepository;
import com.zsc.edu.gateway.modules.message.repo.UserNoticeRepository;
import com.zsc.edu.gateway.modules.message.service.UserNoticeService;
import com.zsc.edu.gateway.modules.message.vo.AdminNoticeVo;
import com.zsc.edu.gateway.modules.message.vo.UserNoticeVo;
import com.zsc.edu.gateway.modules.system.entity.User;
import com.zsc.edu.gateway.modules.system.repo.UserRepository;
import lombok.AllArgsConstructor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;


/**
 * 用户消息Service
 *
 * @author harry_yao
 */
@AllArgsConstructor
@Service
public class UserNoticeServiceImpl extends ServiceImpl<UserNoticeRepository, UserNotice> implements UserNoticeService {

    private final NoticeRepository noticeRepo;
    private final EmailSender emailSender;
    private final SmsSender smsSender;
    private final UserRepository userRepository;
    private final NoticeMapper noticeMapper;


    /**
     * 查询消息详情
     *
     * @param noticeId 消息ID
     * @param userId    用户ID
     * @return 查询详情
     */

    @Override
    public UserNoticeVo detail(Long noticeId, Long userId) {
        UserNoticeVo userNoticeVo = baseMapper.selectByNoticeIdAndUserId(noticeId, userId);
        if (userNoticeVo == null) {
            throw new RuntimeException("您输入的信息有误,请检查输入ID信息是否正确");
        }
        return userNoticeVo;
    }

    /**
     * 分页查询用户消息
     *
     * @param query 查询表单
     * @param page  分页参数
     * @return 页面数据
     */
    @Override
    public IPage<UserNoticeVo> page(Page<UserNoticeVo> page, UserNoticeQuery query) {
        return baseMapper.page(page, query);
    }

    /**
     * 统计用户未读消息数量
     *
     * @param userDetails 操作用户
     * @return 未读消息数量
     */
    @Override
    public Integer countUnread(UserDetailsImpl userDetails) {
        LambdaQueryWrapper<UserNotice> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(UserNotice::getUserId, userDetails.getId())
                .eq(UserNotice::getIsRead, true);
        return Math.toIntExact(baseMapper.selectCount(lambdaQueryWrapper));
    }

    /**
     * 设为已读
     *
     * @param userDetails 操作用户
     * @param messageIds  消息ID集合，如为空，则将该用户的所有未读消息设为已读
     * @return 修改记录数量
     */
    @Override
    public boolean markAsRead(UserDetailsImpl userDetails, List<Long> messageIds) {
        if (CollectionUtils.isEmpty(messageIds)) {
            throw new RuntimeException("您输入的集合为空");
        }
        return this.lambdaUpdate().eq(UserNotice::getUserId, userDetails.getId())
                .in(UserNotice::getNoticeId, messageIds)
                .set(UserNotice::getIsRead, true)
                .set(UserNotice::getReadTime, LocalDateTime.now())
                .update();
    }

    /**
     * 全部已读
     */
    @Override
    public boolean markAllAsRead(UserDetailsImpl userDetails) {
        return this.lambdaUpdate().eq(UserNotice::getUserId, userDetails.getId())
                .set(UserNotice::getIsRead, true)
                .set(UserNotice::getReadTime, LocalDateTime.now())
                .update();
    }

    /**
     * 管理员查询消息分页
     *
     * @return 消息设置列表
     */
    @Override
    public IPage<AdminNoticeVo> getAdminNoticePage(Page<AdminNoticeVo> page, AdminNoticeQuery query) {
        return baseMapper.pageAdmin(page, query);
    }


    /**
     *
     * 管理员手动创建用户消息并发送
     *
     * @param dto 表单数据
     * @return 创建的用户消息列表
     */
    @Transactional
    @Override
    public Boolean createByAdmin(UserNoticeDto dto) {
        Set<User> users = new HashSet<>(userRepository.selectList(new LambdaQueryWrapper<User>().in(User::getId, dto.userIds)));
        Notice notice = noticeMapper.toEntity(dto);
        noticeRepo.insert(notice);
        Set<UserNotice> userNotices = users.stream()
                .map(user -> new UserNotice(null, user.getId(), notice.getId(), true, null))
                .collect(Collectors.toSet());
        send(users, notice);
        return saveBatch(userNotices);
    }


    /**
     * 以邮件、短信等形式发送消息，只有非html格式的消息才能以短信方式发送
     *
     * @param users   接收者
     * @param notice 消息
     */
    @Async
    void send(Set<User> users, Notice notice) {
        if (notice.email) {
            emailSender.send(users.stream().map(User::getEmail).collect(Collectors.toSet()), notice);
        }
        if (notice.sms && !notice.html) {
            smsSender.send(users.stream().map(User::getPhone).collect(Collectors.toSet()), notice.content);
        }
    }


    /**
     * 系统自动创建用户消息并发送
     *
     * @param receivers 接收者
     * @param payload   消息内容
     */
    @Transactional
    public Boolean createBySystem(Set<User> receivers, NoticePayload payload) {
        AtomicBoolean email = new AtomicBoolean(false);
        AtomicBoolean sms = new AtomicBoolean(false);
        Optional.of(noticeRepo.selectById(payload.type)).ifPresent(message -> {
            email.set(message.email);
            sms.set(message.sms);
        });
        Notice notice = new Notice(payload.type, true, email.get(), sms.get(),
                payload.html, payload.type.name(), payload.content, null);
        noticeRepo.insert(notice);
        Set<UserNotice> userNotices = receivers.stream().map(user ->
                new UserNotice(null, user.getId(), notice.getId(), true, null)).collect(Collectors.toSet());
        send(receivers, notice);
        return saveBatch(userNotices);
    }

}
