package com.ruoyi.iot.service.impl;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.ruoyi.bst.area.domain.AreaVO;
import com.ruoyi.bst.area.domain.vo.LocationAreaVO;
import com.ruoyi.bst.area.service.AreaService;
import com.ruoyi.bst.area.utils.AreaUtil;
import com.ruoyi.bst.areaSub.domain.AreaSubVO;
import com.ruoyi.bst.areaSub.service.AreaSubService;
import com.ruoyi.bst.device.domain.Device;
import com.ruoyi.bst.device.domain.DeviceVO;
import com.ruoyi.bst.device.domain.enums.DeviceLockStatus;
import com.ruoyi.bst.device.domain.enums.DeviceQuality;
import com.ruoyi.bst.device.domain.enums.DeviceStatus;
import com.ruoyi.bst.device.domain.enums.DeviceUnLockType;
import com.ruoyi.bst.device.domain.vo.DeviceIotVO;
import com.ruoyi.bst.device.service.DeviceAssembler;
import com.ruoyi.bst.device.service.DeviceIotService;
import com.ruoyi.bst.device.service.DeviceService;
import com.ruoyi.bst.device.utils.DeviceUtil;
import com.ruoyi.bst.locationLog.domain.LocationLog;
import com.ruoyi.bst.locationLog.service.LocationLogConverter;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.core.redis.RedisLock;
import com.ruoyi.common.enums.LogBizType;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MathUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.iot.constants.IotConstants;
import com.ruoyi.iot.domain.IotDeviceSysInfo;
import com.ruoyi.iot.domain.ReceiveMsg;
import com.ruoyi.iot.enums.ReceiveType;
import com.ruoyi.iot.service.IotReceiveService;
import com.ruoyi.iot.util.IotUtil;
import com.ruoyi.system.operLog.service.OperLogService;
import com.ruoyi.ws.service.DeviceWebSocketService;

import lombok.extern.slf4j.Slf4j;

/**
 * @author wjh
 *         2024/5/6
 */
@Service
@Slf4j
public class IotReceiveServiceImpl implements IotReceiveService {

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private LocationLogConverter locationLogConverter;

    @Autowired
    private DeviceService deviceService;

    @Autowired
    private AreaSubService areaSubService;

    @Autowired
    private AreaService areaService;

    @Autowired
    private DeviceIotService deviceIotService;

    @Autowired
    private DeviceWebSocketService deviceWebSocketService;

    @Autowired
    private OperLogService operLogService;

    @Autowired
    private RedisLock redisLock;

    @Autowired
    private DeviceAssembler deviceAssembler;

    @Override
    public void handleReceive(ReceiveMsg msg) {
        if (msg == null) {
            return;
        }
        // 数据点推送
        if (ReceiveType.DATA_POINT.getType().equals(msg.getType())) {
            // 设备数据
            if (IotConstants.DS_SYS.equals(msg.getDsId())) {
                // 查询设备信息
                DeviceVO device = deviceService.selectSimpleByMacForLocationLog(msg.getDevName());
                if (device == null) {
                    return;
                }
                deviceAssembler.assembleIot(device);
                BigDecimal oldLon = device.getLongitude();
                BigDecimal oldLat = device.getLatitude();
                LocalDateTime lastLocationTime = device.getLastLocationTime();

                // 转为设备信息
                IotDeviceSysInfo sys = IotUtil.toSysInfo(msg.getValue());
                LocalDateTime at = DateUtils.toLocalDateTime(msg.getAt());
                DeviceUtil.setIotSysInfoByReceive(device, sys, at, true);

                // 更新设备信息
                deviceIotService.updateIot(device);

                // 定位无效，不处理
                if (!DeviceUtil.validLocation(device.getLongitude(), device.getLatitude())) {
                    log.info("设备{}定位无效,不处理", device.getMac());
                    return;
                }

                // 处理设备定位BUG，若出现BUG则重启设备
                int handle = this.handleDeviceLocationBug(device, at, lastLocationTime, oldLon, oldLat);
                if (handle == 1) {
                    return;
                }

                // 处理运营区
                this.handleArea(device);

                // 处理定位日志
                this.handleLocationLog(device);
            }
            // 软件版本
            else if (IotConstants.DS_VER.equals(msg.getDsId())) {
                Device data = new Device();
                data.setSoftwareVersion(msg.getValue().toString());
                data.setMac(msg.getDevName());
                int update = deviceIotService.updateIot(data);
                if (update != 1) {
                    log.error("更新设备软件版本失败: {}，版本：{}", msg.getDevName(), msg.getValue());
                }
            }
            // CSQ 重启
            else if (IotConstants.DS_CSQ.equals(msg.getDsId())) {
                this.handleReboot(msg);
            }
        }
        // 生命周期（暂时不做，需要测试）
        else if (ReceiveType.DEVICE_STATUS.getType().equals(msg.getType())) {
        }
    }

    /**
     * 处理设备重启事件
     */
    private void handleReboot(ReceiveMsg msg) {
        if (msg == null || StringUtils.isBlank(msg.getDevName())) {
            return;
        }
        // 同步一次设备状态
        deviceIotService.monitor(msg.getDevName());
    }

    /**
     * 处理设备定位BUG
     *
     * @param device
     */
    private int handleDeviceLocationBug(DeviceVO device, LocalDateTime at, LocalDateTime lastLocationTime,
            BigDecimal oldLon, BigDecimal oldLat) {
        if (device == null || StringUtils.isBlank(device.getMac()) || lastLocationTime == null || at == null) {
            return 0;
        }
        // 相差一分钟以上的消息不做处理
        Duration duration = Duration.between(at, LocalDateTime.now());
        if (duration.getSeconds() > 60) {
            return 0;
        }
        // 若设备禁用或者入库，则不处理
        if (DeviceStatus.notHandleLocationBug().contains(device.getStatus())) {
            return 0;
        }

        boolean isOpen = DeviceQuality.OPEN.getCode().equals(device.getQuality()); // 电门是否开启
        // 需要在设备未启动的时候做，否则可能有安全问题
        if (!isOpen) {
            // 若当前数据点的上报时间和设备内上次上报的时间不一样，但是定位一样，则重启设备
            if (at.isAfter(lastLocationTime) && MathUtils.equals(device.getLongitude(), oldLon)
                    && MathUtils.equals(device.getLatitude(), oldLat)) {
                String reason = String.format("设备不同时间的两次定位一样，重启设备。新定位：%s,%s，旧定位：%s,%s", device.getLongitude(),
                        device.getLatitude(), oldLon, oldLat);
                deviceIotService.reboot(device, reason, true);
                return 1;
            }
        }

        return 0;
    }

    /**
     * 处理车辆运营区
     */
    private void handleArea(DeviceVO device) {
        if (device == null || StringUtils.isBlank(device.getMac())) {
            return;
        }
        // 相差一分钟以上的消息不做处理
        Duration duration = Duration.between(device.getLastTime(), LocalDateTime.now());
        if (duration.getSeconds() > 60) {
            return;
        }
        // 卫星数量小于5，不处理设备操作
        if (device.getSatellites() == null || device.getSatellites() < 5) {
            log.error("卫星数量小于5，不处理: {}", device.getMac());
            return;
        }
        // 若处理过，则不处理
        String key = CacheConstants.DEVICE_AREA_HANDLE_KEY + device.getMac();
        Integer isHandle = redisCache.getCacheObject(key);
        if (isHandle != null && isHandle == 1) {
            return;
        }

        // 管理员开锁，不处理
        boolean isAdminUnlocking = DeviceStatus.DISPATCHING.getCode().equals(device.getStatus());
        if (isAdminUnlocking) {
            return;
        }

        // 查询运营区
        AreaVO area = areaService.selectAreaById(device.getAreaId());
        if (area == null) {
            return;
        }
        // 查询运营区内的禁行区
        List<AreaSubVO> noRidingList = areaSubService.selectNoRidingListByAreaId(device.getAreaId());

        // 处理设备在运营区内的行为
        int handle = handleDeviceArea(device, area, noRidingList);
        // 若处理了，则返回1，并设置缓存5秒
        if (handle == 1) {
            redisCache.setCacheObject(key, handle, 5, TimeUnit.SECONDS);
        }

    }

    // 设备运营区处理
    private int handleDeviceArea(DeviceVO device, AreaVO area, List<AreaSubVO> noRidingList) {
        if (device == null || area == null) {
            return 0;
        }

        boolean isOpen = DeviceLockStatus.OPEN.getCode().equals(device.getLockStatus()); // 是否开锁
        boolean isQLocked = DeviceStatus.Q_LOCKED.getCode().equals(device.getStatus()); // 是否强制断电
        boolean isDispatching = DeviceStatus.DISPATCHING.getCode().equals(device.getStatus()); // 是否调度中

        // 是否有正在使用的订单，若没有订单则不处理
        boolean hasOrder = DeviceUtil.isOrderUsing(device);
        if (!hasOrder) {
            return 0;
        }
        Long orderId = device.getOrderId();

        // 获取车辆位置信息
        LocationAreaVO locationArea = AreaUtil.getLocationArea(device.getLongitude(), device.getLatitude(), area,
                noRidingList);
        boolean isInArea = locationArea.getIsInArea() != null && locationArea.getIsInArea();
        boolean isInAreaMin = locationArea.getIsInAreaMin() != null && locationArea.getIsInAreaMin();
        boolean isInAreaMax = locationArea.getIsInAreaMax() != null && locationArea.getIsInAreaMax();
        boolean isInNoRidingArea = locationArea.getIsInNoRidingArea() != null && locationArea.getIsInNoRidingArea();
        boolean isNearNoRidingArea = locationArea.getIsNearNoRidingArea() != null
                && locationArea.getIsNearNoRidingArea();
        boolean hasAreaBoundary = StringUtils.isNotBlank(area.getBoundary()); // 是否有运营区边界

        // 在运营区内，并且不在禁行区内，并且车辆为强制断电状态，为车辆上电
        if (isInAreaMax && !isInNoRidingArea && isQLocked && !isOpen && hasOrder) {
            DeviceIotVO unlock = deviceIotService.unlock(device, DeviceUnLockType.BACK_AREA, "重新返回运营区上电", false);
            operLogService.operSysLog("重新返回运营区上电", unlock.isIotSuccess(), LogBizType.ORDER, orderId, device);
            return 1;
        }

        boolean noRidingOutage = area.getNoRidingOutage() != null && area.getNoRidingOutage(); // 禁行区内断电
        if (noRidingOutage) {
            // 进入禁行区，并且车辆为开锁状态，强制断电
            if (isInNoRidingArea && isOpen) {
                DeviceIotVO qLock = deviceIotService.qLock(device, "进入禁行区，强制断电", false);
                operLogService.operSysLog("进入禁行区，强制断电", qLock.isIotSuccess(), LogBizType.ORDER, orderId, device);
                return 1;
            }

            // 靠近禁行区，并且为开启状态，播报语音警告
            if (isNearNoRidingArea && isOpen) {
                boolean play = deviceIotService.play(device, IotConstants.PLAY_BOUNDARY_NEAR, "靠近禁行区", false);
                operLogService.operSysLog("靠近禁行区，播报语音警告", play, LogBizType.ORDER, orderId, device);
                return 1;
            }
        }

        // 超出运营区域外的最大范围，且车辆为开锁状态，强制断电
        boolean areaOutOutage = area.getAreaOutOutage() != null && area.getAreaOutOutage(); // 运营区外断电
        if (areaOutOutage && hasAreaBoundary) {
            // 超出运营区最大边界 && 有订单 && 不是调度中，强制断电
            if (!isInAreaMax && hasOrder && !isDispatching) {
                DeviceIotVO qLock = deviceIotService.qLock(device, "超出运营区外边界最大值，强制断电", false);
                operLogService.operSysLog("超出运营区外边界最大值，强制断电", qLock.isIotSuccess(), LogBizType.ORDER, orderId, device);
                return 1;
            }
            // 二次警告
            if (!isInArea && isInAreaMax) {
                boolean play = deviceIotService.play(device, IotConstants.PLAY_BOUNDARY_OUT, "超出运营区外边界，还未到达断电距离",
                        false);
                operLogService.operSysLog("超出运营区外边界警告", play, LogBizType.ORDER, orderId, device);
                return 1;
            }
            // 一次警告
            if (isInArea && !isInAreaMin) {
                boolean play = deviceIotService.play(device, IotConstants.PLAY_BOUNDARY_NEAR, "靠近运营区内边界", false);
                operLogService.operSysLog("靠近运营区内边界警告", play, LogBizType.ORDER, orderId, device);
                return 1;
            }
        }

        return 0;
    }

    // 定位日志处理
    private void handleLocationLog(DeviceVO device) {
        // 卫星信号弱，不保存定位日志
        if (DeviceUtil.isLowSatelliteSignal(device.getSatellites())) {
            log.error("卫星信号弱，不处理: {}", device.getMac());
            return;
        }
        // 转换定位日志
        LocationLog po = locationLogConverter.toPo(device);
        if (po == null) {
            log.error("转换定位日志失败: {}", device.getMac());
            return;
        }

        // 暂存到Redis缓存
        redisCache.rightPush(CacheConstants.LOCATION_LOG_QUEUE, po);

        // 发送消息给ws服务
        deviceWebSocketService.sendMessageToDevice(po, device.getMac());
    }
}
