package com.ltgk.boat.controller;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.ltgk.boat.domain.dto.*;
import com.ltgk.boat.domain.entity.BoatAlarmInfo;
import com.ltgk.boat.domain.entity.RadarSolrEntity;
import com.ltgk.boat.domain.vo.BoatAlarmInfoStatisticsByTimeVO;
import com.ltgk.boat.domain.vo.BoatAlarmInfoVo;
import com.ltgk.boat.domain.vo.BoatAlarmStatisticsVO;
import com.ltgk.boat.service.BoatAlarmInfoService;
import com.ltgk.common.annotation.Anonymous;
import com.ltgk.common.core.controller.BaseController;
import com.ltgk.common.core.domain.AjaxResult;
import com.ltgk.common.core.domain.entity.SysUser;
import com.ltgk.common.core.page.BasePage;
import com.ltgk.common.core.page.TableDataInfo;
import com.ltgk.common.enums.SafetyAlarmTypeEnums;
import com.ltgk.common.enums.ViolationsAlarmTypeEnums;
import com.ltgk.common.utils.SecurityUtils;
import com.ltgk.common.utils.StringUtils;
import com.ltgk.common.utils.enums.EnumUtil;
import com.ltgk.common.utils.push.ManageUniPushUtils;
import com.ltgk.common.vo.EnumsVO;
import com.ltgk.system.service.ISysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.lang.Math.*;

/**
 * @Author: wan kun
 * @Description: 渔船报警查询逻辑
 * @Date: 2025/3/10 17:00
 * @Version: 1.0
 */
@RestController
@RequestMapping("/fishingPort/BoatAlarm")
@Api(tags = "渔船报警")
@Slf4j
@Validated
public class BoatAlarmInfoController extends BaseController {
    @Resource
    private BoatAlarmInfoService boatAlarmInfoService;
    @Resource
    private ManageUniPushUtils manageUniPushUtils;
    @Resource
    private ISysUserService userService;
    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @Value("${rocketmq.threeNoBoat.topic}")
    private String THREE_NO_BOAT_TOPIC;

    private static final double EARTH_RADIUS = 6371000; // 地球半径（米）

    /**
     * 查询安全预警所有的枚举类
     *
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/3/13
     */
    @PostMapping("/findSafetyAlarmTypeEnumList")
    @ApiOperation(value = "查询安全预警所有的枚举类")
    public AjaxResult findSafetyAlarmTypeEnumList() {
        return success(EnumUtil.getEnumsVOList(SafetyAlarmTypeEnums.class));
    }

    /**
     * 查询违规预警所有的枚举类
     *
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/3/13
     */
    @PostMapping("/findViolationsAlarmTypeEnumList")
    @ApiOperation(value = "查询违规预警所有的枚举类")
    public AjaxResult findViolationsAlarmTypeEnumList() {
        return success(EnumUtil.getEnumsVOList(ViolationsAlarmTypeEnums.class).stream()
                .sorted((e1, e2) -> e2.getCode().compareTo(e1.getCode()))
                .collect(Collectors.toList()));
    }

    /**
     * 查询所有的预警枚举类
     * -- 包括安全预警和违规预警
     *
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/5/15
     */
    @PostMapping("/findAllAlarmTypeEnumList")
    @ApiOperation(value = "查询所有的预警的枚举类--包括安全预警和违规预警")
    public AjaxResult findAllAlarmTypeEnumList() {
        return success(Stream.concat(EnumUtil.getEnumsVOList(SafetyAlarmTypeEnums.class).stream(), EnumUtil.getEnumsVOList(ViolationsAlarmTypeEnums.class).stream())
                .sorted((e1, e2) -> e2.getCode().compareTo(e1.getCode()))
                .collect(Collectors.toList()));
    }

    /**
     * 获取渔船报警
     *
     * @param getBoatAlarmListReq：
     * @return {@link List<BoatAlarmInfoVo>}
     */
    @PostMapping("/page")
    @ApiOperation(value = "获取渔船报警")
    public TableDataInfo page(@RequestBody GetBoatAlarmListReq getBoatAlarmListReq) {
        return getDataTable(boatAlarmInfoService.page(getBoatAlarmListReq));
    }

    /**
     * 获取渔船报警分类统计  -- 安全大屏页右侧安全动态
     *
     * @param req：
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/4/21
     */
    @PostMapping("/getBoatAlarmStatisticsByAlarmType")
    @ApiOperation(value = "获取渔船报警统计 -- 安全大屏页右侧安全动态")
    public AjaxResult getBoatAlarmStatisticsByAlarmType(@RequestBody BoatAlarmInfoReq req) {

        // 查询所有的报警信息
        List<EnumsVO> safetyAlarmTypeEnumList = EnumUtil.getEnumsVOList(SafetyAlarmTypeEnums.class);
        List<EnumsVO> violationsAlarmTypeEnumList = EnumUtil.getEnumsVOList(ViolationsAlarmTypeEnums.class);

        List<BoatAlarmStatisticsVO> boatAlarmInfoVoList = Stream.concat(
                safetyAlarmTypeEnumList.stream(),
                violationsAlarmTypeEnumList.stream()
        ).map(item -> {
            BoatAlarmStatisticsVO boatAlarmStatisticsVO = new BoatAlarmStatisticsVO();
            boatAlarmStatisticsVO.setAlarmType(item.getDesc());
            return boatAlarmStatisticsVO;
        }).collect(Collectors.toList());

        List<BoatAlarmInfo> boatAlarmInfoList = boatAlarmInfoService.lambdaQuery()
                .ge(BoatAlarmInfo::getAlarmTime, req.getStartTime())
                .list();

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

        boatAlarmInfoVoList.forEach(item -> {
            if (boatAlarmInfoCount.containsKey(item.getAlarmType())) {
                item.setCount(boatAlarmInfoCount.get(item.getAlarmType()).intValue());
            } else {
                item.setCount(0);
            }
        });

        return success(boatAlarmInfoVoList.stream().sorted(Comparator.comparing(BoatAlarmStatisticsVO::getCount).reversed()).collect(Collectors.toList()));
    }

    /**
     * 获取渔船报警统计 -- 安全大屏页右侧告警变化趋势
     *
     * @param req：
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/4/21
     */
    @PostMapping("/getBoatAlarmStatisticsByTime")
    @ApiOperation(value = "获取渔船报警统计 -- 安全大屏页右侧告警变化趋势")
    public AjaxResult getBoatAlarmStatisticsByTime(@RequestBody BoatAlarmInfoReq req) {

        BoatAlarmInfoStatisticsByTimeVO vo = new BoatAlarmInfoStatisticsByTimeVO();

        Date startDate = req.getStartTime();
        Date endDate = new Date();
        List<DateTime> dateList = DateUtil.rangeToList(startDate, endDate, DateField.WEEK_OF_MONTH);

        if (dateList.isEmpty()) {
            return success(vo);
        }
        List<String> timeList = new ArrayList<>();
        for (int i = 0; i < dateList.size(); i++) {
            // 月度间隔一周
            if ("month".equals(req.getTimeType())) {
                timeList.add(DateUtil.format(dateList.get(i), "yyyy-MM-dd"));
            }
            // 季度度间隔两周
            if ("quarter".equals(req.getTimeType())) {
                if (i % 2 == 0) {
                    timeList.add(DateUtil.format(dateList.get(i), "yyyy-MM-dd"));
                }
            }
        }

        List<BoatAlarmInfo> boatAlarmInfoList = boatAlarmInfoService.lambdaQuery()
                .between(BoatAlarmInfo::getAlarmTime, startDate, endDate)
                .list();

        Map<String, Long> boatAlarmInfoCount = boatAlarmInfoList.stream()
                .collect(Collectors.groupingBy(item -> DateUtil.format(item.getAlarmTime(), "yyyy-MM-dd"), Collectors.counting()));
        if (timeList.isEmpty()) {
            return success(vo);
        }

        List<Long> countList = new ArrayList<>();
        timeList.forEach(item -> {
            countList.add(boatAlarmInfoCount.getOrDefault(item, 0L));
        });
        vo.setTimeList(timeList)
                .setCountList(countList);
        return success(vo);
    }

    /**
     * 导出渔船报警信息     InstallThePackage
     * 接口权限：fishingPort:BoatAlarm:export
     *
     * @param response:
     * @param req:
     * @author Qi ChengBin
     * @date 2025/3/24
     */
    @PostMapping("/export")
    @ApiOperation(value = "导出渔船报警信息")
    public void export(HttpServletResponse response, GetBoatAlarmListReq req) {
        boatAlarmInfoService.export(response, req);
    }

    /**
     * 执法人员受理渔船报警
     *
     * @param req:
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/5/15
     */
    @PostMapping("/accept")
    @ApiOperation(value = "受理渔船报警")
    public AjaxResult accept(@RequestBody BoatAlarmHandleReq req) {
        SysUser user = SecurityUtils.getLoginUser().getUser();
        return toAjax(boatAlarmInfoService.lambdaUpdate()
                .eq(BoatAlarmInfo::getId, req.getId())
                .set(BoatAlarmInfo::getAcceptState, 1)
                .set(BoatAlarmInfo::getAcceptTime, new Date())
                .set(BoatAlarmInfo::getAcceptUserId, String.valueOf(user.getUserId()))
                .set(BoatAlarmInfo::getAcceptUserName, user.getNickName())
                .set(BoatAlarmInfo::getUpdateBy, SecurityUtils.getLoginUser().getUser().getNickName())
                .set(BoatAlarmInfo::getUpdateAt, new Date())
                .update()
        );
    }

    /**
     * 处理渔船报警接口
     *
     * @param boatAlarmHandleReq:
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/5/15
     */
    @PostMapping("/handle")
    @ApiOperation(value = "处置渔船报警")
    public AjaxResult handle(@Valid @RequestBody BoatAlarmHandleReq boatAlarmHandleReq) {
        return boatAlarmInfoService.handle(boatAlarmHandleReq);
    }

    /**
     * 修改渔船报警状态接口
     *
     * @param req
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/5/15
     */
    @PostMapping("/updateAcceptState")
    @ApiOperation(value = "修改渔船报警状态接口")
    public AjaxResult updateAcceptState(@RequestBody BoatAlarmUpdateAcceptStateReq req) {
        return toAjax(boatAlarmInfoService.lambdaUpdate()
                .eq(BoatAlarmInfo::getId, req.getId())
                .set(BoatAlarmInfo::getAcceptState, req.getAcceptState())
                .set(BoatAlarmInfo::getUpdateBy, SecurityUtils.getLoginUser().getUser().getNickName())
                .set(BoatAlarmInfo::getUpdateAt, new Date())
                .update()
        );
    }

    /**
     * 根据报警类型数组获取指定时间内的渔船报警信息统计
     *
     * @param dto:
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/5/14
     */
    @PostMapping("/getBoatAlarmStatistics")
    @ApiOperation(value = "根据报警类型数组获取指定时间内的渔船报警信息统计")
    public AjaxResult getBoatAlarmStatistics(@RequestBody BoatAlarmStatisticsDTO dto) {
        return boatAlarmInfoService.getBoatAlarmStatistics(dto);
    }

    /**
     * 根据报警类型数组获取指定时间内的渔船报警信息统计【返回数据为 类型:数】
     *
     * @param dto:
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/5/15
     */
    @PostMapping("/getBoatAlarmStatisticsToAlarmType")
    @ApiOperation(value = "根据报警类型数组获取指定时间内的渔船报警信息统计【返回数据为 类型:数】")
    public AjaxResult getBoatAlarmStatisticsToAlarmType(@RequestBody BoatAlarmStatisticsDTO dto) {
        return boatAlarmInfoService.getBoatAlarmStatisticsToAlarmType(dto);
    }

    @ApiOperation("app消息推送")
    @PostMapping(value = "/unipush-app")
    public AjaxResult uniPushApp() {

        List<String> cidList = userService.list()
                .stream()
                .map(SysUser::getCid)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        String title = "测试标题";
        String content = "测试内容";
        // 单推
//        nuiPushUtils.pushMsg(clientId, title, content);
        // 多推
//        List<String> cids = new ArrayList<>();
//        cids.add(clientId);
        manageUniPushUtils.pushMsg(cidList, title, content);
        return success();
    }

    @PostMapping("/doThreeNoBoatList")
    @ApiOperation(value = "接收三无渔船数据")
    @Anonymous
    public AjaxResult doThreeNoBoatList(@RequestBody List<RadarSolrEntity> dto) {
        log.info("接收疑似三无渔船数据的数量>>>:{}", dto);

        if (CollectionUtils.isNotEmpty(dto)) {
            // 查询最后一次定位时间半小时内所有的待处理的数据
            List<BoatAlarmInfo> dbBoatAlarmInfoList = boatAlarmInfoService.lambdaQuery()
                    // 待受理的数据
                    .eq(BoatAlarmInfo::getAcceptState, 0)
                    .eq(BoatAlarmInfo::getAlarmType, "疑似三无船只")
                    // 查询大于等于30分钟之前到现在的数据
                    .ge(BoatAlarmInfo::getUpdateAt, DateUtil.offsetMinute(new Date(), -30))
                    .list();

            Map<String, BoatAlarmInfo> dbBoatAlarmInfoMap = dbBoatAlarmInfoList.stream()
                    .collect(Collectors.toMap(BoatAlarmInfo::getTargetSourceId, Function.identity(), (v1, v2) -> v2));

            List<BoatAlarmInfo> saveBoatAlarmInfoList = new ArrayList<>();
            List<BoatAlarmInfo> updateBoatAlarmInfoList = new ArrayList<>();
            List<BoatAlarmInfo> updateBoatAlarmInfoByPointList = new ArrayList<>();
            for (RadarSolrEntity entity : dto) {
                Map<Boolean, String> within10MetersMap = this.isWithin10Meters(entity, dbBoatAlarmInfoList);
                // 存在雷达目标id 相同的数据
                if (CollectionUtils.isNotEmpty(dbBoatAlarmInfoList) && dbBoatAlarmInfoMap.containsKey(entity.getTargetSourceId())) {
                    BoatAlarmInfo dbBoatAlarmInfo = dbBoatAlarmInfoMap.get(entity.getTargetSourceId())
                            .setBoatName("疑似三无船只")
                            .setLastLatitude(entity.getLatitude())
                            .setLastLongitude(entity.getLongitude())
                            .setLastLocationTime(entity.getLocationTime())
                            .setSpeed(String.valueOf(entity.getSpeed()))
                            .setCourse(String.valueOf(entity.getCourse()))
                            .setUpdateAt(new Date())
                            .setUpdateBy("SYS-Z");
                    updateBoatAlarmInfoList.add(dbBoatAlarmInfo);
                } else if (within10MetersMap.containsKey(true)) {
                    BoatAlarmInfo dbBoatAlarmInfo = dbBoatAlarmInfoMap.get(within10MetersMap.get(true))
                            .setBoatName("疑似三无船只")
                            .setLastLatitude(entity.getLatitude())
                            .setLastLongitude(entity.getLongitude())
                            .setLastLocationTime(entity.getLocationTime())
                            .setSpeed(String.valueOf(entity.getSpeed()))
                            .setCourse(String.valueOf(entity.getCourse()))
                            .setUpdateAt(new Date())
                            .setUpdateBy("SYS-Z");
                    updateBoatAlarmInfoByPointList.add(dbBoatAlarmInfo);
                } else {
                    // 雷达目标不同的数据
                    saveBoatAlarmInfoList.add(
                            new BoatAlarmInfo()
                                    .setBoatName("疑似三无船只")
                                    .setTargetSourceId(entity.getTargetSourceId())
                                    .setAlarmType("疑似三无船只")
                                    .setAlarmNature("2")
                                    .setLastLongitude(entity.getLongitude())
                                    .setLastLatitude(entity.getLatitude())
                                    .setLastLocationTime(entity.getLocationTime())
                                    .setSpeed(String.valueOf(entity.getSpeed()))
                                    .setCourse(String.valueOf(entity.getCourse()))
                                    .setAlarmTime(new Date())
                                    .setCreateBy("SYS-Z")
                                    .setCreateAt(new Date())
                                    .setUpdateBy("SYS-Z")
                                    .setUpdateAt(new Date()));
                }
            }
            log.info(">>>>保存的数据数量:{}", saveBoatAlarmInfoList.size());
            log.info(">>>>更新的数据数量:{}", updateBoatAlarmInfoList.size());
            log.info(">>>>根据点位更新的数据数量:{}", updateBoatAlarmInfoByPointList.size());

            boolean result = (!CollectionUtils.isNotEmpty(saveBoatAlarmInfoList) || boatAlarmInfoService.saveBatch(saveBoatAlarmInfoList)) &&
                    (!CollectionUtils.isNotEmpty(updateBoatAlarmInfoList) || boatAlarmInfoService.updateBatchById(updateBoatAlarmInfoList))
                    && (!CollectionUtils.isNotEmpty(updateBoatAlarmInfoByPointList) || boatAlarmInfoService.updateBatchById(updateBoatAlarmInfoByPointList));

            if (result) {
                // 将数据存储到mq 中去
                updateBoatAlarmInfoList.addAll(updateBoatAlarmInfoByPointList);
                this.doThreeNoBoatListToMQ(saveBoatAlarmInfoList, updateBoatAlarmInfoList);
            }

            return toAjax(result);
        }
        return success();
    }

    // 判断跟现有的点位是否在10米内
    private Map<Boolean, String> isWithin10Meters(RadarSolrEntity entity, List<BoatAlarmInfo> dbBoatAlarmInfoList) {

        Map<Boolean, String> resMap = new HashMap<>();
        resMap.put(false, "");

        for (BoatAlarmInfo boatAlarmInfo : dbBoatAlarmInfoList) {
            double distance = calculateDistance(
                    Double.parseDouble(entity.getLatitude()), Double.parseDouble(entity.getLongitude()),
                    Double.parseDouble(boatAlarmInfo.getLastLatitude()), Double.parseDouble(boatAlarmInfo.getLastLongitude())
            );
            if (distance <= 50.0) {
                resMap.put(true, boatAlarmInfo.getTargetSourceId());
                // 发现一个满足条件的点即可跳出循环
                break;
            }
        }
        return resMap;
    }

    // 计算两个经纬度点之间的距离（Haversine公式）
    public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
        double dLat = toRadians(lat2 - lat1);
        double dLon = toRadians(lon2 - lon1);
        double a = sin(dLat / 2) * sin(dLat / 2) +
                cos(toRadians(lat1)) * cos(toRadians(lat2)) *
                        sin(dLon / 2) * sin(dLon / 2);
        double c = 2 * atan2(sqrt(a), sqrt(1 - a));
        return EARTH_RADIUS * c; // 返回距离（米）
    }

    // 将三无渔船数据存储数据到MQ中
    @Async("threadPoolTaskExecutor")
    protected void doThreeNoBoatListToMQ(List<BoatAlarmInfo> saveBoatAlarmInfoList, List<BoatAlarmInfo> updateBoatAlarmInfoList) {
        updateBoatAlarmInfoList.forEach(item -> rocketMQTemplate.convertAndSend(this.THREE_NO_BOAT_TOPIC, JSONUtil.toJsonStr(item)));
        saveBoatAlarmInfoList.forEach(item -> rocketMQTemplate.convertAndSend(this.THREE_NO_BOAT_TOPIC, JSONUtil.toJsonStr(item)));
    }

    /**
     * 查询疑似三无渔船数据
     * -- 暂无筛选条件
     *
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/6/24
     */
    @PostMapping("/findThreeNoBoatList")
    public TableDataInfo findThreeNoBoatList(@RequestBody BasePage basePage) {
        startPage(basePage.getPageNum(), basePage.getPageSize());
        return getDataTable(boatAlarmInfoService.lambdaQuery()
                .eq(BoatAlarmInfo::getAlarmType, "疑似三无船只")
                .eq(BoatAlarmInfo::getAlarmNature, "2")
//                .eq(BoatAlarmInfo::getAcceptState, "0")
                .ge(BoatAlarmInfo::getUpdateAt, DateUtil.offsetMinute(new Date(), -1))
//                .isNotNull(BoatAlarmInfo::getImgUrl)
                .orderByDesc(BoatAlarmInfo::getLastLocationTime)
                .list());
    }

    /**
     * 查询疑似三无渔船历史列表
     *
     * @param req:
     * @return com.ltgk.common.core.page.TableDataInfo
     * @author Qi ChengBin
     * @date 2025/6/27
     */
    @PostMapping("/findThreeNoBoatHistoryList")
    public TableDataInfo findThreeNoBoatHistoryList(@RequestBody GetBoatAlarmListReq req) {
        startPage(req.getPageNum(), req.getPageSize());
        return getDataTable(boatAlarmInfoService.lambdaQuery()
                .eq(BoatAlarmInfo::getAlarmNature, "2")
                .eq(BoatAlarmInfo::getAlarmType, "疑似三无船只")
                .like(StringUtils.isNotBlank(req.getTargetSourceId()), BoatAlarmInfo::getTargetSourceId, req.getTargetSourceId())
//                .isNotNull(BoatAlarmInfo::getImgUrl)
                .orderByDesc(BoatAlarmInfo::getLastLocationTime)
                .list());
    }

    /**
     * 根据告警信息主键删除告警数据
     *
     * @param req: 请求参数
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/6/27
     */
    @PostMapping("/delete")
    public AjaxResult delete(@RequestBody BoatAlarmInfo req) {
        return toAjax(boatAlarmInfoService.removeById(req.getId()));
    }

    /**
     * 根据告警信息主键列表批量删除告警数据
     *
     * @param req：
     * @return com.ltgk.common.core.domain.AjaxResult
     * @author Qi ChengBin
     * @date 2025/7/18
     */
    @PostMapping("/batchDelete")
    public AjaxResult batchDelete(@RequestBody BoatAlarmInfo req) {
        return toAjax(boatAlarmInfoService.removeBatchByIds(req.getBoatAlarmInfoIdList()));
    }
}
