/*
* Copyright (c) 京北方信息技术股份有限公司 Corporation 2022 . All rights reserved.
*
*/
package net.northking.cctp.device.api.pcDevice.service;

import net.northking.cctp.common.db.BasicService;
import net.northking.cctp.common.db.Pagination;
import net.northking.cctp.common.db.annotation.DatabaseReadOnly;
import net.northking.cctp.common.db.annotation.DatabaseWriteOnly;
import net.northking.cctp.common.db.excel.AbstractExcelService;
import net.northking.cctp.common.db.excel.ExcelHeader;
import net.northking.cctp.common.exception.PlatformRuntimeException;
import net.northking.cctp.common.http.QueryByPage;
import net.northking.cctp.common.security.authentication.NKSecurityContext;
import net.northking.cctp.common.util.UUIDUtil;

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.pcDevice.*;
import net.northking.cctp.device.util.DeviceStatusUtil;
import net.northking.cctp.device.util.IpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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.ObjectUtils;
import org.springframework.util.StringUtils;

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

/**
 * PC设备管理服务实现类
 *
 * @author maven-cctp-plugin
 * @since 1.0
 */

@Service
@ConditionalOnMissingBean(name = "CdPcDeviceApiServiceImpl")
public class CdPcDeviceApiServiceImpl extends AbstractExcelService<CdPcDevice> implements CdPcDeviceApiService
{
  /**
   * 日志
   */
  private final static Logger logger = LoggerFactory.getLogger(CdPcDeviceApiServiceImpl.class);

  @Autowired
  private CdPcDeviceService cdPcDeviceService;

  @Autowired
  private CdDeviceStatusService cdDeviceStatusService;

  @Autowired
  private CdEngineInfoService cdEngineInfoService;

  @Autowired
  private CdDeviceTokenService cdDeviceTokenService;

  @Autowired
  private CdPcUserService cdPcUserService;

  @Autowired
  private CdDeviceUsageLogService cdDeviceUsageLogService;

  @Autowired
  private RedisLockRegistry redisLockRegistry;

  @Autowired
  private IpUtil ipUtil;

  @Autowired
  private DeviceStatusUtil statusUtil;

  //= Excel 导入导出相关代码 start ==================//
  @Override
  public BasicService<CdPcDevice> getService()
  {
    return this.cdPcDeviceService;
  }

  @Override
  public ExcelHeader[] excelHeaders()
  {
    ExcelHeader id = new ExcelHeader("主键id", "id", String.class);
    ExcelHeader deviceName = new ExcelHeader("设备名称", "deviceName", String.class);
    ExcelHeader domainName = new ExcelHeader("设备域名", "domainName", String.class);
    ExcelHeader ipAddr = new ExcelHeader("ip地址", "ipAddr", String.class);
    ExcelHeader macAddr = new ExcelHeader("mac地址", "macAddr", String.class);
    ExcelHeader osArch = new ExcelHeader("操作系统架构", "osArch", String.class);
    ExcelHeader ramSize = new ExcelHeader("内存容量", "ramSize", String.class);
    ExcelHeader location = new ExcelHeader("执行机位置", "location", String.class);
    ExcelHeader remoteProtocol = new ExcelHeader("远程协议", "remoteProtocol", String.class);
    ExcelHeader registedTime = new ExcelHeader("注册时间", "registedTime", Date.class);
    ExcelHeader createdBy = new ExcelHeader("创建人", "createdBy", String.class);
    ExcelHeader createdTime = new ExcelHeader("创建时间", "createdTime", Date.class);
    ExcelHeader updatedBy = new ExcelHeader("更新人", "updatedBy", String.class);
    ExcelHeader updatedTime = new ExcelHeader("更新时间", "updatedTime", Date.class);

    return new ExcelHeader[]{};
  }

  /**
   * Excel导入，入库之前的处理
   * @param records 从Excel读取到的记录
   * @return 待入库的记录
   */
  @Override
  protected List<CdPcDevice> beforeInsertBatch(List<CdPcDevice> records)
  {
    for (CdPcDevice record : records)
    {
      record.setId(UUIDUtil.create32UUID());
    }
    return records;
  }

  /**
   * 写入Excel之前的处理<br>
   * 数据的特殊处理在此处加工处理
   *
   * @param record 来自数据库记录
   * @return 待写入Excel的记录
   */
  @Override
  protected CdPcDevice beforeWriteExcel(CdPcDevice record)
  {
    return record;
  }

  //= Excel 导入导出相关代码 end =======================//

  //= 增删改查 相关代码 start ==================//
  @Override
  @DatabaseReadOnly
  public Pagination<CdPcDeviceDetailDto> pagingQuery(QueryByPage<CdPcDeviceQueryDto> queryByPage)
  {
    //Platform分页

    CdPcDeviceDto deviceDto = new CdPcDeviceDto();
    BeanUtils.copyProperties(queryByPage.getQuery(),deviceDto);
    deviceDto.setTenantId(NKSecurityContext.getTenantId());

    QueryByPage<CdPcDeviceDto> query = new QueryByPage<>();
    query.setPageNo(queryByPage.getPageNo());
    query.setPageSize(queryByPage.getPageSize());
    query.setQuery(deviceDto);

    return this.cdPcDeviceService.queryPages(query);

  }

  @Override
  @DatabaseWriteOnly
  public Map<String,String> add(CdPcDeviceAddDto dto, HttpServletRequest request)
  {
    //校验ip
    Integer count = cdPcDeviceService.selectPcDevice(dto.getIpAddr());
    if (count > 0 ){
      throw new PlatformRuntimeException(DeviceError.DEVICE_IP_ADDR_EXIST);
    }
    //新增pc设备
    String deviceId = UUIDUtil.create32UUID();
    CdPcDevice entity = new CdPcDevice();
    BeanUtils.copyProperties(dto, entity);
    entity.setId(deviceId);
    this.cdPcDeviceService.insert(entity);

    //新增PC用户
    String uuid = UUIDUtil.create32UUID();
    CdPcUser cdPcUser = new CdPcUser();
    BeanUtils.copyProperties(dto, cdPcUser);
    cdPcUser.setId(uuid);
    cdPcUser.setDeviceId(deviceId);
    cdPcUser.setAccountType("2");
    this.cdPcUserService.insert(cdPcUser);

    //新增PC设备状态
    CdDeviceStatus cdDeviceStatus = new CdDeviceStatus();
    cdDeviceStatus.setId(deviceId);
    cdDeviceStatus.setDeviceType(DeviceConstants.DEVICE_PC_TYPE);
    cdDeviceStatus.setUsageStatus(DeviceConstants.USAGE_STATUS_UN_USE);
    cdDeviceStatus.setDeviceStatus(DeviceConstants.DEVICE_STATUS_LINK);
    this.cdDeviceStatusService.insert(cdDeviceStatus);

    //生成token
    String token = createdToken(entity, request);
    Map<String,String> map = new HashMap<>();
    map.put(DeviceConstants.DEVICE_UUID,deviceId);
    map.put(DeviceConstants.DEVICE_TOKEN,token);
    return map;
  }

  @Override
  @DatabaseWriteOnly
  public Integer updateByPK(CdPcDeviceUpdateDto dto) throws IllegalAccessException {
    //修改设备信息
    CdPcDevice entity = new CdPcDevice();
    BeanUtils.copyProperties(dto, entity);
    if (!org.apache.commons.lang3.StringUtils.isEmpty(dto.getWidth())) {
      entity.setWidth(Integer.parseInt(dto.getWidth()));
    }
    if (!org.apache.commons.lang3.StringUtils.isEmpty(dto.getHeight())) {
      entity.setHeight(Integer.parseInt(dto.getHeight()));
    }

    int update = this.cdPcDeviceService.updateByPrimaryKey(entity);
    //修改设备用户信息
    if (dto.getAccount() != null && dto.getPwd() != null) {
      CdPcUser cdPcUser = new CdPcUser();
      BeanUtils.copyProperties(dto, cdPcUser);
      this.cdPcUserService.updateByPrimaryKey(cdPcUser);
    }

    return update;
  }

  @Override
  @DatabaseReadOnly
  public CdPcDeviceDetailDto findByPK(String uuid)
  {
    CdPcDevice example = new CdPcDevice();
    example.setId(uuid);
    List<CdPcDevice> entitys = this.cdPcDeviceService.query(example);
    if (entitys.isEmpty())
    {
      return null;
    }

    CdPcDeviceDetailDto dto = new CdPcDeviceDetailDto();
    BeanUtils.copyProperties(entitys.get(0), dto);
    if (dto.getRamSize()!=null){
      String size = dto.getRamSize();
      String[] split = size.split(DeviceConstants.RAM_SIZE_M);
      double num = Integer.parseInt(split[0]);
      double i = num/1024;
      String ramSize = String.valueOf(i) + DeviceConstants.RAM_SIZE_G;
      dto.setRamSize(ramSize);
    }
    CdPcUser cdPcUser = new CdPcUser();
    cdPcUser.setDeviceId(uuid);
    List<CdPcUser> query = this.cdPcUserService.query(cdPcUser);
    dto.setUsers(query);
    return dto;
  }

  @Override
  @DatabaseWriteOnly
  public Integer deleteByPK(String uuid)
  {
    CdDeviceStatus status = cdDeviceStatusService.findByPrimaryKey(uuid);
    if (status.getDeviceStatus().equals(DeviceConstants.DEVICE_STATUS_ONLINE) 
            || status.getDeviceStatus().equals(DeviceConstants.DEVICE_STATUS_LINK)) {
      throw new PlatformRuntimeException(DeviceError.DELETE_DEVICE_ERROR);
    }
    CdDeviceStatus cdDeviceStatus = new CdDeviceStatus();
    cdDeviceStatus.setDeviceStatus(DeviceConstants.DEVICE_STATUS_REMOVE);
    statusUtil.updatePcStatus(cdDeviceStatus);
    return 1;
  }

  @Override
  public Pagination<CdPcDeviceDetailDto> pagingQueryByTenant(QueryByPage<CdPcDeviceQueryDto> queryByPage) {
    //SaaS分页
    CdPcDeviceDto deviceDto = new CdPcDeviceDto();
    BeanUtils.copyProperties(queryByPage.getQuery(),deviceDto);

    QueryByPage<CdPcDeviceDto> query = new QueryByPage<>();
    query.setPageNo(queryByPage.getPageNo());
    query.setPageSize(queryByPage.getPageSize());
    query.setQuery(deviceDto);

    return cdPcDeviceService.queryPages(query);
  }

  @Override
  public Pagination<CdPcDeviceDetailDto> pagingNoneTenant(QueryByPage<CdPcDeviceQueryDto> queryByPage) {
    //未关联租户的设备
    CdPcDeviceDto deviceDto = new CdPcDeviceDto();
    BeanUtils.copyProperties(queryByPage.getQuery(),deviceDto);
    deviceDto.setTenantNull(true);

    QueryByPage<CdPcDeviceDto> query = new QueryByPage<>();
    query.setPageNo(queryByPage.getPageNo());
    query.setPageSize(queryByPage.getPageSize());
    query.setQuery(deviceDto);

    return this.cdPcDeviceService.queryPages(query);
  }

  @Override
  public Pagination<CdPcDeviceDetailDto> pagingByTenant(QueryByPage<CdPcDeviceQueryDto> queryByPage) {
    //已关联租户的设备
    CdPcDeviceDto deviceDto = new CdPcDeviceDto();
    BeanUtils.copyProperties(queryByPage.getQuery(),deviceDto);
    deviceDto.setTenantNotNull(true);

    QueryByPage<CdPcDeviceDto> query = new QueryByPage<>();
    query.setPageNo(queryByPage.getPageNo());
    query.setPageSize(queryByPage.getPageSize());
    query.setQuery(deviceDto);

    return this.cdPcDeviceService.queryPages(query);
  }

  @Override
  public Pagination<CdPcDeviceDetailDto> pagingNoneProject(QueryByPage<CdPcDeviceQueryDto> queryByPage) {
    //未关联系统的设备
    CdPcDeviceDto deviceDto = new CdPcDeviceDto();
    BeanUtils.copyProperties(queryByPage.getQuery(),deviceDto);
    deviceDto.setProjectId(null);
    deviceDto.setProNull(true);
    deviceDto.setTenantId(NKSecurityContext.getTenantId());

    QueryByPage<CdPcDeviceDto> query = new QueryByPage<>();
    query.setPageNo(queryByPage.getPageNo());
    query.setPageSize(queryByPage.getPageSize());
    query.setQuery(deviceDto);

    return this.cdPcDeviceService.queryPages(query);
  }

  @Override
  public Pagination<CdPcDeviceDetailDto> pagingByProject(QueryByPage<CdPcDeviceQueryDto> queryByPage) {
    //已分配给系统的设备
    CdPcDeviceDto deviceDto = new CdPcDeviceDto();
    BeanUtils.copyProperties(queryByPage.getQuery(),deviceDto);
    deviceDto.setTenantId(NKSecurityContext.getTenantId());
    deviceDto.setProNotNull(true);

    QueryByPage<CdPcDeviceDto> query = new QueryByPage<>();
    query.setPageNo(queryByPage.getPageNo());
    query.setPageSize(queryByPage.getPageSize());
    query.setQuery(deviceDto);

    return this.cdPcDeviceService.queryPages(query);
  }

  @Override
  @Transactional
  public String registerPC(CdPcDevice cdPcDevice) {
    String uuid = UUIDUtil.create32UUID();
    cdPcDevice.setId(uuid);
    cdPcDevice.setRegistedTime(new Date());
    this.cdPcDeviceService.insert(cdPcDevice);
    CdDeviceStatus cdDeviceStatus = new CdDeviceStatus();
    cdDeviceStatus.setId(uuid);
    cdDeviceStatus.setDeviceType(DeviceConstants.DEVICE_PC_TYPE);
    cdDeviceStatus.setDeviceStatus(DeviceConstants.DEVICE_STATUS_ONLINE);
    cdDeviceStatus.setUsageStatus(DeviceConstants.USAGE_STATUS_FREE);
    this.cdDeviceStatusService.insert(cdDeviceStatus);
    return DeviceConstants.PC_REGISTER_SUCCESS;
  }

  @Override
  public CdEngineInfo findEngineById(String id) {
    CdEngineInfo cdEngineInfo = new CdEngineInfo();
    cdEngineInfo.setDeviceId(id);
    List<CdEngineInfo> query = this.cdEngineInfoService.query(cdEngineInfo);
    if (query.size() == 0){
      return null;
    }
    return query.get(0);
  }

  @Override
  @Transactional
  public CdPcDeviceRemoteDto achieve(CdDeviceRemoteDto dto, HttpServletRequest request) {
    CdPcDeviceRemoteDto remoteDto = new CdPcDeviceRemoteDto();
    Lock lock = redisLockRegistry.obtain(DeviceConstants.PC_DEIVCE_LOCK + dto.getDeviceId());
    try {
      if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
        logger.info("申请PC设备:{}-------------------->start",dto.getDeviceId());
        remoteDto = achievePcDevice(dto, request);
        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 remoteDto;
  }


  @Override
  public Pagination<ScriptPcResultDto> scriptDeviceByPro(QueryByPage<CdPcDeviceScriptDto> queryByPage) {
    Pagination<ScriptPcResultDto> pagination = this.cdPcDeviceService.scriptPcDevice(queryByPage);
    return pagination;
  }

  @Override
  public List<CdPcUser> queryDeviceUser(String deviceId) {
    CdPcUser cdPcUser = new CdPcUser();
    cdPcUser.setDeviceId(deviceId);
    return cdPcUserService.query(cdPcUser);
  }

  @Override
  public Integer updatePcUser(CdPcUserUpdateDto dto) {
    //先删除原有的
    CdPcUser cdPcUser = new CdPcUser();
    cdPcUser.setDeviceId(dto.getDeviceId());
    cdPcUserService.deleteByExample(cdPcUser);
    if (dto.getList().size() > 0) {
      //新增
      for (CdPcUser pcUser : dto.getList()) {
        if (StringUtils.isEmpty(pcUser.getId())) {
          pcUser.setId(UUIDUtil.create32UUID());
        }
        pcUser.setDeviceId(dto.getDeviceId());
        this.cdPcUserService.insert(pcUser);
      }
    }
    return dto.getList().size();
  }

  //申请pc设备
  private CdPcDeviceRemoteDto achievePcDevice(CdDeviceRemoteDto dto, HttpServletRequest request) {
    CdPcDeviceRemoteDto remoteDto = new CdPcDeviceRemoteDto();
    //判断当前设备是否被占用 或者 设备状态是否处于使用中
    CdDeviceToken checkToken = this.cdDeviceTokenService.findByPrimaryKey(dto.getDeviceId());
    CdDeviceStatus status = this.cdDeviceStatusService.findByPrimaryKey(dto.getDeviceId());
    //当前设备是否在线
    if (!status.getDeviceStatus().equals("1")) {
      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());

    if (!ObjectUtils.isEmpty(checkToken) || status.getUsageStatus().equals("2")) {
      throw new PlatformRuntimeException(DeviceError.DEVICE_IS_USE);
    }
    //新增令牌
    Date date = new Date();
    String index;
    if (engineList.size()>0){
      index = pcDevice.getId() + DeviceConstants.STR_REGEX + engineList.get(0).getId() + DeviceConstants.STR_REGEX + date;
      remoteDto.setCdEngineInfo(engineList.get(0));
    }else{
      remoteDto.setCdEngineInfo(null);
      index = pcDevice.getId() + DeviceConstants.STR_REGEX + DeviceConstants.STR_REGEX + date;
    }
    String token = DigestUtils.md5DigestAsHex(index.getBytes());
    //记录到设备令牌表
    CdDeviceToken cdDeviceToken = new CdDeviceToken();
    cdDeviceToken.setId(dto.getDeviceId());
    cdDeviceToken.setDeviceType(DeviceConstants.DEVICE_PC_TYPE);
    cdDeviceToken.setToken(token);
    cdDeviceToken.setUsageType(DeviceConstants.USAGE_STATUS_FREE);
    cdDeviceToken.setStartTime(date);
    cdDeviceToken.setUserBy(dto.getUserBy());
    cdDeviceToken.setUserId(dto.getId());
    this.cdDeviceTokenService.insert(cdDeviceToken);//1
    remoteDto.setCdPcUser(users.size() > 0 ? users.get(0) : new CdPcUser());
    remoteDto.setCdPcDevice(pcDevice);
    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);//1
    //修改设备使用状态
    if (status.getUsageStatus().equals(DeviceConstants.USAGE_STATUS_FREE)){
      status.setUsageStatus(DeviceConstants.USAGE_STATUS_USE);
      this.cdDeviceStatusService.updateByPrimaryKey(status);//1
    }
    return remoteDto;
  }

  public String createdToken(CdPcDevice dto, HttpServletRequest request){
    Date date = new Date();
    String index = dto.getId() + DeviceConstants.STR_REGEX + DeviceConstants.STR_REGEX + date;
    String token = DigestUtils.md5DigestAsHex(index.getBytes());

    //记录到设备令牌表
    CdDeviceToken cdDeviceToken = new CdDeviceToken();
    cdDeviceToken.setId(dto.getId());
    cdDeviceToken.setDeviceType(DeviceConstants.DEVICE_PC_TYPE);
    cdDeviceToken.setToken(token);
    cdDeviceToken.setUsageType(DeviceConstants.USAGE_STATUS_FREE);
    cdDeviceToken.setStartTime(date);
    cdDeviceToken.setUserBy(DeviceConstants.USE_BY_USER);
    cdDeviceToken.setUserId(NKSecurityContext.getUserId());
    this.cdDeviceTokenService.insert(cdDeviceToken);

    //新增日志
    CdDeviceUsageLog log = new CdDeviceUsageLog();
    log.setId(UUIDUtil.create32UUID());
    log.setDeviceId(dto.getId());
    log.setStartTime(date);
    log.setDeviceType(DeviceConstants.DEVICE_PC_TYPE);
    log.setUsageLog(DeviceConstants.PC_USAGE_LOG + dto.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);//1

    return token;
  }

}