package com.ruoyi.iot.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.constant.IotConstants;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException;
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.common.utils.oneNet.Token;
import com.ruoyi.iot.domain.*;
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.domain.response.HistoryDataPointResponse;
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.ss.commandLog.service.ICommandLogService;
import com.ruoyi.ss.device.domain.enums.DeviceOnlineStatus;
import com.ruoyi.ss.device.domain.vo.DeviceVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
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;

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

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

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

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

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

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

    @Value(value = "${debug}")
    private Boolean debug;

    @Autowired
    private IotConverter iotConverter;

    @Autowired
    private ScheduledExecutorService scheduledExecutorService;

    @Autowired
    private ICommandLogService commandLogService;

    // 查询OneNet设备在线状态
    @Override
    public DeviceOnlineStatus getOnlineStatus(String deviceName, String productId) {
        if (StringUtils.isAnyBlank(deviceName, productId)) {
            return DeviceOnlineStatus.OFFLINE;
        }
//        CommandResponse response = sendCommand(deviceName, "111");
//        return IotHttpStatus.SUCCESS.getCode().equals(response.getCode()) ? DeviceOnlineStatus.ONLINE : DeviceOnlineStatus.OFFLINE;

        IotDeviceDetail detail = this.getDeviceDetail(deviceName, productId);
        if (detail == null) {
            return DeviceOnlineStatus.OFFLINE;
        }

        return IotDeviceDetail.Status.toDeviceOnlineStatus(detail.getStatus());
    }

    @Override
    public DeviceOnlineStatus getOnlineStatus(IotDevice device) {
        DeviceOnlineStatus status = DeviceOnlineStatus.OFFLINE;
        if (device == null || StringUtils.isBlank(device.getProductId())) {
            return status;
        }
        // 优先使用mac1判断
        if (StringUtils.hasText(device.iotMac1())) {
            status = this.getOnlineStatus(device.iotMac1(), device.getProductId());
        }
        // 若还是离线，则判断mac2是否在线
        if (status == DeviceOnlineStatus.OFFLINE && StringUtils.hasText(device.iotMac2())) {
            status = this.getOnlineStatus(device.iotMac2(), device.getProductId());
        }
        return status;
    }

    // 通电
    private boolean open(String deviceName, String productId, String reason) {
        if (StringUtils.hasBlank(deviceName, productId)) {
            return false;
        }
        CommandResponse response = sendCommand(deviceName, IotConstants.COMMAND_OPEN, productId, reason);
        IotHttpStatus status = IotHttpStatus.convertByCode(response.getCode());
        if (!IotHttpStatus.SUCCESS.equals(status)) {
            log.error("{}通电异常：{}", deviceName, status.getMsg());
            throw new ServiceException("通电发生异常：" + status.getMsg());
        }
        return true;
    }

    @Override
    public boolean open(IotDevice device, String reason) {
        boolean result = false;
        if (device == null || StringUtils.isBlank(device.getProductId())) {
            return result;
        }

        // 尝试用mac1通电
        try {
            result = this.open(device.iotMac1(), device.getProductId(), reason);
            if (!result) {
                log.error("mac1通电失败：{}", device.iotMac1());
                throw new ServiceException("mac1通电失败");
            }
        } catch (Exception e) {
            log.info("mac1通电失败，尝试用mac2通电");
            if (!StringUtils.isAnyBlank(device.iotMac2(), device.getProductId())) {
                result = this.open(device.iotMac2(), device.getProductId(), reason);
            } else {
                throw e;
            }
        }

        return result;
    }

    // 断电
    private boolean close(String deviceName, String productId, String reason) {
        if (StringUtils.isAnyBlank(deviceName, productId)) {
            return false;
        }
        CommandResponse response = sendCommand(deviceName, IotConstants.COMMAND_CLOSE, productId, reason);
        IotHttpStatus status = IotHttpStatus.convertByCode(response.getCode());
        if (!IotHttpStatus.SUCCESS.equals(status)) {
            log.error("断电发生异常：{}, {}", deviceName, status.getMsg());
            throw new ServiceException("断电发生异常：" + status.getMsg());
        }
        return true;
    }

    @Override
    public boolean close(IotDevice device, String reason) {
        boolean result = false;
        if (device == null || StringUtils.isBlank(device.getProductId())) {
            return result;
        }

        // 尝试用mac1断电
        try {
            result = this.close(device.iotMac1(), device.getProductId(), reason);
            if (!result) {
                throw new ServiceException("mac1断电失败");
            }
        } catch (Exception e) {
            log.info("mac1断电失败:{}，尝试用mac2断电", e.getMessage());
            if (!StringUtils.isAnyBlank(device.iotMac2(), device.getProductId())) {
                result = this.close(device.iotMac2(), device.getProductId(), reason);
            } else {
                throw e;
            }
        }

        return result;
    }

    // 获取历史设备数据点信息
    @Override
    public HistoryDeviceData getHistoryDataPoint(String deviceName, String productId) {
        String param = "device_name=" + deviceName + "&product_id=" + productId;
        String sendUrl = iotHost + IotConstants.ADDS_HISTORY_DATAPOINTS + "?"+param;

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

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

    @Override
    public CommandResponse setVxs(DeviceVO device, BigDecimal vxs, String reason) {
        ServiceUtil.assertion(device == null || vxs == null, "设备或参数为空");

        CommandResponse res = null;
        if (StringUtils.hasText(device.iotMac1())) {
            res = this.setVxs(device.iotMac1(), vxs, device.getProductId(), reason);
        }
        if ((res == null || !res.isSuccess()) && StringUtils.hasText(device.iotMac2())) {
            res = this.setVxs(device.iotMac2(), vxs, device.getProductId(), reason);
        }

        return res;
    }

    private CommandResponse setVxs(String deviceName, BigDecimal vxs, String productId, String reason) {
        if (StringUtils.isBlank(deviceName) || vxs == null || productId == null) {
            return null;
        }
        String command = IotConstants.COMMAND_SET_VXS + vxs + IotConstants.COMMAND_SEPARATOR;
        return sendCommand(deviceName, command, productId, reason);
    }

    // 获取当前设备数据点信息
    public List<CurrentDeviceData> getCurrentDataPoint(List<String> deviceNames, String productId) {
        String param = "device_name=" + String.join(",", deviceNames) + "&product_id=" + productId;
        String sendUrl = iotHost+ IotConstants.ADDS_CURRENT_DATAPOINTS + "?" + param;

        String result = HttpUtils.sendGetWithToken(sendUrl, null, Token.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();
    }

    // 获取当前设备数据点信息
    private CurrentDeviceData getCurrentDataPoint(String deviceName, String productId) {
        List<CurrentDeviceData> list = this.getCurrentDataPoint(Collections.singletonList(deviceName), productId);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        return list.stream().filter(item -> Objects.equals(item.getTitle(), deviceName)).findFirst().orElse(null);
    }

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

        String token = Token.getToken();
//        log.info("IOT获取到Authorization:【{}】",token);
        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();
    }

    /**
     * 设置剩余时长
     *
     * @param deviceName 设备名称
     * @param seconds    时长（秒）
     * @param productId
     * @param reason
     * @return 是否成功
     */
    private CommandResponse setTime(String deviceName, long seconds, String productId, String reason) {
        if (seconds < 0) {
            throw new ServiceException("设置剩余时长参数错误：读数不允许小于0");
        }
        return sendCommand(deviceName, IotConstants.COMMAND_RECHARGE + seconds + IotConstants.COMMAND_SEPARATOR, 5, productId, reason);
    }

    @Override
    public CommandResponse setTime(IotDevice device, long seconds, String reason) {
        if (device == null || StringUtils.isBlank(device.getProductId())) {
            throw new ServiceException("设置时长错误：参数不允许为空");
        }

        CommandResponse res = null;
        if (StringUtils.hasText(device.iotMac1())) {
            res = this.setTime(device.iotMac1(), seconds, device.getProductId(), reason);
        }
        if ((res == null || !res.isSuccess()) && StringUtils.hasText(device.iotMac2())) {
            res = this.setTime(device.iotMac2(), seconds, device.getProductId(), reason);
        }

        return res;
    }

    private CommandResponse addEle(String deviceName, BigDecimal ele, String productId, String reason) {
        if (ele == null || ele.compareTo(BigDecimal.ZERO) < 0) {
            throw new ServiceException("充值电量错误：充值电量不允许小于0");
        }
        return sendCommand(deviceName, IotConstants.COMMAND_ADD_ELE + ele.multiply(BigDecimal.valueOf(1000)) + IotConstants.COMMAND_SEPARATOR, 5, productId, reason);
    }

    @Override
    public CommandResponse addEle(IotDevice device, BigDecimal ele, String reason) {
        if (device == null || StringUtils.isBlank(device.getProductId())) {
            throw new ServiceException("充值电量错误：参数不允许为空");
        }

        CommandResponse res = null;
        if (StringUtils.hasText(device.iotMac1())) {
            res = this.addEle(device.iotMac1(), ele, device.getProductId(), reason);
        }
        if ((res == null || !res.isSuccess()) && StringUtils.hasText(device.iotMac2())) {
            res = this.addEle(device.iotMac2(), ele, device.getProductId(), reason);
        }

        return res;
    }

    private CommandResponse setEle(String deviceName, BigDecimal ele, String productId, String reason) {
        if (ele == null) {
            throw new ServiceException("设置电量错误：电量不允许为空");
        }
        return sendCommand(deviceName, IotConstants.COMMAND_SET_ELE + ele.multiply(BigDecimal.valueOf(1000)) + IotConstants.COMMAND_SEPARATOR, 5, productId, reason);
    }

    @Override
    public CommandResponse setEle(IotDevice device, BigDecimal ele, String reason) {
        if (device == null || StringUtils.isBlank(device.getProductId())) {
            throw new ServiceException("设置电量错误：参数不允许为空");
        }

        CommandResponse res = null;
        if (StringUtils.hasText(device.iotMac1())) {
            res = this.setEle(device.iotMac1(), ele, device.getProductId(), reason);
        }
        if ((res == null || !res.isSuccess()) && StringUtils.hasText(device.iotMac2())) {
            res = this.setEle(device.iotMac2(), ele, device.getProductId(), reason);
        }

        return res;
    }

    private IotDeviceInfo getDeviceInfo(String deviceName, String productId) {
        if (StringUtils.isAnyBlank(deviceName, productId)) {
            return null;
        }
        CurrentDeviceData currentDataPoint = getCurrentDataPoint(deviceName, productId);
        if (currentDataPoint == null) {
            return null;
        }
        return iotConverter.toIotDeviceInfo(currentDataPoint, null);
    }

    @Override
    public IotDeviceInfo getDeviceInfo(IotDevice device) {
        if (device == null || StringUtils.isBlank(device.getProductId())) {
            return null;
        }

        CurrentDeviceData data1 = getCurrentDataPoint(device.iotMac1(), device.getProductId());
        CurrentDeviceData data2 = getCurrentDataPoint(device.iotMac2(), device.getProductId());

        return iotConverter.toIotDeviceInfo(data1, data2);
    }

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

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

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

        return result;
    }

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

    private CommandResponse sendCommand(String deviceName, String command, String productId, String reason) {
        return sendCommand(deviceName, command, null, productId, reason);
    }

    // 发送MQTT命令
    private CommandResponse sendCommand(String deviceName, String command, Integer timeout, String productId, String reason) {
        if (timeout == null) {
            timeout = this.timeout;
        }
        String sendUrl = iotHost + IotConstants.ADDS_COMMAND;
        String param = "device_name=" + deviceName + "&product_id=" + productId +"&timeout=" + timeout;
        sendUrl = sendUrl  + "?" + param;
        String token = Token.getToken();
        try {
            String result = HttpUtils.sendPostWithToken(sendUrl, command, token);
            ServiceUtil.assertion(!StringUtils.hasText(result), "与OneNet通信异常");
            CommandResponse res = JSON.parseObject(result, CommandResponse.class);
            this.addCommandLogAsync(deviceName, command, res.getMsg(), reason);
            return res;
        } catch (Exception e) {
            this.addCommandLogAsync(deviceName, command, "操作失败：" + e.getMessage(), reason);
            throw e;
        }
    }

    // 异步添加日志
    private void addCommandLogAsync(String deviceName, String command, String result, String reason) {
        LoginUser loginUser = null;
        try {
             loginUser = SecurityUtils.getLoginUser();
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        LoginUser finalLoginUser = loginUser;
        scheduledExecutorService.schedule(() -> {
            commandLogService.addApiLog(deviceName, command, result, reason, finalLoginUser);
        }, 0, TimeUnit.SECONDS);
    }

    @Override
    public CommandResponse trySetTime(IotDevice device, long seconds, int tryCount, String reason) {
        CommandResponse res = this.setTime(device, seconds, reason);
        if (res == null || !res.isSuccess()) {
            if (tryCount > 0) {
                try {
                    Thread.sleep(1000);
                    return trySetTime(device, seconds, --tryCount, reason);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return res;
    }

    @Override
    public CommandResponse trySetEle(IotDevice device, BigDecimal ele, int tryCount, String reason) {
        CommandResponse res = this.setEle(device, ele, reason);
        if (res == null || !res.isSuccess()) {
            if (tryCount > 0) {
                try {
                    Thread.sleep(1000);
                    return trySetEle(device, ele, --tryCount, reason);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return res;
    }

    @Override
    public CommandResponse setWifi(IotDevice device, String wifiName, String wifiPwd, String reason) {

        CommandResponse res = null;
        if (StringUtils.hasText(device.iotMac1())) {
            res = this.setWifi(device.iotMac1(), device.getProductId(), wifiName, wifiPwd, reason);
        }
        if ((res == null || !res.isSuccess()) && StringUtils.hasText(device.iotMac2())) {
            res = this.setWifi(device.iotMac2(), device.getProductId(), wifiName, wifiPwd, reason);
        }

        return res;
    }

    private CommandResponse setWifi(String mac, String productId, String wifiName, String wifiPwd, String reason) {
        if (StringUtils.hasBlank(mac, productId)) {
            return null;
        }
        String command = IotConstants.COMMAND_SET_SSID + IotConstants.COMMAND_SEPARATOR + wifiName
                + IotConstants.COMMAND_SET_PASS + IotConstants.COMMAND_SEPARATOR + wifiPwd ;

        return sendCommand(mac, command, productId, reason);
    }

    @Override
    public boolean setVoice(IotDevice device, long seconds, String reason) {
        if (device == null) {
            return false;
        }

        try {
            boolean res = this.setVoice(device.iotMac1(), device.getProductId(), seconds, reason);
            ServiceUtil.assertion(!res, "设备MAC1设置语音播报时间失败");
            return true;
        } catch (Exception e) {
            log.warn("设备MAC1设置语音播报时间失败: {}", e.getMessage());
            return this.setVoice(device.iotMac2(), device.getProductId(), seconds, reason);
        }
    }

    private boolean setVoice(String mac, String productId, long seconds, String reason) {
        if (StringUtils.isBlank(mac) || StringUtils.isBlank(productId)) {
            return false;
        }
        CommandResponse res = this.sendCommand(mac, IotConstants.COMMAND_SET_VOICE + seconds + IotConstants.COMMAND_SEPARATOR, productId, reason);
        ServiceUtil.assertion(!res.isSuccess(), "设置语音播报失败：" + res.getMsg());
        return res.isSuccess();
    }

    @Override
    public boolean setTotalEle(IotDevice device, BigDecimal totalEle, String reason) {
        if (device == null || totalEle.compareTo(BigDecimal.ZERO) < 0) {
            return false;
        }

        try {
            boolean res = this.setTotalEle(device.iotMac1(), device.getProductId(), totalEle, reason);
            ServiceUtil.assertion(!res, "设备MAC1设置总电量失败");
            return res;
        } catch (Exception e) {
            log.warn("设备MAC1设置总电量失败: {}", e.getMessage());
            return this.setTotalEle(device.iotMac2(), device.getProductId(), totalEle, reason);
        }
    }

    private boolean setTotalEle(String deviceName, String productId, BigDecimal totalEle, String reason) {
        if (StringUtils.isAnyBlank(deviceName, productId)) {
            return false;
        }
        if (totalEle.compareTo(BigDecimal.ZERO) < 0) {
            return false;
        }

        CommandResponse res = this.sendCommand(deviceName, IotConstants.COMMAND_SET_TOTAL_ELE + totalEle + IotConstants.COMMAND_SEPARATOR, productId, reason);
        return res != null && res.isSuccess();
    }

}
