package com.sunyard.chsm.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.dto.DeviceDTO;
import com.sunyard.chsm.enums.DeviceTmkStatus;
import com.sunyard.chsm.enums.ManufacturerEnum;
import com.sunyard.chsm.enums.ManufacturerModelEnum;
import com.sunyard.chsm.mapper.SpDeviceMapper;
import com.sunyard.chsm.model.dto.DeviceCheckRes;
import com.sunyard.chsm.model.entity.Device;
import com.sunyard.chsm.service.DeviceService;
import com.sunyard.chsm.service.TmkService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author liulu
 * @since 2024/10/17
 */
@Slf4j
@Service("sp_DeviceServiceImpl")
@Transactional
public class DeviceServiceImpl implements DeviceService {

    @Resource
    private SpDeviceMapper spDeviceMapper;
    @Resource
    private TmkService tmkService;

    @Override
    public Page<DeviceDTO.DeviceView> selectPageList(DeviceDTO.Query query) {
        LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<Device>()
                .eq(StringUtils.hasText(query.getManufacturers()), Device::getManufacturer, query.getManufacturers())
                .eq(StringUtils.hasText(query.getModel()), Device::getManufacturerModel, query.getModel())
                .like(StringUtils.hasText(query.getName()), Device::getName, query.getName())
                .orderByDesc(Device::getCreateTime);

        IPage<Device> page = spDeviceMapper.selectPage(
                new Page<>(query.getPageNumber(), query.getPageSize()),
                wrapper);
        List<Device> records = page.getRecords();
        if (CollectionUtils.isEmpty(records)) {
            return new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        }
        List<DeviceDTO.DeviceView> viewList = records.stream()
                .map(it -> {
                    DeviceDTO.DeviceView view = new DeviceDTO.DeviceView();
                    BeanUtils.copyProperties(it, view);
                    view.setIsGrouped(it.getGroupId() > 0);
                    Optional.ofNullable(ManufacturerEnum.of(it.getManufacturer()))
                            .map(ManufacturerEnum::getName)
                            .ifPresent(view::setManufacturerText);
                    Optional.ofNullable(ManufacturerModelEnum.of(it.getManufacturerModel()))
                            .map(ManufacturerModelEnum::getName)
                            .ifPresent(view::setManufacturerModelText);
                    return view;
                })
                .collect(Collectors.toList());

        return new Page<DeviceDTO.DeviceView>(page.getCurrent(), page.getSize(), page.getTotal()).setRecords(viewList);
    }

    @Override
    public List<DeviceDTO.DeviceView> getUngroupedList() {

        LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<Device>()
                .eq(Device::getGroupId, 0L);
        List<Device> devices = spDeviceMapper.selectList(wrapper);
        return devices.stream()
                .map(it -> {
                    DeviceDTO.DeviceView view = new DeviceDTO.DeviceView();
                    BeanUtils.copyProperties(it, view);
                    Optional.ofNullable(ManufacturerEnum.of(it.getManufacturer()))
                            .map(ManufacturerEnum::getName)
                            .ifPresent(view::setManufacturerText);
                    Optional.ofNullable(ManufacturerModelEnum.of(it.getManufacturerModel()))
                            .map(ManufacturerModelEnum::getName)
                            .ifPresent(view::setManufacturerModelText);
                    return view;
                })
                .collect(Collectors.toList());
    }

    @Override
    public DeviceDTO.Detail getDetailById(Long id) {
        Device exist = spDeviceMapper.selectById(id);
        Assert.notNull(exist, "查询的数据不存在");

        DeviceDTO.Detail detail = new DeviceDTO.Detail();
        BeanUtils.copyProperties(exist, detail);
        Optional.ofNullable(ManufacturerEnum.of(exist.getManufacturer()))
                .map(ManufacturerEnum::getName)
                .ifPresent(detail::setManufacturerText);
        Optional.ofNullable(ManufacturerModelEnum.of(exist.getManufacturerModel()))
                .map(ManufacturerModelEnum::getName)
                .ifPresent(detail::setManufacturerModelText);

        // todo
        detail.setCpuRate(0.5D);
        detail.setMemoryRate(0.5D);
        detail.setDiskRate(0.5D);

        return detail;
    }


    @Override
    public Long save(DeviceDTO.DeviceSave save) {
        ManufacturerEnum manufacturer = ManufacturerEnum.of(save.getManufacturer());
        Assert.notNull(manufacturer, "不支持的设备厂商: " + save.getManufacturer());
        ManufacturerModelEnum manufacturerModel = ManufacturerModelEnum.of(save.getManufacturerModel());
        Assert.notNull(manufacturerModel, "不支持的设备型号: " + save.getManufacturerModel());
        Assert.isTrue(manufacturerModel.getManufacturer() == manufacturer, "设备厂商和型号不匹配");

        checkName(save.getName());
        checkIpPort(save.getServiceIp(), save.getServicePort());

        Device device = new Device();
        BeanUtils.copyProperties(save, device);
        device.setId(IdWorker.getId());
        device.setGroupId(0L);
        device.setGroupName("");
        device.setWeight(1);
        device.setCreateTime(LocalDateTime.now());

        DeviceCheckRes checkRes = tmkService.checkDevice(device);
        Assert.isTrue(!checkRes.isHasError(), checkRes.getMessage());

        device.setTmkStatus(checkRes.getStatus().name());
        device.setDeviceSerial(checkRes.getDeviceSerial());
        device.setPubKey(checkRes.getPubKey());
        device.setEncTmk(checkRes.getEncTmk());

        spDeviceMapper.insert(device);
        return device.getId();
    }

    private void checkIpPort(String serviceIp, Integer servicePort) {
        LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<Device>()
                .eq(Device::getServiceIp, serviceIp)
                .eq(Device::getServicePort, servicePort);
        Device exist = spDeviceMapper.selectOne(wrapper);
        Assert.isNull(exist, "设备IP和端口已存在");
    }

    @Override
    public void update(DeviceDTO.DeviceSave update) {
        Assert.notNull(update.getId(), "id不能为空");
        Device exist = spDeviceMapper.selectById(update.getId());
        Assert.notNull(exist, "id对应的设备不存在");

        if (!Objects.equals(exist.getName(), update.getName())) {
            checkName(update.getName());
        }
        boolean ipChanged = !Objects.equals(exist.getServiceIp(), update.getServiceIp())
                || !Objects.equals(exist.getServicePort(), update.getServicePort());
        if (ipChanged) {
            checkIpPort(update.getServiceIp(), update.getServicePort());
        }

        Device up = new Device();
        BeanUtils.copyProperties(update, up);
        up.setUpdateTime(LocalDateTime.now());

        if (ipChanged
                || !Objects.equals(exist.getEncKeyIdx(), update.getEncKeyIdx())
                || !Objects.equals(exist.getAccessCredentials(), update.getAccessCredentials())) {

            DeviceCheckRes checkRes = tmkService.checkDevice(up);
            Assert.isTrue(!checkRes.isHasError(), checkRes.getMessage());

            up.setTmkStatus(checkRes.getStatus().name());
            up.setDeviceSerial(checkRes.getDeviceSerial());
            up.setPubKey(checkRes.getPubKey());
            up.setEncTmk(checkRes.getEncTmk());
        }
        spDeviceMapper.updateById(up);
    }

    @Override
    public void delete(Long id) {
        spDeviceMapper.deleteById(id);
    }

    private void checkName(String name) {
        LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<Device>()
                .eq(Device::getName, name);
        Device exist = spDeviceMapper.selectOne(wrapper);
        Assert.isNull(exist, "设备名称已存在");
    }

    @Override
    public void checkDeviceStatus() {

        for (int i = 1; i < 500; i++) {
            Page<Device> devicePage = spDeviceMapper.selectPage(
                    new Page<>(i, 20L),
                    new LambdaQueryWrapper<Device>().orderByAsc(Device::getId)
            );
            List<Device> records = devicePage.getRecords();
            if (CollectionUtils.isEmpty(records)) {
                break;
            }
            for (Device record : records) {
                DeviceCheckRes checkRes = tmkService.checkDevice(record);
                Device up = new Device();
                up.setId(record.getId());
                up.setConnected(checkRes.getStatus() != DeviceTmkStatus.device_error);
                LocalDateTime now = LocalDateTime.now();
                if (up.getConnected()) {
                    up.setLastConnectedTime(now);
                }
                up.setLastCheckTime(now);

                up.setTmkStatus(checkRes.getStatus().name());
                up.setDeviceSerial(checkRes.getDeviceSerial());
                if (!Objects.equals(checkRes.getPubKey(), record.getPubKey())) {
                    up.setPubKey(checkRes.getPubKey());
                }
                up.setEncTmk(checkRes.getEncTmk());
                spDeviceMapper.updateById(up);
            }
        }
    }
}
