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.dto.TmkStatus;
import com.sunyard.chsm.enums.ManufacturerEnum;
import com.sunyard.chsm.enums.ManufacturerModelEnum;
import com.sunyard.chsm.enums.TmkStatusEnum;
import com.sunyard.chsm.mapper.ParamConfMapper;
import com.sunyard.chsm.mapper.SpDeviceMapper;
import com.sunyard.chsm.mapper.TmkInfoMapper;
import com.sunyard.chsm.model.entity.Device;
import com.sunyard.chsm.model.entity.ParamConf;
import com.sunyard.chsm.model.entity.TmkInfo;
import com.sunyard.chsm.sdf.adapter.BcSdfApiAdaptor;
import com.sunyard.chsm.sdf.adapter.SdfApiAdapter;
import com.sunyard.chsm.sdf.adapter.SdfApiAdapterFactory;
import com.sunyard.chsm.sdf.model.EccCipher;
import com.sunyard.chsm.sdf.model.EccPubKey;
import com.sunyard.chsm.service.DeviceService;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.BeanUtils;
import org.springframework.data.util.Pair;
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 ParamConfMapper paramConfMapper;
    @Resource
    private TmkInfoMapper tmkInfoMapper;

    @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());

        Pair<String, String> pair = checkDeviceSdf(save);
        device.setTmkStatus(TmkStatusEnum.available.name());
        device.setPubKey(pair.getFirst());

        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和端口已存在");
    }

    private Pair<String, String> checkDeviceSdf(DeviceDTO.DeviceSave save) {
        SdfApiAdapter sdfApiAdapter = SdfApiAdapterFactory.newInstance(save.getManufacturerModel(), save.getServiceIp(), save.getServicePort());

        String hd = sdfApiAdapter.openDevice();
        String hs = null;
        try {
            hs = sdfApiAdapter.openSession(hd);
            EccPubKey eccPubKey = sdfApiAdapter.exportEncPublicKeyECC(hs, save.getEncKeyIdx());
            sdfApiAdapter.getPrivateKeyAccessRight(hs, save.getEncKeyIdx(), save.getAccessCredentials().getBytes());
            return Pair.of(eccPubKey.getPubKeyHex(), "");
        } finally {
            Optional.ofNullable(hs).ifPresent(sdfApiAdapter::closeSession);
            sdfApiAdapter.closeDevice(hd);
        }
    }

    @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())) {
            Pair<String, String> pair = checkDeviceSdf(update);
            up.setPubKey(pair.getFirst());
        }

        spDeviceMapper.updateById(up);
    }

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

    @Override
    public TmkStatus getTMKStatus() {
        TmkStatus status = new TmkStatus();
        ParamConf tmkInit = paramConfMapper.selectByKey("tmk_init");
        if (tmkInit != null && "true".equals(tmkInit.getValue())) {
            status.setHasDevice(true);
            status.setTmkInit(true);
            return status;
        }
        status.setTmkInit(false);
        Long c = spDeviceMapper.selectCount(new LambdaQueryWrapper<>());
        status.setHasDevice(c > 0L);
        return status;
    }

    @Override
    public void initTmk() {
        ParamConf tmkInit = paramConfMapper.selectByKey("tmk_init");
        Assert.isTrue(tmkInit == null || !"true".equals(tmkInit.getValue()), "主密钥已经初始化");
//        List<Device> conned = spDeviceMapper.selectConnedList();
//
        LocalDateTime now = LocalDateTime.now();
//        if (CollectionUtils.isEmpty(conned)) {
        //
        BcSdfApiAdaptor sdfApi = new BcSdfApiAdaptor();
        byte[] sk = sdfApi.generateRandom("", 16);

        EccPubKey publicKey = sdfApi.exportEncPublicKeyECC("", 1);
        EccCipher encSk = sdfApi.externalEncryptECC("", publicKey, sk);

        TmkInfo info = new TmkInfo();
        info.setId(IdWorker.getId());
        info.setCreateTime(now);
        info.setDeviceSerial(sdfApi.getDeviceInfo("").getDeviceSerial());
        info.setEncTmk(Hex.toHexString(encSk.getC1C3C2Bytes()));
        info.setPubKey(Hex.toHexString(publicKey.getPubKeyBytes()));
        tmkInfoMapper.insert(info);
//            return;
//        }
//        Device device = conned.iterator().next();
//
//        DeviceContext context = new DeviceContext();
//        context.setManufacturer(device.getManufacturer());
//        context.setManufacturerModel(device.getManufacturerModel());
//        context.setServiceIp(device.getServiceIp());
//        context.setServicePort(device.getServicePort());
//        SdfApiAdapter sdfApi = SdfApiAdapterFactory.newInstance(context);
//        String dh = sdfApi.openDevice();
//        String sh = sdfApi.openSession(dh);
//        DeviceInfo deviceInfo = sdfApi.getDeviceInfo(sh);
//
//        byte[] sk = sdfApi.generateRandom(sh, 16);
//        byte[] publicKey = sdfApi.exportEncPublicKeyECC(sh, 1);
//        byte[] encSk = sdfApi.externalEncryptECC(sh, publicKey, sk);
//
//        TmkInfo info = new TmkInfo();
//        info.setId(IdWorker.getId());
//        info.setCreateTime(now);
//        info.setDeviceSerial(deviceInfo.getDeviceSerial());
//        info.setEncTmk(Hex.toHexString(encSk));
//        info.setPubKey(Hex.toHexString(publicKey));
//        tmkInfoMapper.insert(info);

        //
//        BcSdfApiAdaptor bcApi = new BcSdfApiAdaptor();
//        byte[] bcPubK = bcApi.exportEncPublicKeyECC("", 1);
//        byte[] bcEncSk = sdfApi.exchangeDigitEnvelopeBaseOnECC(sh, 1, bcPubK, encSk);
//
//        TmkInfo bcinfo = new TmkInfo();
//        bcinfo.setId(IdWorker.getId());
//        bcinfo.setCreateTime(now);
//        bcinfo.setDeviceSerial(bcApi.getDeviceInfo("").getDeviceSerial());
//        bcinfo.setEncTmk(Hex.toHexString(bcEncSk));
//        bcinfo.setPubKey(Hex.toHexString(bcPubK));
//        tmkInfoMapper.insert(bcinfo);
//        sdfApi.closeSession(sh);
//        sdfApi.closeDevice(dh);

        if (tmkInit == null) {
            tmkInit = new ParamConf();
            tmkInit.setValue("true");
            tmkInit.setKey("tmk_init");
            tmkInit.setCreatTime(LocalDateTime.now());
            paramConfMapper.insert(tmkInit);
        } else {
            tmkInit.setValue("true");
            paramConfMapper.updateById(tmkInit);
        }
    }

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

}
