package com.ltgk.boat.service.impl;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ltgk.boat.config.websocket.WebSocketSessionMap;
import com.ltgk.boat.domain.dto.BoatAlarmHandleReq;
import com.ltgk.boat.domain.dto.BoatAlarmStatisticsDTO;
import com.ltgk.boat.domain.dto.GetBoatAlarmListReq;
import com.ltgk.boat.domain.entity.BoatAlarmInfo;
import com.ltgk.boat.domain.vo.BoatAlarmInfoVo;
import com.ltgk.boat.domain.vo.IndexAlarmInfoWebsocketVO;
import com.ltgk.boat.mapper.BoatAlarmInfoMapper;
import com.ltgk.boat.service.BoatAlarmInfoService;
import com.ltgk.common.constant.ReturnMsgConstants;
import com.ltgk.common.core.domain.AjaxResult;
import com.ltgk.common.core.domain.entity.SysUser;
import com.ltgk.common.enums.ViolationsAlarmTypeEnums;
import com.ltgk.common.utils.SecurityUtils;
import com.ltgk.common.utils.StringUtils;
import com.ltgk.common.utils.poi.ExcelUtil;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import static com.github.pagehelper.page.PageMethod.startPage;

@Service
public class BoatAlarmInfoServiceImpl extends ServiceImpl<BoatAlarmInfoMapper, BoatAlarmInfo> implements BoatAlarmInfoService {
    @Resource
    private BoatAlarmInfoMapper boatAlarmInfoMapper;

    private final Map<String, WebSocketSession> webSocketSessionMap = new WebSocketSessionMap().sharedMap();

    @Override
    public List<BoatAlarmInfoVo> page(GetBoatAlarmListReq getBoatAlarmListReq) {
        // 组装疑似三无渔船查询条件方法
        assembledThreeNoBoatReq(getBoatAlarmListReq);
        startPage(getBoatAlarmListReq.getPageNum(), getBoatAlarmListReq.getPageSize());
        List<BoatAlarmInfoVo> boatAlarmInfoVoList = boatAlarmInfoMapper.page(getBoatAlarmListReq);
        boatAlarmInfoVoList.stream()
                .filter(Objects::nonNull)
                .forEach(item -> {
                    item.setTerminalCode(StringUtils.isBlank(item.getTerminalCode()) ? item.getTargetSourceId() : item.getTerminalCode());
                });
        return boatAlarmInfoVoList;
    }

    @Override
    public AjaxResult handle(BoatAlarmHandleReq req) {

        SysUser user = SecurityUtils.getLoginUser().getUser();

        LambdaQueryWrapper<BoatAlarmInfo> lqw = new LambdaQueryWrapper<>();
        lqw.eq(BoatAlarmInfo::getId, req.getId());
        BoatAlarmInfo one = this.getOne(lqw);
        if (ObjectUtils.isEmpty(one)) return AjaxResult.error(ReturnMsgConstants.ALARM_NOT_EXIT);

        one.setAcceptState(2)
                .setVerifySituation(req.getVerifySituation())
                .setParticipants(req.getParticipants())
                .setProcessResults(req.getProcessResults())
                .setProcessUserId(String.valueOf(user.getUserId()))
                .setProcessUserName(user.getNickName())
                .setUpdateBy(user.getNickName())
                .setUpdateAt(new Date());

        if (this.updateById(one)) {
            return AjaxResult.success();
        }
        return AjaxResult.error();
    }

    /**
     * 导出渔船报警信息
     *
     * @param response：
     * @param req：
     * @author Qi ChengBin
     * @date 2025/3/24
     */
    @Override
    public void export(HttpServletResponse response, GetBoatAlarmListReq req) {
        // 组装疑似三无渔船查询条件方法
        assembledThreeNoBoatReq(req);
        List<BoatAlarmInfoVo> boatAlarmInfoVoList = boatAlarmInfoMapper.page(req);
        ExcelUtil<BoatAlarmInfoVo> util = new ExcelUtil<>(BoatAlarmInfoVo.class);
        util.exportExcel(response, boatAlarmInfoVoList, "渔船报警信息数据");
    }

    /**
     * 根据查询条件组装疑似三无渔船查询条件
     * -- alarmTypeList： 报警类型列表中有疑似三无渔船则设置 查询条件是否包含疑似三无渔船 为 true；
     * -- 并且将列表中的 疑似三无船只 的查询条件去除
     *
     * @param req:
     * @author Qi ChengBin
     * @date 2025/6/25
     */
    private static void assembledThreeNoBoatReq(GetBoatAlarmListReq req) {

        if (CollectionUtils.isNotEmpty(req.getAlarmTypeList())) {
            // 如果包括 疑似三无船只
            if (req.getAlarmTypeList().contains(ViolationsAlarmTypeEnums.THREE_NO_BOATS.getDesc())) {
                req.setAlarmTypeList(
                        req.getAlarmTypeList().stream()
                                .filter(item -> !Objects.equals(ViolationsAlarmTypeEnums.THREE_NO_BOATS.getDesc(), item))
                                .collect(Collectors.toList())
                );
                // 在设置完疑似三无船只数据 之后  如果是空的 ，证明只查询了疑似三无船只
                if (CollectionUtils.isEmpty(req.getAlarmTypeList())) {
                    req.setOnlyThreeNoBoat(true);
                }
            } else {
                // 不包括疑似三无船只
                req.setNoThreeNoBoat(true);
            }
        }
    }

    /**
     * 根据报警类型数据获取指定时间内的渔船报警统计
     *
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/5/14
     */
    @Override
    public AjaxResult getBoatAlarmStatistics(BoatAlarmStatisticsDTO dto) {
        Date startDate = DateUtil.parse(dto.getStartTime(), "yyyy-MM-dd");
        Date endDate = DateUtil.parse(dto.getEndTime(), "yyyy-MM-dd");

        List<String> alarmTypeList = dto.getAlarmTypeList();

        List<String> dateList = DateUtil.rangeToList(startDate, endDate, DateField.DAY_OF_MONTH)
                .stream()
                .map(dateTime -> dateTime.toString("yyyy-MM-dd")) // 转换为字符串格式
                .collect(Collectors.toList());

        List<BoatAlarmInfo> boatAlarmInfoList = this.lambdaQuery()
                .between(BoatAlarmInfo::getAlarmTime, DateUtil.beginOfDay(startDate), DateUtil.endOfDay(endDate))
                .in(CollectionUtils.isNotEmpty(alarmTypeList), BoatAlarmInfo::getAlarmType, alarmTypeList)
                .list();

        Map<String, Map<String, Long>> boatAlarmInfoMap = boatAlarmInfoList.stream()
                .collect(Collectors.groupingBy(BoatAlarmInfo::getAlarmType,
                        Collectors.groupingBy(item -> DateUtil.format(item.getAlarmTime(), "yyyy-MM-dd"), Collectors.counting())
                ));

        Map<String, Object> resultMap = new HashMap<>();

        alarmTypeList.forEach(alarmType -> {
            List<Long> countList = new ArrayList<>();
            dateList.forEach(date -> {
                countList.add(boatAlarmInfoMap.getOrDefault(alarmType, new HashMap<>()).getOrDefault(date, 0L));
            });
            resultMap.put(alarmType, countList);
        });

        resultMap.put("dateList", dateList);
        return AjaxResult.success(resultMap);
    }

    /**
     * 根据报警类型数组获取指定时间内的渔船报警信息统计【返回数据为 类型:数】
     *
     * @param dto:
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/5/15
     */
    @Override
    public AjaxResult getBoatAlarmStatisticsToAlarmType(BoatAlarmStatisticsDTO dto) {
        Date startDate = DateUtil.parse(dto.getStartTime(), "yyyy-MM-dd");
        Date endDate = DateUtil.parse(dto.getEndTime(), "yyyy-MM-dd");

        List<String> alarmTypeList = dto.getAlarmTypeList();

        List<BoatAlarmInfo> boatAlarmInfoList = this.lambdaQuery()
                .between(BoatAlarmInfo::getAlarmTime, DateUtil.beginOfDay(startDate), DateUtil.endOfDay(endDate))
                .in(CollectionUtils.isNotEmpty(alarmTypeList), BoatAlarmInfo::getAlarmType, alarmTypeList)
                .list();

        Map<String, Long> boatAlarmInfoCount = boatAlarmInfoList.stream()
                .collect(Collectors.groupingBy(BoatAlarmInfo::getAlarmType, Collectors.counting()));

        Map<String, Long> resultMap = new HashMap<>();
        alarmTypeList.forEach(item -> {
            resultMap.put(item, boatAlarmInfoCount.getOrDefault(item, 0L));
        });
        return AjaxResult.success(resultMap);
    }

    /**
     * 将连接的websocket 存储起来，定时发送数据时使用
     *
     * @param session:
     * @author Qi ChengBin
     * @date 2025/3/12
     */
    @Override
    public void saveWebsocketSession(WebSocketSession session) {
        if (!this.webSocketSessionMap.containsKey(session.getId())) {
            // 将 session 存储起来，要不发送时，不知道给谁发
            this.webSocketSessionMap.put(session.getId(), session);
        }
        // 连接成功后直接给前端推送一次数据
        if (session.isOpen()) {
            // 组装告警数据和疑似三无船只数据用来推送给前端
            Map<String, Object> resMap = assembledSendData();
            try {
                session.sendMessage(
                        new TextMessage(JSONUtil.toJsonStr(resMap)));
            } catch (IOException e) {
                log.error(">>>>>>websocket 连接成功后推送数据失败！:::" + e.getMessage());
            }
        }
    }

    /**
     * 组装告警数据和疑似三无船只数据用来推送给前端
     *
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @author Qi ChengBin
     * @date 2025/6/25
     */
    public Map<String, Object> assembledSendData() {
        List<IndexAlarmInfoWebsocketVO> indexAlarmInfoWebsocketVOList = getIndexAlarmInfoWebsocketVOS();
//        // 查询疑似三无渔船数据
//        List<BoatAlarmInfo> threeNoBoatList = this.lambdaQuery()
//                .eq(BoatAlarmInfo::getAlarmType, "疑似三无船只")
//                // 查询大于等于30分钟之前到现在的数据
//                .ge(BoatAlarmInfo::getUpdateAt, DateUtil.offsetMinute(new Date(), -1))
//                .isNotNull(BoatAlarmInfo::getImgUrl)
//                .list();
        Map<String, Object> resMap = new HashMap<>();
        resMap.put("index", indexAlarmInfoWebsocketVOList);
//        resMap.put("threeNoBoat", threeNoBoatList);
        return resMap;
    }

    // 定时推送前端告警信息
    @Scheduled(cron = "0/10 * * * * ?")
    public void sendTwinBoatDynamic() {
        // 组装告警数据和疑似三无船只数据用来推送给前端
        Map<String, Object> resMap = assembledSendData();
        this.webSocketSessionMap.forEach((key, value) -> {
            if (value.isOpen()) {
                try {
                    value.sendMessage(
                            new TextMessage(JSONUtil.toJsonStr(resMap)));
                } catch (IOException e) {
                    log.error(">>>>>>websocket 定时推送失败！:::" + e.getMessage());
                }
            }
        });
    }

    /**
     * 查询系统中未受理的预警，包括安全预警和违规预警
     *
     * @return java.util.List<com.ltgk.boat.domain.vo.IndexAlarmInfoWebsocketVO>
     * @author Qi ChengBin
     * @date 2025/4/9
     */
    private List<IndexAlarmInfoWebsocketVO> getIndexAlarmInfoWebsocketVOS() {
        List<BoatAlarmInfo> boatAlarmInfoList = this.lambdaQuery()
                // 查询所有的待受理的预警，包括安全预警和违规预警
                .eq(BoatAlarmInfo::getAcceptState, 0)
                .and(wrapper -> wrapper
                        // 条件1：alarmType为"疑似三无船只"且更新时间在30分钟内
                        .eq(BoatAlarmInfo::getAlarmType, "疑似三无船只")
                        .ge(BoatAlarmInfo::getUpdateAt, DateUtil.offsetMinute(new Date(), -30))
                        .isNotNull(BoatAlarmInfo::getImgUrl)
                        // 条件2：alarmType不是"疑似三无船只"（无时间限制）
                        .or()
                        .ne(BoatAlarmInfo::getAlarmType, "疑似三无船只")
                )
                .list();

        // 查询 类型为疑似三无渔船的 30分钟内的数据，其他类型的全部的数据
        return boatAlarmInfoList.stream()
                .map(item -> {
                    String boatName = item.getBoatName() != null ? item.getBoatName() : "";
                    String alarmType = item.getAlarmType() != null ? item.getAlarmType() : "";
                    String alarmTime = item.getAlarmTime() != null ? DateUtil.formatDateTime(item.getAlarmTime()) : "";
                    return new IndexAlarmInfoWebsocketVO()
                            .setAlarmInfo(boatName + alarmType + "告警")
                            .setAlarmTime(alarmTime);
                })
                .collect(Collectors.toList());
    }
}
