package com.ruoyi.bst.device.service.impl;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import com.ruoyi.bst.device.domain.Device;
import com.ruoyi.bst.device.domain.DeviceQuery;
import com.ruoyi.bst.device.domain.DeviceVO;
import com.ruoyi.bst.device.domain.dto.DeviceBltUploadDTO;
import com.ruoyi.bst.device.domain.enums.DeviceLocationType;
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.mapper.DeviceMapper;
import com.ruoyi.bst.device.service.DeviceConverter;
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.orderDevice.domain.enums.OrderDeviceStatus;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constants.ServiceCode;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.enums.LogBizType;
import com.ruoyi.common.utils.MathUtils;
import com.ruoyi.common.utils.ServiceUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.collection.CollectionUtils;
import com.ruoyi.iot.constants.IotConstants;
import com.ruoyi.iot.domain.IotDeviceInfo;
import com.ruoyi.iot.domain.response.CommandResponse;
import com.ruoyi.iot.enums.IotHttpStatus;
import com.ruoyi.iot.service.IotService;
import com.ruoyi.iot.util.IotUtil;
import com.ruoyi.system.operLog.service.OperLogService;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class DeviceIotServiceImpl implements DeviceIotService {

    @Autowired
    private IotService iotService;

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private DeviceService deviceService;

    @Autowired
    private ScheduledExecutorService scheduledExecutorService;

    @Autowired
    private DeviceConverter deviceConverter;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private OperLogService operLogService;

    private final static Integer SUB_FAST = 5; // 上报频率（快）
    private final static Integer SUB_SLOW = 300; // 上报频率（慢）
    private final static Integer SUB_X_SLOW = 50; // 上报频率（X开头硬件）

    @Override
    public DeviceIotVO unlock(DeviceVO device, DeviceUnLockType type, String reason, boolean requiredIot, int tryCount) {
        if (device == null || device.getId() == null) {
            return new DeviceIotVO(0, false);
        }
        DeviceIotVO vo = new DeviceIotVO();

        // 更新设备状态
        Device data = new Device();
        data.setLockStatus(DeviceLockStatus.OPEN.getCode());
        DeviceQuery query = new DeviceQuery();
        query.setId(device.getId());

        // 管理员解锁
        if (DeviceUnLockType.ADMIN.equals(type)) {
            ServiceUtil.assertion(!DeviceStatus.canAdminUnlock().contains(device.getStatus()), "设备%s当前状态不允许管理员开锁",
                    device.getSn());
            data.setStatus(DeviceStatus.DISPATCHING.getCode());
            query.setStatusList(DeviceStatus.canAdminUnlock());
        }
        // 用户解锁
        else if (DeviceUnLockType.USER.equals(type)) {
            ServiceUtil.assertion(!DeviceStatus.canUserUnlock().contains(device.getStatus()), "设备%s当前状态不允许用户开锁",
                    device.getSn());
            data.setStatus(DeviceStatus.IN_USE.getCode());
            query.setStatusList(DeviceStatus.canUserUnlock());
        }
        // 返回运营区上电
        else if (DeviceUnLockType.BACK_AREA.equals(type)) {
            ServiceUtil.assertion(!DeviceStatus.canBackUnlock().contains(device.getStatus()), "设备%s当前状态不允许返回运营区开锁",
                    device.getSn());
            data.setStatus(DeviceStatus.IN_USE.getCode());
            query.setStatusList(DeviceStatus.canBackUnlock());
        }

        transactionTemplate.execute(status -> {
            // 更新数据库
            int rows = deviceMapper.updateByQuerySimple(data, query);
            vo.setDb(rows);

            if (rows > 0) {
                // 发送命令开锁
                CommandResponse res = iotService.unlock(device, SUB_FAST, reason, tryCount);
                boolean iot = IotUtil.isSuccess(res);
                ServiceUtil.assertion(requiredIot && !iot, IotUtil.getMsg(res), ServiceCode.IOT_FAILED);
                vo.setIot(iot);
            }

            return rows;
        });

        return vo;
    }

    @Override
    public DeviceIotVO lock(DeviceVO device, boolean isAdmin, String reason, boolean requiredIot, int tryCount) {
        if (device == null || device.getId() == null) {
            return new DeviceIotVO(0, false);
        }
        DeviceIotVO vo = new DeviceIotVO();

        // 是否有正在进行的订单
        boolean hasOrder = device.getOrderDeviceId() != null
                && OrderDeviceStatus.inUse().contains(device.getOrderDeviceStatus());

        // 构造数据
        Device data = new Device();
        data.setLockStatus(DeviceLockStatus.CLOSE.getCode());
        data.setStatus(hasOrder ? DeviceStatus.TEMP_LOCKED.getCode() : DeviceStatus.AVAILABLE.getCode());
        DeviceQuery query = new DeviceQuery();
        query.setId(device.getId());
        if (isAdmin) {
            ServiceUtil.assertion(!DeviceStatus.canAdminLock().contains(device.getStatus()), "设备%s当前状态不允许管理员锁车",
                    device.getSn());
            query.setStatusList(DeviceStatus.canAdminLock());
        } else {
            ServiceUtil.assertion(!DeviceStatus.canUserLock().contains(device.getStatus()), "设备%s当前状态不允许用户锁车",
                    device.getSn());
            query.setStatusList(DeviceStatus.canUserLock());
        }
        if (DeviceStatus.TEMP_LOCKED.getCode().equals(data.getStatus())) {
            query.setOrderDeviceId(device.getOrderDeviceId());
            query.setOrderDeviceStatusList(OrderDeviceStatus.inUse());
        }

        transactionTemplate.execute(status -> {
            // 更新设备状态
            int rows = deviceMapper.updateByQuery(data, query);
            vo.setDb(rows);

            if (rows > 0) {
                // 发送命令锁车
                CommandResponse res = null;

                int sub = getDeviceSubSlow(device);
                if (DeviceStatus.TEMP_LOCKED.getCode().equals(data.getStatus())) {
                    // 临时锁车
                    res = iotService.tempLock(device, sub, reason, tryCount);
                } else {
                    // 锁车
                    res = iotService.lock(device, sub, reason, tryCount);
                }
                boolean iot = IotUtil.isSuccess(res);
                ServiceUtil.assertion(requiredIot && !iot, IotUtil.getMsg(res), ServiceCode.IOT_FAILED);
                vo.setIot(iot);
            }

            return rows;
        });

        return vo;
    }

    public int getDeviceSubSlow(DeviceVO device) {
        if (device != null && device.getHardwareVersion() != null && device.getHardwareVersion().startsWith("X")) {
            return SUB_X_SLOW;
        }
        return SUB_SLOW;
    }

    @Override
    public DeviceIotVO qLock(DeviceVO device, String reason, boolean requiredIot, int tryCount) {
        if (device == null || device.getId() == null) {
            return new DeviceIotVO(0, false);
        }
        ServiceUtil.assertion(!DeviceStatus.canQLock().contains(device.getStatus()), "设备%s当前状态不允许强制锁车", device.getSn());

        DeviceIotVO vo = new DeviceIotVO();

        // 是否有正在进行的订单
        boolean hasOrder = device.getOrderDeviceId() != null
                && OrderDeviceStatus.inUse().contains(device.getOrderDeviceStatus());

        transactionTemplate.execute(status -> {
            // 更新设备状态
            Device data = new Device();
            data.setLockStatus(DeviceLockStatus.CLOSE.getCode());
            data.setStatus(hasOrder ? DeviceStatus.Q_LOCKED.getCode() : DeviceStatus.AVAILABLE.getCode());
            DeviceQuery query = new DeviceQuery();
            query.setId(device.getId());
            query.setStatusList(DeviceStatus.canQLock());
            int rows = deviceMapper.updateByQuerySimple(data, query);
            vo.setDb(rows);

            if (rows > 0) {
                // 发送命令锁车
                CommandResponse res = iotService.qLock(device, SUB_FAST, reason, tryCount);
                boolean iot = IotUtil.isSuccess(res);
                ServiceUtil.assertion(requiredIot && !iot, IotUtil.getMsg(res), ServiceCode.IOT_FAILED);
                vo.setIot(iot);
            }

            return rows;
        });

        return vo;
    }

    @Override
    public boolean play(DeviceVO device, String type, String reason, boolean requiredIot) {
        if (device == null) {
            return false;
        }
        CommandResponse res = iotService.play(device, type, reason);
        boolean success = IotUtil.isSuccess(res);
        ServiceUtil.assertion(requiredIot && !success, IotUtil.getMsg(res), ServiceCode.IOT_FAILED);
        return success;
    }

    @Override
    public boolean reboot(DeviceVO device, String reason, boolean requiredIot) {
        if (device == null) {
            return false;
        }
        CommandResponse res = iotService.reboot(device, reason);
        boolean success = IotUtil.isSuccess(res);
        ServiceUtil.assertion(requiredIot && !success, IotUtil.getMsg(res), ServiceCode.IOT_FAILED);
        return success;
    }

    @Override
    public boolean unlockSeat(DeviceVO device, String reason, boolean requiredIot) {
        if (device == null) {
            return false;
        }
        CommandResponse res = iotService.unlockSeat(device, reason);
        boolean success = IotUtil.isSuccess(res);
        ServiceUtil.assertion(requiredIot && !success, IotUtil.getMsg(res), ServiceCode.IOT_FAILED);
        return success;
    }

    @Override
    public void refresh(List<DeviceVO> deviceList, String onlineType) {
        if (CollectionUtils.isEmptyElement(deviceList)) {
            return;
        }

        // 获取物联网信息
        List<IotDeviceInfo> iotList = iotService.getDeviceInfo(deviceList);
        if (CollectionUtils.isEmptyElement(iotList)) {
            return;
        }

        // 更新设备信息
        for (DeviceVO device : deviceList) {
            if (device == null || StringUtils.isBlank(device.getMac())) {
                continue;
            }
            IotDeviceInfo iot = iotList.stream()
                    .filter(item -> Objects.equals(item.getMac(), device.getMac()))
                    .findFirst().orElse(null);
            DeviceUtil.setIotInfo(device, iot);
            this.updateIot(device);
        }

        // 异步发送命令，强制设备上报数据
        if (onlineType != null) {
            scheduledExecutorService.execute(() -> {
                for (DeviceVO device : deviceList) {
                    try {
                        if (onlineType != null) {
                            iotService.getOnlineStatus(device, onlineType);
                        }
                    } catch (Exception e) {
                        log.error("强制设备{}上报数据失败", device.getSn(), e);
                    }
                }
            });
        }
    }

    @Override
    public int updateIot(Device device) {
        if (device == null || StringUtils.isBlank(device.getMac())) {
            return 0;
        }

        Map<String, Object> info = deviceConverter.toCacheInfo(device);

        String key = CacheConstants.DEVICE_CACHE_INFO + device.getMac();
        redisCache.setCacheMap(key, info);

        return 1;
    }

    @Override
    public boolean create(String mac) {
        if (StringUtils.isBlank(mac)) {
            return false;
        }
        int code = iotService.create(mac);
        IotHttpStatus status = IotHttpStatus.convertByCode(code);
        ServiceUtil.assertion(!status.equals(IotHttpStatus.SUCCESS) && !status.equals(IotHttpStatus.DEVICE_EXIST),
                "设备【%s】注册失败:%s", mac, status.getMsg());
        return true;
    }

    @Override
    public DeviceIotVO lock(Long id, boolean isAdmin, String reason, boolean requiredIot) {
        DeviceVO device = deviceMapper.selectDeviceById(id);
        return this.lock(device, isAdmin, reason, requiredIot);
    }

    @Override
    public DeviceIotVO unlock(Long id, DeviceUnLockType type, String reason, boolean requiredIot) {
        DeviceVO device = deviceMapper.selectDeviceById(id);
        return this.unlock(device, type, reason, requiredIot);
    }

    @Override
    public void monitor(List<Long> ids) {
        DeviceQuery query = new DeviceQuery();
        query.setIds(ids);
        List<Long> idList = deviceMapper.selectIdList(query);
        if (CollectionUtils.isEmptyElement(idList)) {
            return;
        }

        // 监控设备
        for (Long deviceId : idList) {
            try {
                // 获取设备最新信息
                DeviceVO device = deviceService.selectDeviceById(deviceId);
                this.monitor(device);
            } catch (Exception e) {
                log.error("监控设备id={}出错：{}", deviceId, e.getMessage());
            }
        }
    }

    private void monitor(DeviceVO device) {
        if (device == null) {
            return;
        }
        if (DeviceLockStatus.CLOSE.getCode().equals(device.getLockStatus())
                && !DeviceQuality.CLOSE.getCode().equals(device.getQuality())) {
            CommandResponse res = iotService.lock(device, getDeviceSubSlow(device), "重新尝试锁车", 1);
            if (device.getOrderId() != null) {
                operLogService.operSysLog("【设备监控】发现未关闭的车辆", IotUtil.isSuccess(res), LogBizType.ORDER,
                        device.getOrderId(), device);
            } else {
                operLogService.operSysLog("【设备监控】发现未关闭的车辆", IotUtil.isSuccess(res), LogBizType.DEVICE, device.getId(),
                        device);
            }
        }
    }

    @Override
    public void monitor(String mac) {
        DeviceVO device = deviceService.selectByMac(mac);
        this.monitor(device);
    }

    @Override
    public DeviceIotVO setMusic(DeviceVO device, String music, String reason, boolean requiredIot) {
        ServiceUtil.assertion(device == null, "设备不存在");
        ServiceUtil.assertion(StringUtils.isBlank(music), "声音设置不能为空");

        String softwareVersion = device.getSoftwareVersion();
        ServiceUtil.assertion(softwareVersion == null, "当前设备暂无软件版本，无法设置声音");
        if (IotConstants.MUSIC_VOICE.equals(music)) {
            ServiceUtil.assertion(MathUtils.smallerThan(softwareVersion, "6000"), "当前设备软件版本低于6000，无法设置为语音");
        }
        if (IotConstants.MUSIC_RING.equals(music)) {
            ServiceUtil.assertion(MathUtils.smallerThan(softwareVersion, "6900"), "当前设备软件版本低于6900，无法设置为彩铃");
        }

        DeviceIotVO vo = new DeviceIotVO();

        transactionTemplate.execute(status -> {
            Device data = new Device();
            data.setId(device.getId());
            data.setMusic(music);
            int rows = deviceMapper.updateDevice(data);
            vo.setDb(rows);

            if (rows > 0) {
                CommandResponse res = iotService.setMusic(device, music, reason);
                boolean success = IotUtil.isSuccess(res);
                ServiceUtil.assertion(requiredIot && !success, IotUtil.getMsg(res), ServiceCode.IOT_FAILED);
                vo.setIot(success);
            }

            return rows;
        });

        return vo;
    }

    @Override
    public int bltUpload(DeviceBltUploadDTO dto) {
        ServiceUtil.assertion(dto == null, "数据不能为空");

        DeviceVO device = deviceService.selectByMac(dto.getMac());
        ServiceUtil.assertion(device == null, "设备不存在");

        // 更新设备信息
        DeviceUtil.setIotSysInfo(device, dto.getSys(), LocalDateTime.now(), true, DeviceLocationType.PHONE);

        this.updateIot(device);

        return 1;
    }

    @Override
    public Map<String, Object> getIotInfo(String mac) {
        if (StringUtils.isBlank(mac)) {
            return null;
        }
        String key = CacheConstants.DEVICE_CACHE_INFO + mac;
        return redisCache.getCacheMap(key);
    }

}
