package net.northking.cctp.device.api.activity.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.github.pagehelper.page.PageMethod;
import net.northking.cctp.common.cache.EntityNameCache;
import net.northking.cctp.common.cache.EntityNameCatalog;
import net.northking.cctp.common.db.Pagination;
import net.northking.cctp.common.exception.PlatformRuntimeException;
import net.northking.cctp.common.http.QueryByPage;
import net.northking.cctp.common.util.UUIDUtil;
import net.northking.cctp.device.config.EngineConfig;
import net.northking.cctp.device.constants.DeviceConstants;
import net.northking.cctp.device.constants.DeviceError;
import net.northking.cctp.device.db.entity.*;
import net.northking.cctp.device.db.service.*;
import net.northking.cctp.device.dto.device.CdMobileDeviceExitDto;
import net.northking.cctp.device.dto.device.DeviceInfoDto;
import net.northking.cctp.device.dto.device.DeviceLockDto;
import net.northking.cctp.device.dto.device.DeviceLogDto;
import net.northking.cctp.device.dto.pcDevice.CdDeviceRemoteDto;
import net.northking.cctp.device.dto.pcDevice.CdPcDeviceExitDto;
import net.northking.cctp.device.dto.pcDevice.CdPcDeviceRemoteDto;
import net.northking.cctp.device.util.IpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;


@Service
public class DeviceActivityApiServiceImpl implements DeviceActivityApiService {

    private final static Logger logger = LoggerFactory.getLogger(DeviceActivityApiServiceImpl.class);

    @Autowired
    private CdDeviceTokenService cdDeviceTokenService;

    @Autowired
    private CdDeviceCtrlService cdDeviceCtrlService;

    @Autowired
    private CdMobileDeviceService cdMobileDeviceService;

    @Autowired
    private CdPcDeviceService cdPcDeviceService;

    @Autowired
    private CdEngineInfoService cdEngineInfoService;

    @Autowired
    private CdDeviceUsageLogService cdDeviceUsageLogService;

    @Autowired
    private CdDeviceStatusService cdDeviceStatusService;

    @Autowired
    private CdDeviceModelService cdDeviceModelService;

    @Autowired
    private CdMobileBrandService cdMobileBrandService;

    @Autowired
    private CdPcUserService cdPcUserService;

    @Autowired
    private CdDeviceProjectService cdDeviceProjectService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisLockRegistry redisLockRegistry;

    @Autowired
    private EntityNameCache entityNameCache;

    @Autowired
    private IpUtil ipUtil;
    @Autowired
    private EngineConfig engineConfig;

    @Override
    public DeviceInfoDto getInfoFromToken(String token) {
        CdDeviceToken query = new CdDeviceToken();
        query.setToken(token);
        List<CdDeviceToken> resultList = this.cdDeviceTokenService.query(query);
        if (resultList == null || resultList.isEmpty()) {
            return new DeviceInfoDto();
        }
        CdDeviceToken deviceToken = resultList.get(0);
        DeviceInfoDto result = new DeviceInfoDto();
        result.setToken(deviceToken.getToken());
        String id = deviceToken.getId();
        if (deviceToken.getDeviceType().equals(DeviceConstants.DEVICE_MOB_TYPE)){
            //移动端信息
            CdMobileDevice mobile = this.cdMobileDeviceService.findByPrimaryKey(id);
            result.setCdMobileDevice(mobile);
            CdDeviceCtrl ctrl = this.cdDeviceCtrlService.findByPrimaryKey(mobile.getCtrlId());
            result.setCdDeviceCtrl(ctrl);
            //获取设备机型和品牌
            CdDeviceModel deviceModel = this.cdDeviceModelService.findByPrimaryKey(mobile.getModelId());

            CdMobileBrand brand = this.cdMobileBrandService.findByPrimaryKey(deviceModel.getBrandId());
            result.setCdMobileBrand(brand);
            result.setCdDeviceModel(deviceModel);
            LinkedHashMap<String,Object> link = (LinkedHashMap<String, Object>) redisTemplate.opsForValue().get(DeviceConstants.MOBILE_INFO_PREFIX + id);
            if (link!=null) {
                link.put(CdMobileDevice.KEY_platform,mobile.getPlatform());
                link.put(CdMobileDevice.KEY_platformVersion,mobile.getPlatformVersion());
                link.put(CdDeviceModel.KEY_screenSize,deviceModel.getScreenSize());
                link.put(CdDeviceModel.KEY_resolution,deviceModel.getResolution());
                link.put(CdDeviceModel.KEY_cpuBrand,deviceModel.getCpuBrand());
                link.put(CdDeviceModel.KEY_cpuModel,deviceModel.getCpuModel());
                link.put(CdDeviceModel.KEY_cpuCoreNum,deviceModel.getCpuCoreNum());
                link.put(CdDeviceModel.KEY_ramSize,deviceModel.getRamSize());
                result.setDeviceInfo(link);
            }
            String stfAddr = (String) redisTemplate.opsForValue().get(DeviceConstants.STF_PREFIX + id);
            if (stfAddr!=null) {
                result.setStfAddr(stfAddr);
            }
            // 根据分数排序获取引擎id集合
            Set<String> engineIdSet = redisTemplate.opsForZSet().rangeByScore(DeviceConstants.ENGINE_MOB_ACTIVE_THREAD_KEY,
                    0, 100, 0, 1);
            if (engineIdSet == null || engineIdSet.size() <= 0){
                throw new PlatformRuntimeException(DeviceError.ENGINE_IS_NULL);
            }
            CdEngineInfo cdEngineInfo = this.cdEngineInfoService.findByPrimaryKey(engineIdSet.iterator().next());
            if (cdEngineInfo == null){
                throw new PlatformRuntimeException(DeviceError.ENGINE_IS_NULL);
            }
            result.setEngineInfo(cdEngineInfo);
        }else {
            //PC信息
            CdPcDevice pcDevice = this.cdPcDeviceService.findByPrimaryKey(id);
            result.setCdPcDevice(pcDevice);
            CdEngineInfo ngQuery = new CdEngineInfo();
            ngQuery.setDeviceId(id);
            List<CdEngineInfo> ngList = this.cdEngineInfoService.query(ngQuery);
            if (ngList != null && !ngList.isEmpty()) {
                result.setEngineInfo(ngList.get(0));
            }else {
                CdEngineInfo cdEngineInfo = new CdEngineInfo();
                cdEngineInfo.setIpAddr(pcDevice.getIpAddr());
                result.setEngineInfo(cdEngineInfo);
            }
            //PC设备账号信息
            CdPcUser queryDevice = new CdPcUser();
            queryDevice.setDeviceId(id);
            List<CdPcUser> userList = cdPcUserService.query(queryDevice);
            if (userList!=null && userList.size()>0){
                result.setAccount(userList.get(0).getAccount());
                result.setPwd(userList.get(0).getPwd());
            }
        }
        return result;
    }


    @Override
    public Boolean checkToken(DeviceLockDto dto) {
        CdDeviceToken deviceToken = this.cdDeviceTokenService.findByPrimaryKey(dto.getDeviceId());
        if (!deviceToken.getToken().equals(dto.getToken())){
            throw new PlatformRuntimeException(DeviceError.DEVICE_TOKEN_ERROR);
        }
        return true;
    }

    @Override
    public Pagination<CdDeviceUsageLog> getUsageLog(QueryByPage<DeviceLogDto> dto) {
        Pagination<CdDeviceUsageLog> result = this.cdDeviceUsageLogService.pageQuery(dto);
        for (CdDeviceUsageLog record : result.getRecords()) {
            String userName = entityNameCache.translateById(EntityNameCatalog.MONSTER_USER, record.getUserBy());
            if (userName == null) {
                userName = DeviceConstants.SERVICE_USE;
            }
            record.setUserBy(userName);
        }
        return result;
    }

    @Override
    public DeviceInfoDto pubGetInfo(String uuid) {
        CdDeviceStatus status = this.cdDeviceStatusService.findByPrimaryKey(uuid);
        DeviceInfoDto dto = new DeviceInfoDto();

        if (status!=null){
            switch (status.getDeviceType()){
                case DeviceConstants.DEVICE_MOB_TYPE:
                    //查询移动设备 上位机
                    CdMobileDevice mobileDevice = this.cdMobileDeviceService.findByPrimaryKey(uuid);
                    dto.setCdMobileDevice(mobileDevice);

                    CdDeviceModel deviceModel = this.cdDeviceModelService.findByPrimaryKey(mobileDevice.getModelId());
                    dto.setCdDeviceModel(deviceModel);

                    CdMobileBrand brand = this.cdMobileBrandService.findByPrimaryKey(deviceModel.getBrandId());
                    dto.setCdMobileBrand(brand);

                    CdDeviceCtrl deviceCtrl = this.cdDeviceCtrlService.findByPrimaryKey(mobileDevice.getCtrlId());
                    dto.setCdDeviceCtrl(deviceCtrl);
                    LinkedHashMap<String,Object> map = (LinkedHashMap<String, Object>) redisTemplate.opsForValue().get(DeviceConstants.MOBILE_INFO_PREFIX + uuid);
                    if (map!=null) {
                        map.put(DeviceConstants.DEVICE_PLATFORM,mobileDevice.getPlatform());
                        map.put(DeviceConstants.DEVICE_PLATFORM_VERSION,mobileDevice.getPlatformVersion());
                        map.put(DeviceConstants.DEVICE_SCREEN_SIZE,deviceModel.getScreenSize());
                        map.put(DeviceConstants.DEVICE_RESOLUTION,deviceModel.getResolution());
                        map.put(DeviceConstants.DEVICE_CPU_BRAND,deviceModel.getCpuBrand());
                        map.put(DeviceConstants.DEVICE_CPU_MODEL,deviceModel.getCpuModel());
                        map.put(DeviceConstants.DEVICE_CPU_CORE_NUM,deviceModel.getCpuCoreNum());
                        map.put(DeviceConstants.DEVICE_RAM_SIZE,deviceModel.getRamSize());
                        dto.setDeviceInfo(map);
                    }
                    String stfAddr = (String) redisTemplate.opsForValue().get(DeviceConstants.STF_PREFIX + uuid);
                    if (stfAddr!=null) {
                        dto.setStfAddr(stfAddr);
                    }
                    break;
                case DeviceConstants.DEVICE_PC_TYPE:
                    CdPcDevice pcDevice = this.cdPcDeviceService.findByPrimaryKey(uuid);
                    dto.setCdPcDevice(pcDevice);
                    CdEngineInfo engine = new CdEngineInfo();
                    engine.setDeviceId(uuid);
                    List<CdEngineInfo> query = this.cdEngineInfoService.query(engine);
                    dto.setEngineInfo(query.get(0));
                    break;
                default:
                    break;
            }
        }
        return dto;
    }

    @Override
    public List<Map<String, Object>> checkDeviceStatus(List<String> deviceIds) {
        List<Object> ids = new ArrayList<>(deviceIds);
        List<CdDeviceStatus> statusList = this.cdDeviceStatusService.findByPrimaryKeys(ids);

        List<Map<String,Object>> resultList = new ArrayList<>();
        for (CdDeviceStatus deviceStatus : statusList) {
            if (deviceStatus.getDeviceStatus().equals(DeviceConstants.DEVICE_STATUS_OFFLINE)){
                Map<String,Object> map = new HashMap<>();
                map.put(DeviceConstants.DEVICE_ID,deviceStatus.getId());
                map.put(DeviceConstants.DEVICE_LAST_HEART_BEAT_TIME,deviceStatus.getLastHeartbeatTime());
                resultList.add(map);
            }
        }
        return resultList;
    }

    @Override
    public List<Map<String, Object>> queryDeviceInfo(Set<String> deviceList) {
        List<Map<String,Object>> mapList = new ArrayList<>();
        for (String deviceId : deviceList) {
            if(StringUtils.isEmpty(deviceId)){
                continue;
            }
            CdDeviceStatus deviceStatus = this.cdDeviceStatusService.findByPrimaryKey(deviceId);
            Map<String, Object> map = new HashMap<>();
            map.put(DeviceConstants.DEVICE_UUID,deviceId);
            if (deviceStatus.getDeviceType().equals(DeviceConstants.DEVICE_MOB_TYPE)) {
                //移动设备 型号 RAM 系统 分辨率
                CdMobileDevice mobileDevice = this.cdMobileDeviceService.findByPrimaryKey(deviceId);
                CdDeviceModel model = this.cdDeviceModelService.findByPrimaryKey(mobileDevice.getModelId());
                map.put(DeviceConstants.DEVICE_NAME,model.getProductName());
                map.put(DeviceConstants.DEVICE_TYPE,DeviceConstants.DEVICE_MOB_TYPE);
                map.put(DeviceConstants.MODEL_NAME, model.getModelName());
                String system;
                if (mobileDevice.getPlatform().equals(DeviceConstants.MOBILE_PLATFORM_ANDROID)) {
                    system = DeviceConstants.SYS_NAME_ANDROID;
                } else {
                    system = DeviceConstants.SYS_NAME_IOS;
                }
                map.put(DeviceConstants.DEVICE_SYSTEM, system + model.getSystemVersion());
                map.put(DeviceConstants.DEVICE_RAM_SIZE,StringUtils.isEmpty(model.getRamSize())? "" :model.getRamSize());
                map.put(DeviceConstants.DEVICE_RESOLUTION, model.getResolution());
            }
            if (deviceStatus.getDeviceType().equals(DeviceConstants.DEVICE_PC_TYPE)) {
                //Pc设备
                CdPcDevice device = this.cdPcDeviceService.findByPrimaryKey(deviceId);
                map.put(DeviceConstants.DEVICE_NAME, device.getDeviceName());
                map.put(DeviceConstants.DEVICE_TYPE,DeviceConstants.DEVICE_PC_TYPE);
                map.put(DeviceConstants.DEVICE_IP_ADDR, device.getIpAddr());
                map.put(DeviceConstants.DEVICE_MAC_ADDR, device.getMacAddr());
                map.put(DeviceConstants.DEVICE_OS_NAME, device.getOsName());
                map.put(DeviceConstants.DEVICE_RAM_SIZE, device.getRamSize());
            }
            mapList.add(map);
        }
        return mapList;
    }

    @Override
    public Map<String, Object> queryMobDevicePlatform(List<String> deviceIds) {
        boolean hasIOS = false;
        boolean hasAndroid = false;
        List<CdMobileDevice> list = this.cdMobileDeviceService.queryDeviceList(deviceIds);
        for (CdMobileDevice device : list) {
            if (device.getPlatform().equals(DeviceConstants.MOBILE_PLATFORM_ANDROID)){
                hasAndroid = true;
            }
            if (device.getPlatform().equals(DeviceConstants.MOBILE_PLATFORM_IOS)){
                hasIOS = true;
            }
        }
        Map<String,Object> map = new HashMap<>();
        map.put(DeviceConstants.DEVICE_HAS_ANDROID,hasAndroid);
        map.put(DeviceConstants.DEVICE_HAS_IOS,hasIOS);
        return map;
    }

    @Override
    @Transactional
    public List<Map<String, String>> deviceListApply(Map<String, Object> map, HttpServletRequest request) {
        List<Map<String, String>> mapList = new ArrayList<>();
        String type = (String) map.get(DeviceConstants.MAP_KEY_TYPE);
        String id = (String) map.get(DeviceConstants.DEVICE_UUID);
        String userBy = DeviceConstants.ENGINE_NAME;
        Object listMap = map.get(DeviceConstants.DEVICE_LIST);
        List<String> deviceList = JSONObject.parseObject(JSON.toJSONString(listMap),new TypeReference<List<String>>(){});
        if (type.equals(DeviceConstants.DEVICE_MOB_TYPE)) {

            // 根据缓存中引擎线程使用状态判断是否可申请设备
            Set<ZSetOperations.TypedTuple<String>> mobActiveThreadTopOneSet =
                    redisTemplate.opsForZSet().rangeByScoreWithScores(DeviceConstants.ENGINE_MOB_ACTIVE_THREAD_KEY,
                            0, 100, 0, 1);
            if (mobActiveThreadTopOneSet == null || mobActiveThreadTopOneSet.isEmpty()){
                logger.error("缓存中引擎线程使用状态数据不存在，无法判断引擎是否可用");
                return null;
            }

            ZSetOperations.TypedTuple<String> topOne = mobActiveThreadTopOneSet.iterator().next();
            String engineId = topOne.getValue();
            if (!id.equals(engineId)){
                logger.error("存在其他更空闲的引擎[{}]，当前线程休息一会儿", engineId);
                return null;
            }

            for (String deviceId : deviceList) {
                Lock lock = redisLockRegistry.obtain(DeviceConstants.LOCK_MOBILE + deviceId);
                try {
                    if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
                        Map<String, String> mobDevice = applyMobDevice(deviceId,id,request,userBy);
                        if (mobDevice != null) {
                            mapList.add(mobDevice);
                            // 只申请一个设备
                            return mapList;
                        }
                    }
                } catch (InterruptedException e) {
                    logger.error("申请移动设备异常:",e);
                    Thread.currentThread().interrupt();
                } finally {
                    lock.unlock();
                }
            }
        } else {
            for (String deviceId : deviceList) {
                Lock lock = redisLockRegistry.obtain(DeviceConstants.LOCK_PC + deviceId);
                try {
                    if (lock.tryLock(5000,TimeUnit.MILLISECONDS)) {
                        Map<String, String> device = applyPcDevice(deviceId, id,request,userBy);
                        if (device != null) {
                            mapList.add(device);
                        }
                    }
                } catch (InterruptedException e) {
                    logger.error("申请PC设备异常:",e);
                    Thread.currentThread().interrupt();
                } finally {
                    lock.unlock();
                }
            }
        }
        return mapList;
    }

    @Override
    public String queryDeviceToken(String deviceId) {
        String token = new String();
        CdDeviceStatus deviceStatus = this.cdDeviceStatusService.findByPrimaryKey(deviceId);
        if (deviceStatus.getUsageStatus().equals(DeviceConstants.USAGE_STATUS_USE)) {
            //当前设备使用中
            CdDeviceToken deviceToken = this.cdDeviceTokenService.findByPrimaryKey(deviceId);
            token = deviceToken.getToken();
        }else {
            token = null;
        }
        return token;
    }

    @Override
    public CdPcDeviceRemoteDto scriptPcDevice(CdDeviceRemoteDto dto, String projectId, HttpServletRequest request) {
        //判断设备是否被分配到别的项目中
        CdDeviceProject queryDto = this.cdDeviceProjectService.findByPrimaryKey(dto.getDeviceId());
        CdDeviceStatus status = this.cdDeviceStatusService.findByPrimaryKey(dto.getDeviceId());
        if (queryDto != null && !queryDto.getProjectId().equals(projectId)) {
            CdPcDevice pcDevice = this.cdPcDeviceService.findByPrimaryKey(dto.getDeviceId());
            throw new PlatformRuntimeException(DeviceError.DEVICE_IS_PRIVATE_BY_PRO, pcDevice.getDeviceName());
        }
        CdPcDeviceRemoteDto remoteDto = new CdPcDeviceRemoteDto();
        //判断当前设备是否被占用 或者 设备状态是否处于使用中
        CdDeviceToken checkToken = this.cdDeviceTokenService.findByPrimaryKey(dto.getDeviceId());
        //当前设备是否在线
        if (!status.getDeviceStatus().equals(DeviceConstants.DEVICE_STATUS_ONLINE)) {
            throw new PlatformRuntimeException(DeviceError.DEVICE_NOT_ONLINE);
        }
        //引擎信息
        CdEngineInfo cdEngineInfo = new CdEngineInfo();
        cdEngineInfo.setDeviceId(dto.getDeviceId());
        List<CdEngineInfo> engineList = this.cdEngineInfoService.query(cdEngineInfo);
        //PC设备信息
        CdPcDevice pcDevice = this.cdPcDeviceService.findByPrimaryKey(dto.getDeviceId());
        //PC用户信息
        List<CdPcUser> users = this.cdPcUserService.queryUserByPC(dto.getDeviceId());
        //设备使用中 使用人为当前用户 直接返回数据
        remoteDto.setCdEngineInfo(engineList.get(0));
        remoteDto.setCdPcUser(users.size() > 0 ? users.get(0) : new CdPcUser());
        remoteDto.setCdPcDevice(pcDevice);
        if (checkToken != null) {
            throw new PlatformRuntimeException(DeviceError.DEVICE_IS_USE);
        }
        //新增令牌
        Date date = new Date();
        String index = pcDevice.getId() + DeviceConstants.STR_REGEX + engineList.get(0).getId() + DeviceConstants.STR_REGEX + date;
        String token = DigestUtils.md5DigestAsHex(index.getBytes());
        if (StringUtils.isEmpty(pcDevice.getRamSize())) {
            String[] split = pcDevice.getRamSize().split(DeviceConstants.RAM_SIZE_M);
            double num = Integer.parseInt(split[0]);
            double ramSize = num / 1024;
            pcDevice.setRamSize(ramSize + DeviceConstants.RAM_SIZE_G);
        }
        //记录到设备令牌表
        CdDeviceToken cdDeviceToken = new CdDeviceToken();
        cdDeviceToken.setId(dto.getDeviceId());
        cdDeviceToken.setDeviceType(DeviceConstants.DEVICE_PC_TYPE);
        cdDeviceToken.setToken(token);
        cdDeviceToken.setUsageType(DeviceConstants.DEVICE_STATUS_ONLINE);
        cdDeviceToken.setStartTime(date);
        cdDeviceToken.setUserId(dto.getId());
        cdDeviceToken.setUserBy(dto.getUserBy());
        this.cdDeviceTokenService.insert(cdDeviceToken);
        remoteDto.setToken(token);

        //新增日志
        CdDeviceUsageLog log = new CdDeviceUsageLog();
        log.setId(UUIDUtil.create32UUID());
        log.setDeviceId(dto.getDeviceId());
        log.setStartTime(date);
        log.setDeviceType(DeviceConstants.DEVICE_PC_TYPE);
        log.setUsageLog(DeviceConstants.PC_USAGE_LOG + pcDevice.getDeviceName());
        log.setUserBy(dto.getId());
        log.setUserIp(ipUtil.getRemoteIP(request));
        log.setCreatedBy(dto.getId());
        log.setCreatedTime(date);
        log.setUpdatedBy(dto.getId());
        log.setUpdatedTime(date);
        this.cdDeviceUsageLogService.insert(log);
        //修改设备使用状态
        status.setUsageStatus(DeviceConstants.DEVICE_STATUS_OFFLINE);
        this.cdDeviceStatusService.updateByPrimaryKey(status);//1
        return remoteDto;
    }

    @Override
    public DeviceInfoDto scriptMobDevice(CdDeviceRemoteDto dto, String projectId, HttpServletRequest request) {
        //判断设备是否被分配到别的项目中
        CdDeviceProject queryDto = this.cdDeviceProjectService.findByPrimaryKey(dto.getDeviceId());
        if (queryDto!=null && !queryDto.getProjectId().equals(projectId)){
            CdMobileDevice mobileDevice = this.cdMobileDeviceService.findByPrimaryKey(dto.getDeviceId());
            CdDeviceModel deviceModel = this.cdDeviceModelService.findByPrimaryKey(mobileDevice.getModelId());
            throw new PlatformRuntimeException(DeviceError.DEVICE_IS_PRIVATE_BY_PRO,deviceModel.getModelName());
        }
        DeviceInfoDto remoteDto = new DeviceInfoDto();
        String id = dto.getDeviceId();
        CdDeviceStatus status = this.cdDeviceStatusService.findByPrimaryKey(id);
        //当前设备是否在线
        if (!status.getDeviceStatus().equals(DeviceConstants.DEVICE_STATUS_ONLINE)) {
            throw new PlatformRuntimeException(DeviceError.DEVICE_NOT_ONLINE);
        }
        //远程所需的参数
        CdMobileDevice device = this.cdMobileDeviceService.findByPrimaryKey(id);
        CdDeviceCtrl ctrl = this.cdDeviceCtrlService.findByPrimaryKey(device.getCtrlId());
        //设备品牌 机型
        CdDeviceModel deviceModel = this.cdDeviceModelService.findByPrimaryKey(device.getModelId());
        CdMobileBrand brand = this.cdMobileBrandService.findByPrimaryKey(deviceModel.getBrandId());
        //判断当前设备是否被占用

        remoteDto.setCdDeviceCtrl(ctrl);
        remoteDto.setCdMobileDevice(device);
        remoteDto.setCdMobileBrand(brand);
        remoteDto.setCdDeviceModel(deviceModel);

        CdDeviceToken checkToken = this.cdDeviceTokenService.findByPrimaryKey(id);
        if (checkToken != null) {
            throw new PlatformRuntimeException(DeviceError.DEVICE_IS_USE);
        }

        Date date = new Date();
        String index = device.getId() + DeviceConstants.STR_REGEX + ctrl.getId() + DeviceConstants.STR_REGEX + date;
        String token = DigestUtils.md5DigestAsHex(index.getBytes());

        //记录到设备令牌表
        CdDeviceToken cdDeviceToken = new CdDeviceToken();
        cdDeviceToken.setId(id);
        cdDeviceToken.setDeviceType(status.getDeviceType());
        cdDeviceToken.setToken(token);
        cdDeviceToken.setUsageType(DeviceConstants.DEVICE_STATUS_ONLINE);
        cdDeviceToken.setStartTime(date);
        cdDeviceToken.setUserBy(dto.getUserBy());
        cdDeviceToken.setUserId(dto.getId());
        this.cdDeviceTokenService.insert(cdDeviceToken);

        //修改使用状态
        CdDeviceStatus cdDeviceStatus = new CdDeviceStatus();
        cdDeviceStatus.setId(id);
        cdDeviceStatus.setUsageStatus(DeviceConstants.DEVICE_STATUS_OFFLINE);
        this.cdDeviceStatusService.updateByPrimaryKey(cdDeviceStatus);//1

        //新增日志
        CdDeviceModel model = this.cdDeviceModelService.findByPrimaryKey(device.getModelId());
        CdDeviceUsageLog log = new CdDeviceUsageLog();
        log.setId(UUIDUtil.create32UUID());
        log.setDeviceId(id);
        log.setStartTime(date);
        log.setDeviceType(DeviceConstants.DEVICE_MOB_TYPE);
        log.setUsageLog(DeviceConstants.MOB_USAGE_LOG + model.getModelName());
        log.setUserBy(dto.getId());
        log.setUserIp(ipUtil.getRemoteIP(request));
        this.cdDeviceUsageLogService.insert(log);//1
        remoteDto.setToken(token);
        Object obj = redisTemplate.opsForValue().get(DeviceConstants.MOBILE_INFO_PREFIX + id);
        LinkedHashMap<String, Object> link = JSONObject.parseObject(JSON.toJSONString(obj),new TypeReference<LinkedHashMap<String, Object>>(){});
        if (link != null) {
            link.put(DeviceConstants.DEVICE_PLATFORM, device.getPlatform()); //系统类型 (字典匹配)
            link.put(DeviceConstants.DEVICE_PLATFORM_VERSION, device.getPlatformVersion()); //系统版本
            link.put(DeviceConstants.DEVICE_SCREEN_SIZE, deviceModel.getScreenSize());  //屏幕尺寸
            link.put(DeviceConstants.DEVICE_RESOLUTION, deviceModel.getResolution());  //分辨率
            link.put(DeviceConstants.DEVICE_CPU_BRAND, deviceModel.getCpuBrand());   //cpu品牌
            link.put(DeviceConstants.DEVICE_CPU_MODEL, deviceModel.getCpuModel());   //cpu型号
            link.put(DeviceConstants.DEVICE_CPU_CORE_NUM, deviceModel.getCpuCoreNum()); //cpu核心数
            link.put(DeviceConstants.DEVICE_RAM_SIZE, deviceModel.getRamSize()); //内存
            remoteDto.setDeviceInfo(link);
        }
        String stfAddr = (String) redisTemplate.opsForValue().get(DeviceConstants.STF_PREFIX + id);
        if (stfAddr != null) {
            remoteDto.setStfAddr(stfAddr);
        }
        return remoteDto;
    }

    @Override
    public void releaseDeviceList(Map<String, Object> map) {
        //引擎id
        Object id = map.get(DeviceConstants.DEVICE_UUID);
        List<String> deviceList = new ArrayList<>();
        Object obj = map.get(DeviceConstants.DEVICE_LIST);
        if (obj != null) {
            deviceList = JSONObject.parseObject(JSON.toJSONString(obj),new TypeReference<List<String>>(){});
        }
        //查出由当前引擎申请的设备
        List<CdDeviceToken> queryList = this.cdDeviceTokenService.queryDeviceList(id.toString(), deviceList);
        for (CdDeviceToken cdDeviceToken : queryList) {
            CdDeviceStatus status = this.cdDeviceStatusService.findByPrimaryKey(cdDeviceToken.getId());
            if (status.getDeviceType().equals(DeviceConstants.DEVICE_MOB_TYPE)) {
                CdMobileDeviceExitDto mobileDto = new CdMobileDeviceExitDto();
                mobileDto.setDeviceId(cdDeviceToken.getId());
                mobileDto.setToken(cdDeviceToken.getToken());
                releaseMobDevice(mobileDto);
            } else if (status.getDeviceType().equals(DeviceConstants.DEVICE_PC_TYPE)) {
                CdPcDeviceExitDto pcDto = new CdPcDeviceExitDto();
                pcDto.setDeviceId(cdDeviceToken.getId());
                pcDto.setToken(cdDeviceToken.getToken());
                releasePcDevice(pcDto);
            }
        }
    }

    @Override
    public String releaseMobDevice(CdMobileDeviceExitDto dto) {
        Lock lock = redisLockRegistry.obtain(DeviceConstants.LOCK_MOBILE + dto.getDeviceId());
        try {
            if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
                Map<String,String> map = new HashMap<>();
                map.put(DeviceConstants.DEVICE_ID,dto.getDeviceId());
                map.put(DeviceConstants.DEVICE_TOKEN,dto.getToken());
                CdDeviceStatus status = this.cdDeviceStatusService.findByPrimaryKey(dto.getDeviceId());
                logger.info("结束使用移动设备:{}---------------------------------->start",dto.getDeviceId());
                releaseDeviceByToken(map, status);
                logger.info("结束使用移动设备:{}---------------------------------->end",dto.getDeviceId());
            }
        } catch (InterruptedException e) {
            logger.error("释放移动设备异常:",e);
            Thread.currentThread().interrupt();
            throw new PlatformRuntimeException(DeviceError.REMOTE_FAIL);
        } finally {
            lock.unlock();
        }
        return DeviceConstants.RELEASE_SUCCESS;
    }

    @Override
    public Map<String, List<String>> queryDeviceType(List<String> deviceIds) {
        Map<String, List<String>> resultMap = new HashMap<>();
        if (deviceIds.size()>0) {
            List<CdMobileDevice> deviceList = this.cdMobileDeviceService.queryMobileDeviceList(deviceIds);
            List<String> androidList = new ArrayList<>();
            List<String> iosList = new ArrayList<>();
            for (CdMobileDevice mobileDevice : deviceList) {
                if (mobileDevice.getPlatform().equals(DeviceConstants.MOBILE_PLATFORM_ANDROID)) {
                    androidList.add(mobileDevice.getId());
                }else if (mobileDevice.getPlatform().equals(DeviceConstants.MOBILE_PLATFORM_IOS)){
                    iosList.add(mobileDevice.getId());
                }
            }
            resultMap.put(DeviceConstants.MOBILE_PLATFORM_ANDROID,androidList);
            resultMap.put(DeviceConstants.MOBILE_PLATFORM_IOS,iosList);
        }
        return resultMap;
    }

    @Override
    public Boolean deviceRelease(String token) {
        CdDeviceToken tokenQuery = new CdDeviceToken();
        tokenQuery.setToken(token);
        List<CdDeviceToken> resultList = cdDeviceTokenService.query(tokenQuery);
        release(resultList);
        return true;
    }

    @Override
    public void release(List<CdDeviceToken> deviceTokens){
        if (deviceTokens != null && deviceTokens.size() > 0) {
            for (CdDeviceToken deviceToken : deviceTokens) {
                switch (deviceToken.getDeviceType()) {
                    case DeviceConstants.DEVICE_MOB_TYPE:
                        CdMobileDeviceExitDto mobileDto = new CdMobileDeviceExitDto();
                        mobileDto.setDeviceId(deviceToken.getId());
                        mobileDto.setToken(deviceToken.getToken());
                        this.releaseMobDevice(mobileDto);
                        break;
                    case DeviceConstants.DEVICE_PC_TYPE:
                        CdPcDeviceExitDto pcDto = new CdPcDeviceExitDto();
                        pcDto.setDeviceId(deviceToken.getId());
                        pcDto.setToken(deviceToken.getToken());
                        this.releasePcDevice(pcDto);
                        break;
                    default:
                        break;
                }
            }
        }
    }

    @Override
    public String releasePcDevice(CdPcDeviceExitDto dto) {
        Lock lock = redisLockRegistry.obtain(DeviceConstants.LOCK_PC + dto.getDeviceId());
        try {
            if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
                Map<String,String> map = new HashMap<>();
                map.put(DeviceConstants.DEVICE_ID,dto.getDeviceId());
                map.put(DeviceConstants.DEVICE_TOKEN,dto.getToken());
                CdDeviceStatus status = this.cdDeviceStatusService.findByPrimaryKey(dto.getDeviceId());
                logger.info("结束使用PC设备:{}---------------------------------->start",dto.getDeviceId());
                releaseDeviceByToken(map, status);
                logger.info("结束使用PC设备:{}---------------------------------->end",dto.getDeviceId());
            }
        } catch (InterruptedException e) {
            logger.error("释放PC设备异常:",e);
            Thread.currentThread().interrupt();
            throw new PlatformRuntimeException(DeviceError.REMOTE_FAIL);
        } finally {
            lock.unlock();
        }
        return DeviceConstants.RELEASE_SUCCESS;
    }

    public Map<String, String>  applyMobDevice(String deviceId, String userId,HttpServletRequest request,String userBy) {
        //校验设备是否使用中 如果设备使用中 直接返回null
        CdDeviceStatus status = this.cdDeviceStatusService.findByPrimaryKey(deviceId);
        if (!status.getUsageStatus().equals(DeviceConstants.DEVICE_STATUS_ONLINE)) {
            return null;
        }

        //申请设备所需参数
        CdMobileDevice device = this.cdMobileDeviceService.findByPrimaryKey(deviceId);
        CdDeviceCtrl ctrl = this.cdDeviceCtrlService.findByPrimaryKey(device.getCtrlId());

        Date date = new Date();
        String index = deviceId + DeviceConstants.STR_REGEX + ctrl.getId() + DeviceConstants.STR_REGEX + date;
        String token = DigestUtils.md5DigestAsHex(index.getBytes());

        //记录到设备令牌表
        CdDeviceToken cdDeviceToken = new CdDeviceToken();
        cdDeviceToken.setId(deviceId);
        cdDeviceToken.setDeviceType(status.getDeviceType());
        cdDeviceToken.setToken(token);
        cdDeviceToken.setUsageType(DeviceConstants.DEVICE_STATUS_ONLINE);
        cdDeviceToken.setStartTime(date);
        if (userBy.equals(DeviceConstants.ENGINE_NAME)){
            cdDeviceToken.setUserBy(DeviceConstants.USE_BY_ENGINE);
        }else {
            cdDeviceToken.setUserBy(DeviceConstants.USE_BY_USER);
        }
        cdDeviceToken.setUserId(userId);
        this.cdDeviceTokenService.insert(cdDeviceToken);//1


        //修改使用状态
        CdDeviceStatus cdDeviceStatus = new CdDeviceStatus();
        cdDeviceStatus.setId(deviceId);
        cdDeviceStatus.setUsageStatus(DeviceConstants.DEVICE_STATUS_OFFLINE);
        this.cdDeviceStatusService.updateByPrimaryKey(cdDeviceStatus);//1

        //新增日志
        CdDeviceModel model = this.cdDeviceModelService.findByPrimaryKey(device.getModelId());
        CdDeviceUsageLog log = new CdDeviceUsageLog();
        log.setId(UUIDUtil.create32UUID());
        log.setDeviceId(deviceId);
        log.setStartTime(date);
        log.setDeviceType(DeviceConstants.DEVICE_MOB_TYPE);
        log.setUsageLog(DeviceConstants.MOB_USAGE_LOG + model.getModelName());
        log.setUserBy(userId);
        log.setUserIp(ipUtil.getRemoteIP(request));
        this.cdDeviceUsageLogService.insert(log);//1
        Map<String, String> map = new HashMap<>();
        map.put(DeviceConstants.DEVICE_UUID, deviceId);
        map.put(DeviceConstants.DEVICE_TOKEN, token);
        return map;
    }

    public Map<String, String> applyPcDevice(String deviceId, String userId,HttpServletRequest request,String userBy) {
        CdDeviceStatus status = this.cdDeviceStatusService.findByPrimaryKey(deviceId);
        if (!status.getUsageStatus().equals(DeviceConstants.DEVICE_STATUS_ONLINE)) {
            return null;
        }
        //PC设备信息
        CdPcDevice pcDevice = this.cdPcDeviceService.findByPrimaryKey(deviceId);
        //引擎信息
        CdEngineInfo cdEngineInfo = new CdEngineInfo();
        cdEngineInfo.setDeviceId(deviceId);
        List<CdEngineInfo> engineList = this.cdEngineInfoService.query(cdEngineInfo);
        //新增令牌
        Date date = new Date();
        String index = deviceId + DeviceConstants.STR_REGEX + engineList.get(0).getId() + DeviceConstants.STR_REGEX + date;
        String token = DigestUtils.md5DigestAsHex(index.getBytes());
        //记录到设备令牌表
        CdDeviceToken cdDeviceToken = new CdDeviceToken();
        cdDeviceToken.setId(deviceId);
        cdDeviceToken.setDeviceType(DeviceConstants.DEVICE_PC_TYPE);
        cdDeviceToken.setToken(token);
        cdDeviceToken.setUsageType(DeviceConstants.USAGE_STATUS_FREE);
        cdDeviceToken.setStartTime(date);
        if (userBy.equals(DeviceConstants.ENGINE_NAME)){
            cdDeviceToken.setUserBy(DeviceConstants.USE_BY_ENGINE);
        }else {
            cdDeviceToken.setUserBy(DeviceConstants.USE_BY_USER);
        }
        cdDeviceToken.setUserId(userId);
        this.cdDeviceTokenService.insert(cdDeviceToken);//1

        //新增日志
        CdDeviceUsageLog log = new CdDeviceUsageLog();
        log.setId(UUIDUtil.create32UUID());
        log.setDeviceId(deviceId);
        log.setStartTime(date);
        log.setDeviceType(DeviceConstants.DEVICE_PC_TYPE);
        log.setUsageLog(DeviceConstants.PC_USAGE_LOG + pcDevice.getDeviceName());
        log.setUserBy(userId);
        log.setUserIp(ipUtil.getRemoteIP(request));
        log.setCreatedBy(userId);
        log.setCreatedTime(date);
        log.setUpdatedBy(userId);
        log.setUpdatedTime(date);
        this.cdDeviceUsageLogService.insert(log);//1
        //修改设备使用状态
        status.setUsageStatus(DeviceConstants.DEVICE_STATUS_OFFLINE);
        this.cdDeviceStatusService.updateByPrimaryKey(status);//1
        Map<String, String> map = new HashMap<>();
        map.put(DeviceConstants.DEVICE_UUID, deviceId);
        map.put(DeviceConstants.DEVICE_TOKEN, token);
        return map;
    }

    public void releaseDeviceByToken(Map<String,String> map,CdDeviceStatus status){
        String deviceId = map.get(DeviceConstants.DEVICE_ID);
        String token = map.get(DeviceConstants.DEVICE_TOKEN);
        //校验令牌
        CdDeviceToken cdDeviceToken = this.cdDeviceTokenService.findByPrimaryKey(deviceId);
        if (cdDeviceToken != null) {
            if (!token.equals(cdDeviceToken.getToken())) {
                throw new PlatformRuntimeException(DeviceError.DEVICE_EXIT_ERROR);
            }
            CdDeviceUsageLog log = new CdDeviceUsageLog();
            log.setStartTime(cdDeviceToken.getStartTime());
            //归还设备锁 将设备修改成空闲状态
            if (status.getDeviceStatus().equals(DeviceConstants.DEVICE_STATUS_OFFLINE) && status.getDeviceType().equals(DeviceConstants.DEVICE_PC_TYPE)){
                logger.info("PC设备离线不修改使用状态");
            }else if(status.getUsageStatus().equals(DeviceConstants.DEVICE_STATUS_REMOVE) && status.getDeviceStatus().equals(DeviceConstants.DEVICE_STATUS_LINK)){
                logger.info("PC设备不可用");
                status.setDeviceStatus(DeviceConstants.DEVICE_STATUS_LINK_ERR);
                this.cdDeviceStatusService.updateByPrimaryKey(status);
            }else if(status.getUsageStatus().equals(DeviceConstants.USAGE_STATUS_USE)){//使用中状态 -> 空闲
                status.setUsageStatus(DeviceConstants.USAGE_STATUS_FREE);
                this.cdDeviceStatusService.updateByPrimaryKey(status);//1
            }
            logger.info("释放设备:"+status.getId());
            //删除token
            this.cdDeviceTokenService.deleteByPrimaryKey(deviceId);
            //更新日志
            log.setDeviceId(deviceId);
            log.setDeviceType(cdDeviceToken.getDeviceType());
            List<CdDeviceUsageLog> query = this.cdDeviceUsageLogService.query(log);
            if (query != null && query.size() > 0) {
                CdDeviceUsageLog cdDeviceUsageLog = query.get(0);
                Date now = new Date();
                if (cdDeviceUsageLog.getStartTime() != null) {
                    Date oneSecondAfter = new Date(cdDeviceUsageLog.getStartTime().getTime() + 1000);
                    if (now.getTime() > oneSecondAfter.getTime()) {
                        cdDeviceUsageLog.setEndTime(now);
                        this.cdDeviceUsageLogService.updateByPrimaryKey(cdDeviceUsageLog);
                    } else {
                        // 不记录占用时间小于1秒的记录
                        this.cdDeviceUsageLogService.deleteByPrimaryKey(cdDeviceUsageLog.getId());
                    }
                }else{
                    cdDeviceUsageLog.setEndTime(now);
                    this.cdDeviceUsageLogService.updateByPrimaryKey(cdDeviceUsageLog);
                }
            }
        }
    }
}
