package com.ruoyi.iot.service.impl;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

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

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.bst.commandLog.domain.CommandLog;
import com.ruoyi.bst.commandLog.domain.enums.CommandLogType;
import com.ruoyi.bst.commandLog.service.CommandLogService;
import com.ruoyi.bst.device.domain.Device;
import com.ruoyi.bst.device.service.DeviceIotService;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.core.redis.RedisLock;
import com.ruoyi.common.core.redis.enums.RedisLockKey;
import com.ruoyi.common.utils.LogParamHolder;
import com.ruoyi.common.utils.MathUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServiceUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.collection.CollectionUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.iot.constants.IotConstants;
import com.ruoyi.iot.domain.CreateDeviceVo;
import com.ruoyi.iot.domain.CurrentDeviceData;
import com.ruoyi.iot.domain.IotDeviceDetail;
import com.ruoyi.iot.domain.IotDeviceInfo;
import com.ruoyi.iot.domain.response.CommandResponse;
import com.ruoyi.iot.domain.response.CurrentDataPointResponse;
import com.ruoyi.iot.domain.response.DetailResponse;
import com.ruoyi.iot.enums.IotHttpStatus;
import com.ruoyi.iot.interfaces.IotDevice;
import com.ruoyi.iot.service.IotConverter;
import com.ruoyi.iot.service.IotService;
import com.ruoyi.iot.util.IotUtil;
import com.ruoyi.iot.util.TokenUtil;
import com.ruoyi.system.operLog.domain.enums.OperLogIotStatus;

import lombok.extern.slf4j.Slf4j;

/**
 * @author wjh
 *         2024/3/20
 */
@Service
@Slf4j
public class IotServiceImpl implements IotService {
    @Value("${iot.iotHost}")
    private String iotHost;

    @Value(value = "${iot.version}")
    private String version;

    @Value(value = "${iot.resourceName}")
    private String resourceName;

    @Value(value = "${iot.accessKey}")
    private String accessKey;

    @Value(value = "${iot.timeout}")
    private Integer timeout;

    @Value(value = "${iot.daysToExpire}")
    private Long daysToExpire;

    @Value(value = "${iot.productId}")
    private String productId;

    @Autowired
    private IotConverter iotConverter;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private RedisLock redisLock;

    @Autowired
    private DeviceIotService deviceIotService;

    @Autowired
    private ScheduledExecutorService scheduledExecutorService;

    @Autowired
    private CommandLogService commandLogService;

    // 查询OneNet设备在线状态
    @Override
    public String getOnlineStatus(IotDevice device, String type) {
        if (device == null) {
            return DeviceOnlineStatus.OFFLINE.getStatus();
        }
        String deviceName = device.mac();
        if (StringUtils.isAnyBlank(deviceName, productId)) {
            return DeviceOnlineStatus.OFFLINE.getStatus();
        }
        if (IotConstants.ONLINE_TYPE_COMMAND.equals(type)) {
            // 加锁，防止第一次会有多个请求进入
            boolean lock = redisLock.lock(RedisLockKey.DEVICE_ONLINE_COMMAND, deviceName);
            try {
                if (lock) {
                    // log.info("进入lock");
                    // 从缓存获取上次的在线状态，若不存在，则发命令，防止短时间发送命令过于频繁
                    String cacheKey = this.getOnlineCacheKey(deviceName);
                    String status = redisCache.getCacheObject(cacheKey);
                    if (StringUtils.isBlank(status)) {
                        // 发送命令
                        CommandResponse res = uploadData(device, "获取在线状态");
                        status = this.parseToOnlineStatus(res, deviceName, false);
                    }

                    return status;
                } else {
                    return null;
                }
            } finally {
                if (lock) {
                    redisLock.unlock(RedisLockKey.DEVICE_ONLINE_COMMAND, deviceName);
                }
            }
        } else {
            IotDeviceDetail detail = this.getDeviceDetail(deviceName, productId);
            if (detail == null) {
                return DeviceOnlineStatus.OFFLINE.getStatus();
            }
            return IotDeviceDetail.Status.toDeviceOnlineStatus(detail.getStatus());
        }
    }

    private String getOnlineCacheKey(String deviceName) {
        return CacheConstants.DEVICE_ONLINE_STATUS + deviceName;
    }

    private String parseToOnlineStatus(CommandResponse res, String deviceName, boolean increment) {
        String incrementCacheKey = CacheConstants.DEVICE_OFFLINE_INCREMENT + deviceName;
        // 若命令发送成功，则清空redis累加数据
        if (res != null && res.isSuccess()) {
            redisCache.deleteObject(incrementCacheKey);
            return DeviceOnlineStatus.ONLINE.getStatus();
        }
        // 若是离线，则直接返回离线
        if (res != null && res.isNotOnline()) {
            return DeviceOnlineStatus.OFFLINE.getStatus();
        }
        // 若是命令超时或者其他情况，则累加redis数据，超过一定次数则判断为离线
        else {
            Long offlineCount = MathUtils.IntegerToLong(redisCache.getCacheObject(incrementCacheKey));
            if (increment) {
                offlineCount = redisCache.incrementCacheValue(incrementCacheKey, 60, TimeUnit.SECONDS);
            }
            // 超过2次则判断为离线
            if (offlineCount != null && offlineCount >= 2) {
                return DeviceOnlineStatus.OFFLINE.getStatus();
            } else {
                return DeviceOnlineStatus.ONLINE.getStatus();
            }
        }
    }

    @Override
    public List<IotDeviceInfo> getDeviceInfo(List<? extends IotDevice> deviceList) {
        if (CollectionUtils.isEmpty(deviceList)) {
            return Collections.emptyList();
        }

        // 获取数据点列表
        List<String> macList = CollectionUtils.map(deviceList, IotDevice::mac);
        List<CurrentDeviceData> dataList = getCurrentDataPoint(macList, productId);
        if (CollectionUtils.isEmpty(dataList)) {
            return Collections.emptyList();
        }

        // 构造返回数据
        List<IotDeviceInfo> result = new ArrayList<>();
        for (IotDevice device : deviceList) {
            CurrentDeviceData data = dataList.stream()
                    .filter(item -> Objects.equals(device.mac(), item.getTitle()))
                    .findFirst().orElse(null);
            IotDeviceInfo iotDeviceInfo = iotConverter.toIotDeviceInfo(data);
            if (iotDeviceInfo != null) {
                result.add(iotDeviceInfo);
            }
        }

        return result;
    }

    private IotDeviceDetail getDeviceDetail(String deviceName, String productId) {
        String sendUrl = iotHost + IotConstants.ADDS_DEVICE_DETAIL;
        String param = "device_name=" + deviceName + "&product_id=" + productId;

        String token = TokenUtil.getToken();
        String result = HttpUtils.sendGetWithToken(sendUrl, param, token);

        if (!StringUtils.hasText(result)) {
            log.error("与OneNet通信异常");
            return null;
        }
        DetailResponse response = JSONObject.parseObject(result, DetailResponse.class);
        if (!IotHttpStatus.SUCCESS.getCode().equals(response.getCode())) {
            log.error("获取当前设备数据点信息出错：{}", response.getMsg());
            return null;
        }

        return response.getData();
    }

    // 获取当前设备数据点信息
    public List<CurrentDeviceData> getCurrentDataPoint(List<String> deviceNames, String productId) {
        deviceNames = deviceNames.stream().filter(StringUtils::hasText).collect(Collectors.toList());
        if (CollectionUtils.isEmptyElement(deviceNames)) {
            return Collections.emptyList();
        }
        String param = "device_name=" + String.join(",", deviceNames) + "&product_id=" + productId;
        String sendUrl = iotHost + IotConstants.ADDS_CURRENT_DATAPOINTS + "?" + param;

        String result = HttpUtils.sendGetWithToken(sendUrl, null, TokenUtil.getToken());

        // log.info("IOT返回的结果【{}】", result);
        if (!StringUtils.hasText(result)) {
            log.error("与OneNet通信异常");
            return Collections.emptyList();
        }
        CurrentDataPointResponse response = JSONObject.parseObject(result, CurrentDataPointResponse.class);
        if (!IotHttpStatus.SUCCESS.getCode().equals(response.getCode())) {
            log.error("获取当前设备数据点信息出错：" + response.getMsg());
            return Collections.emptyList();
        }

        return response.getData().getDevices();
    }

    @Override
    public int create(String mac) {
        String sendUrl = iotHost + IotConstants.ADDS_CREATE_DEVICE;
        CreateDeviceVo createDeviceVo = new CreateDeviceVo();
        createDeviceVo.setDevice_name(mac);
        createDeviceVo.setProduct_id(productId);
        String result = HttpUtils.sendPostWithToken(sendUrl, JSON.toJSONString(createDeviceVo), TokenUtil.getToken());
        if (!StringUtils.hasText(result)) {
            log.error("与OneNet通信异常");
            return HttpStatus.NOT_FOUND;
        }
        JSONObject paramsObj = JSON.parseObject(result);
        return paramsObj.getInteger("code");
    }

    private CommandResponse sendCommand(IotDevice device, String command, String reason) {
        return sendCommand(device, command, reason, 1);
    }

    private CommandResponse sendCommand(IotDevice device, String command, String reason, int tryCount) {
        if (tryCount > 0) {
            CommandResponse res = sendCommand(device, command, null, reason);
            // 若操作成功 or 设备离线 or 尝试次数小于等于1，则直接返回结果
            if (IotUtil.isSuccess(res) || IotUtil.isOffline(res) || tryCount <= 1) {
                return res;
            }
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                log.error("线程休眠异常", e);
            }
            return sendCommand(device, command, reason, tryCount - 1);
        }
        return null;
    }

    // 发送MQTT命令
    private CommandResponse sendCommand(IotDevice device, String command, Integer timeout, String reason) {
        if (device == null || StringUtils.isAnyBlank(device.mac(), command)) {
            return null;
        }
        if (timeout == null) {
            timeout = this.timeout;
        }
        String deviceName = device.mac();
        String sendUrl = iotHost + IotConstants.ADDS_COMMAND;
        String param = "device_name=" + deviceName + "&product_id=" + productId + "&timeout=" + timeout;
        sendUrl = sendUrl + "?" + param;
        String token = TokenUtil.getToken();
        String result = null;
        try {
            // 发送命令
            result = HttpUtils.sendPostWithToken(sendUrl, command, token);
            ServiceUtil.assertion(!StringUtils.hasText(result), "与OneNet通信异常");
            CommandResponse res = JSON.parseObject(result, CommandResponse.class);

            // 设置日志物联网参数
            LogParamHolder.set(LogParamHolder.IOT_STATUS,
                    IotUtil.isSuccess(res) ? OperLogIotStatus.SUCCESS.getCode() : OperLogIotStatus.FAIL.getCode());

            // 记录成功日志
            this.addCommandLog(device, command, res.getMsg(), reason, res.getCode(), result);

            // 转为在线状态，并缓存结果10秒
            String status = this.parseToOnlineStatus(res, deviceName, true);
            redisCache.setCacheObject(this.getOnlineCacheKey(deviceName), status, 10, TimeUnit.SECONDS);

            // 更新设备在线状态
            this.updateOnlineStatus(deviceName, status);

            return res;
        } catch (Exception e) {
            this.addCommandLog(device, command, "操作失败：" + e.getMessage(), reason, null, result);
            throw e;
        }
    }

    private void updateOnlineStatus(String mac, String status) {
        Device device = new Device();
        device.setMac(mac);
        device.setOnlineStatus(status);
        if (DeviceOnlineStatus.ONLINE.getStatus().equals(status)) {
            device.setLastOnlineTime(LocalDateTime.now());
        }
        deviceIotService.updateIot(device);
    }

    // 新增命令日志
    private void addCommandLog(IotDevice device, String command, String result, String reason, Integer iotCode,
            String originResponse) {
        LoginUser loginUser = null;
        try {
            loginUser = SecurityUtils.getLoginUser();
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        CommandLog po = new CommandLog();
        po.setType(CommandLogType.API.getType());
        po.setMac(device.mac());
        po.setCommand(command);
        po.setResult(result);
        po.setReason(reason);
        po.setIotCode(iotCode);
        po.setLongitude(device.longitude());
        po.setLatitude(device.latitude());
        po.setOriginResponse(originResponse);
        commandLogService.addLog(po, loginUser);
    }

    @Override
    public CommandResponse uploadData(IotDevice device, String reason) {
        return sendCommand(device, IotConstants.COMMAND_UPLOAD_DATA, reason);
    }

    @Override
    public CommandResponse unlock(IotDevice device, int sub, String reason, int tryCount) {
        String command = IotConstants.COMMAND_UNLOCK + IotConstants.COMMAND_SUB + sub + IotConstants.COMMAND_SEPARATOR;
        return sendCommand(device, command, reason, tryCount);
    }

    @Override
    public CommandResponse lock(IotDevice device, int sub, String reason, int tryCount) {
        String command = IotConstants.COMMAND_LOCK + IotConstants.COMMAND_SUB + sub + IotConstants.COMMAND_SEPARATOR;
        return sendCommand(device, command, reason, tryCount);
    }

    @Override
    public CommandResponse qLock(IotDevice device, int sub, String reason, int tryCount) {
        String command = IotConstants.COMMAND_QLOSE + IotConstants.COMMAND_SUB + sub + IotConstants.COMMAND_SEPARATOR;
        return sendCommand(device, command, reason, tryCount);
    }

    @Override
    public CommandResponse play(IotDevice device, String type, String reason) {
        if (StringUtils.isBlank(type)) {
            return null;
        }
        return sendCommand(device, IotConstants.COMMAND_PLAY + type, reason);
    }

    @Override
    public CommandResponse reboot(IotDevice device, String reason) {
        return sendCommand(device, IotConstants.COMMAND_REBOOT, reason);
    }

    @Override
    public CommandResponse unlockSeat(IotDevice device, String reason) {
        return sendCommand(device, IotConstants.COMMAND_HPEN, reason);
    }

    @Override
    public CommandResponse tempLock(IotDevice device, int sub, String reason, int tryCount) {
        return sendCommand(device, IotConstants.COMMAND_LLOSE, reason, tryCount);
    }

    @Override
    public CommandResponse setMusic(IotDevice device, String music, String reason) {
        if (StringUtils.isBlank(music)) {
            return null;
        }
        return sendCommand(device, IotConstants.COMMAND_MUSIC + music, reason);
    }

}
