package com.njzscloud.supervisory.device.service;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njzscloud.common.core.ex.Exceptions;
import com.njzscloud.common.core.utils.R;
import com.njzscloud.common.localizer.Localizer;
import com.njzscloud.common.localizer.contant.LocalizerType;
import com.njzscloud.common.localizer.mqtt.param.*;
import com.njzscloud.common.mp.support.PageParam;
import com.njzscloud.common.mp.support.PageResult;
import com.njzscloud.common.mqtt.support.MqttMsg;
import com.njzscloud.common.mqtt.util.Mqtt;
import com.njzscloud.common.ws.support.Websocket;
import com.njzscloud.supervisory.device.contant.LocalizerCategory;
import com.njzscloud.supervisory.device.mapper.DeviceLocalizerMapper;
import com.njzscloud.supervisory.device.pojo.entity.DeviceLocalizerEntity;
import com.njzscloud.supervisory.device.pojo.entity.LocalizerConfig;
import com.njzscloud.supervisory.device.pojo.param.SearchDeviceLocalizerParam;
import com.njzscloud.supervisory.device.pojo.result.DeviceInfoResult;
import com.njzscloud.supervisory.device.pojo.result.HeartbeatResult;
import com.njzscloud.supervisory.device.pojo.result.SearchDeviceLocalizerResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * 定位器
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceLocalizerService extends ServiceImpl<DeviceLocalizerMapper, DeviceLocalizerEntity> implements IService<DeviceLocalizerEntity> {

    /**
     * 新增
     */
    @Transactional(rollbackFor = Exception.class)
    public void add(DeviceLocalizerEntity deviceLocalizerEntity) {
        String terminalId = deviceLocalizerEntity.getTerminalId();
        LocalizerCategory localizerCategory = deviceLocalizerEntity.getLocalizerCategory();
        Assert.notEmpty(terminalId, () -> Exceptions.exception("设备号不能为空"));
        Assert.notNull(localizerCategory, () -> Exceptions.exception("定位器类型不能为空"));
        Assert.isFalse(this.exists(Wrappers.lambdaQuery(DeviceLocalizerEntity.class)
                        .eq(DeviceLocalizerEntity::getTerminalId, terminalId)
                        .eq(DeviceLocalizerEntity::getLocalizerCategory, localizerCategory)),
                () -> Exceptions.exception("定位器已存在"));
        this.save(deviceLocalizerEntity
                .setLastTime(LocalDateTime.now())
                .setConfig(new LocalizerConfig()
                        .setSpeedThreshold(60)
                        .setEnableWarn(true)
                ));
        CompletableFuture.runAsync(() -> {
            ThreadUtil.sleep(10000);
            Mqtt.subscribe(terminalId + "/online", this::heartbeat);
            Mqtt.subscribe(terminalId + "/device_info", this::updateDeviceLocalizerStatus);
            // Mqtt.subscribe(terminalId + "/track_location_real", truckLocationTrackService::sendRealTimeData);
            reflash();
        });
    }

    /**
     * 修改
     */
    @Transactional(rollbackFor = Exception.class)
    public void modify(DeviceLocalizerEntity deviceLocalizerEntity) {
        String terminalId = deviceLocalizerEntity.getTerminalId();
        LocalizerCategory localizerCategory = deviceLocalizerEntity.getLocalizerCategory();
        Long id = deviceLocalizerEntity.getId();
        DeviceLocalizerEntity oldData = this.getById(id);
        Assert.notNull(oldData, () -> Exceptions.exception("定位器不存在"));
        Assert.isFalse(this.exists(Wrappers.lambdaQuery(DeviceLocalizerEntity.class)
                        .eq(DeviceLocalizerEntity::getTerminalId, StrUtil.isEmpty(terminalId) ? oldData.getTerminalId() : terminalId)
                        .eq(DeviceLocalizerEntity::getLocalizerCategory, localizerCategory == null ? oldData.getLocalizerCategory() : localizerCategory)
                        .ne(DeviceLocalizerEntity::getId, id)
                ),
                () -> Exceptions.exception("定位器已存在"));
        this.updateById(new DeviceLocalizerEntity()
                .setId(id)
                .setTerminalId(terminalId)
                .setLocalizerCategory(localizerCategory)
        );
        CompletableFuture.runAsync(() -> {
            ThreadUtil.sleep(10000);
            Mqtt.subscribe(terminalId + "/online", this::heartbeat);
            Mqtt.subscribe(terminalId + "/device_info", this::updateDeviceLocalizerStatus);
            // Mqtt.subscribe(terminalId + "/track_location_real", truckLocationTrackService::sendRealTimeData);
            reflash();
        });
    }

    /**
     * 删除
     */
    @Transactional(rollbackFor = Exception.class)
    public void del(List<Long> ids) {
        List<DeviceLocalizerEntity> deviceLocalizerEntities = baseMapper.canDel(Wrappers.query(DeviceLocalizerEntity.class).in("a.id", ids));
        Assert.isTrue(deviceLocalizerEntities.isEmpty(), () -> Exceptions.exception("存在已被关联的定位器，不能删除"));
        this.removeBatchByIds(ids);
        CompletableFuture.runAsync(() -> {
            ThreadUtil.sleep(10000);
            reflash();
        });
    }

    /**
     * 详情
     */
    public SearchDeviceLocalizerResult detail(Long id) {
        return baseMapper.detail(id);
    }

    /**
     * 分页查询
     */
    public PageResult<SearchDeviceLocalizerResult> paging(PageParam pageParam, SearchDeviceLocalizerParam searchDeviceLocalizerParam) {
        String terminalId = searchDeviceLocalizerParam.getTerminalId();
        LocalizerCategory localizerCategory = searchDeviceLocalizerParam.getLocalizerCategory();
        String licensePlate = searchDeviceLocalizerParam.getLicensePlate();
        Boolean online = searchDeviceLocalizerParam.getOnline();

        QueryWrapper<Object> ew = Wrappers.query()
                .eq("a.deleted", false)
                .like(StrUtil.isNotEmpty(terminalId), "a.terminal_id", terminalId)
                .like(StrUtil.isNotEmpty(licensePlate), "b.license_plate", licensePlate)
                .eq(localizerCategory != null, "a.localizer_category", localizerCategory)
                .eq(online != null, "a.online", online);

        if (online != null) {
            if (online) {
                ew.ge("a.last_time", LocalDateTime.now().minusMinutes(5));
            } else {
                ew.lt("a.last_time", LocalDateTime.now().minusMinutes(5));
            }
        }
        return PageResult.of(baseMapper.paging(pageParam.toPage(), ew));
    }

    public void onApplicationReady() {
        Mqtt.subscribe("localizer/loader", this::localizerLoader);
        CompletableFuture.runAsync(() -> {
            ThreadUtil.sleep(3000);
            reflash();
        });
        List<DeviceLocalizerEntity> localizerEntities = this.list();
        if (localizerEntities.isEmpty()) return;
        List<String> collect = localizerEntities.stream().map(DeviceLocalizerEntity::getTerminalId).distinct().collect(Collectors.toList());

        for (String terminalId : collect) {
            Mqtt.subscribe(terminalId + "/online", this::heartbeat);
            Mqtt.subscribe(terminalId + "/device_info", this::updateDeviceLocalizerStatus);
            // Mqtt.subscribe(terminalId + "/track_location_real", truckLocationTrackService::sendRealTimeData);
        }
    }

    public void heartbeat(MqttMsg msg) {
        HeartbeatResult heartbeatResult = msg.getMsg(HeartbeatResult.class);
        if (heartbeatResult == null) return;
        this.update(Wrappers.lambdaUpdate(DeviceLocalizerEntity.class)
                // .set(DeviceLocalizerEntity::getOnline, heartbeatResult.isOnline())
                .set(DeviceLocalizerEntity::getLastTime, heartbeatResult.getTime())
                .eq(DeviceLocalizerEntity::getTerminalId, heartbeatResult.getTerminalId()));
    }

    public void localizerLoader(MqttMsg msg) {
        // CompletableFuture.runAsync(this::reflash);
        reflash();
    }

    public void updateDeviceLocalizerStatus(MqttMsg msg) {
        DeviceInfoResult deviceInfoResult = msg.getMsg(DeviceInfoResult.class);
        if (deviceInfoResult == null) return;
        String terminalId = deviceInfoResult.getTerminalId();
        DeviceLocalizerEntity deviceLocalizerEntity = this.getOne(Wrappers.lambdaQuery(DeviceLocalizerEntity.class).eq(DeviceLocalizerEntity::getTerminalId, terminalId));
        if (deviceLocalizerEntity == null) return;
        this.updateById(new DeviceLocalizerEntity()
                .setId(deviceLocalizerEntity.getId())
                .setConfig(new LocalizerConfig()
                        .setIp(deviceInfoResult.getServerIp())
                        .setPort(deviceInfoResult.getServerPort())
                        .setEnableWarn(deviceInfoResult.getEnableWarn() == 1)
                        .setSpeedThreshold(deviceInfoResult.getSpeedThreshold())
                ));
        Websocket.publish("down/reflash/device_info", R.success(MapUtil.builder()
                .put("terminalId", terminalId)
                .build()));
    }

    @Transactional(rollbackFor = Exception.class)
    public void modifyConfig(Long id, LocalizerConfig config) {
        DeviceLocalizerEntity localizerEntity = this.getById(id);
        Assert.notNull(localizerEntity, () -> Exceptions.exception("定位器不存在"));

        String terminalId = localizerEntity.getTerminalId();
        LocalizerConfig oldConfig = localizerEntity.getConfig();
        if (oldConfig == null) oldConfig = new LocalizerConfig();
        String ip = config.getIp();
        Integer port = config.getPort();
        Integer speedThreshold = config.getSpeedThreshold();
        Boolean enableWarn = config.getEnableWarn();

        if (StrUtil.isNotBlank(ip) && port != null && port > 0) {
            oldConfig.setIp(ip).setPort(port);
        }
        if (speedThreshold != null && speedThreshold > 0) {
            oldConfig.setSpeedThreshold(speedThreshold);
        }
        if (enableWarn != null) {
            oldConfig.setEnableWarn(enableWarn);
        }
        this.updateById(new DeviceLocalizerEntity().setId(id).setConfig(oldConfig));
        CompletableFuture.runAsync(() -> {
            ThreadUtil.sleep(10000);
            reflash();
        });
        // this.configDevice(terminalId, config);
    }

    private void configDevice(String terminalId, LocalizerConfig config) {
        String ip = config.getIp();
        Integer port = config.getPort();
        Integer speedThreshold = config.getSpeedThreshold();
        Boolean enableWarn = config.getEnableWarn();

        if (StrUtil.isNotBlank(ip) && port != null && port > 0) {
            modifyIpPort(terminalId, ip, port);
        }

        /* if (speedThreshold != null && speedThreshold > 0) {
            modifySpeedThreshold(terminalId, speedThreshold);
        }

        if (enableWarn != null) {
            enableWarn(terminalId, enableWarn);
        } */
    }

    private void modifyIpPort(String terminalId, String ip, int port) {
        if (StrUtil.isEmpty(ip) || port <= 0) return;
        Mqtt.publish("location/config_ip_port", new ConfigIpPortParam()
                .setTerminalId(terminalId)
                .setIp(ip)
                .setPort(port)
        );
    }

    private void modifySpeedThreshold(String terminalId, int modifySpeedThreshold) {
        if (modifySpeedThreshold <= 0) return;
        Mqtt.publish("location/speed_threshold", new SpeedThresholdParam()
                .setTerminalId(terminalId)
                .setSpeed(modifySpeedThreshold)
        );
    }

    private void enableWarn(String terminalId, boolean enableWarn) {
        Mqtt.publish("location/warn", new EnableWarnParam()
                .setTerminalId(terminalId)
                .setEnable(enableWarn)
        );
    }

    public void reflashConfig(Long id) {
        DeviceLocalizerEntity localizerEntity = this.getById(id);
        Assert.notNull(localizerEntity, () -> Exceptions.clierr("设备不存在"));
        String terminalId = localizerEntity.getTerminalId();
        Mqtt.publish("location/device_info", new ObtainDeviceInfoParam().setTerminalId(terminalId));
    }

    public void reflash() {
        List<DeviceLocalizerEntity> localizerEntities = this.list();
        if (localizerEntities.isEmpty()) return;
        List<Localizer> collect = localizerEntities.stream()
                .map(it -> {
                            LocalizerConfig config = it.getConfig();
                            return new Localizer()
                                    .setTerminalId(it.getTerminalId())
                                    .setSpeedThreshold(config == null || config.getSpeedThreshold() == null ? 60 : config.getSpeedThreshold())
                                    .setLocalizerType(LocalizerType.valueOf(it.getLocalizerCategory().getVal()));
                        }
                )
                .collect(Collectors.toList());
        Mqtt.publish("location/load", collect);
    }

    public void sendDirective(Long id, String directive) {
        DeviceLocalizerEntity localizerEntity = this.getById(id);
        Assert.notNull(localizerEntity, () -> Exceptions.clierr("设备不存在"));
        String terminalId = localizerEntity.getTerminalId();
        Mqtt.publish("localizer/send_directive", new SendDirectiveParam()
                .setTerminalId(terminalId)
                .setDirective(directive)
        );
    }

    public void enableTrack(Long id) {
        DeviceLocalizerEntity localizerEntity = this.getById(id);
        Assert.notNull(localizerEntity, () -> Exceptions.clierr("设备不存在"));
        String terminalId = localizerEntity.getTerminalId();
        Mqtt.publish("location/track", MapUtil.builder()
                .put("terminalId", terminalId)
                .put("interval", 1)
                .build());
    }
}
