package com.ruoyi.platform.service.impl;

import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.constant.Constant;
import com.ruoyi.platform.domain.ComputingResource;
import com.ruoyi.platform.domain.ResourceOccupy;
import com.ruoyi.platform.mapper.ComputingResourceDao;
import com.ruoyi.platform.mapper.ResourceOccupyDao;
import com.ruoyi.platform.service.ResourceOccupyService;
import com.ruoyi.system.api.model.LoginUser;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service("resourceOccupyService")
public class ResourceOccupyServiceImpl implements ResourceOccupyService {
    @Resource
    private ResourceOccupyDao resourceOccupyDao;

    @Resource
    private ComputingResourceDao computingResourceDao;

    @Override
    public Boolean haveResource(Integer computingResourceId, Integer replicas) throws Exception {
        ComputingResource computingResource = computingResourceDao.queryById(computingResourceId);

        if (resourceOccupyDao.getUserCredit(SecurityUtils.getLoginUser().getUserid()) <= 0) {
            throw new Exception("当前积分不足");
        }
        if (Constant.Computing_Resource_GPU.equals(computingResource.getComputingResource())) {
            if (resourceOccupyDao.haveResource(computingResource.getResourceId(), computingResource.getGpuNums() * replicas)) {
                return true;
            } else {
                throw new Exception("资源不足，GPU资源已被占用");
            }
        } else {
            if (resourceOccupyDao.haveResource(computingResource.getResourceId(), computingResource.getCpuCores() * replicas)) {
                return true;
            } else {
                throw new Exception("资源不足，CPU资源已被占用完");
            }
        }
    }

    @Override
    @Transactional
    public void startDeduce(Integer computingResourceId, Integer replicas, String taskType, Long taskId, Long taskInsId, Long workflowId, String taskName, String nodeId, Integer state) {
        ResourceOccupy resourceOccupy = new ResourceOccupy();
        ComputingResource computingResource = computingResourceDao.queryById(computingResourceId);
        resourceOccupy.setComputingResourceId(computingResourceId);
        LoginUser loginUser = SecurityUtils.getLoginUser();
        resourceOccupy.setUserId(loginUser.getUserid());
        resourceOccupy.setCreditPerHour(computingResource.getCreditPerHour() * replicas);
        if (replicas > 1) {
            resourceOccupy.setDescription(replicas + " * [" + computingResource.getDescription() + "]");
        } else {
            resourceOccupy.setDescription(computingResource.getDescription());
        }
        resourceOccupy.setTaskType(taskType);
        resourceOccupy.setTaskId(taskId);
        resourceOccupy.setTaskInsId(taskInsId);
        resourceOccupy.setWorkflowId(workflowId);
        resourceOccupy.setTaskName(taskName);
        resourceOccupy.setNodeId(nodeId);
        resourceOccupy.setState(state);
        resourceOccupyDao.save(resourceOccupy);

        if (Constant.Computing_Resource_GPU.equals(computingResource.getComputingResource())) {
            resourceOccupyDao.updateUsed(computingResource.getResourceId(), computingResource.getGpuNums() * replicas);
        } else {
            resourceOccupyDao.updateUsed(computingResource.getResourceId(), computingResource.getCpuCores() * replicas);
        }
    }

    @Override
    @Transactional
    public void endDeduce(String taskType, Long taskId, Long taskInsId, String nodeId, Date nodeStartTime) {
        List<ResourceOccupy> resourceOccupys = resourceOccupyDao.getResourceOccupyByTask(taskType, taskId, taskInsId, nodeId);
        for (ResourceOccupy resourceOccupy : resourceOccupys) {
            deducing(taskType, taskId, taskInsId, nodeId, nodeStartTime);
            resourceOccupy.setState(Constant.State_invalid);
            resourceOccupyDao.edit(resourceOccupy);

            ComputingResource computingResource = computingResourceDao.queryById(resourceOccupy.getComputingResourceId());
            int occupy_num = (int) (resourceOccupy.getCreditPerHour() / computingResource.getCreditPerHour());
            if (Constant.Computing_Resource_GPU.equals(computingResource.getComputingResource())) {
                resourceOccupyDao.updateUnUsed(computingResource.getResourceId(), computingResource.getGpuNums() * occupy_num);
            } else {
                resourceOccupyDao.updateUnUsed(computingResource.getResourceId(), computingResource.getCpuCores() * occupy_num);
            }
        }
    }

    @Override
    @Transactional
    public void deducing(String taskType, Long taskId, Long taskInsId, String nodeId, Date nodeStartTime) {
        List<ResourceOccupy> resourceOccupys = resourceOccupyDao.getResourceOccupyByTask(taskType, taskId, taskInsId, nodeId);
        for (ResourceOccupy resourceOccupy : resourceOccupys) {
            Date now = new Date();
            long timeDifferenceMillis;
            if (nodeStartTime != null && resourceOccupy.getDeduceLastTime().before(nodeStartTime)) {
                timeDifferenceMillis = now.getTime() - nodeStartTime.getTime();
            } else {
                timeDifferenceMillis = now.getTime() - resourceOccupy.getDeduceLastTime().getTime();
            }
            Double hours = (double) timeDifferenceMillis / (1000 * 60 * 60);
            Double deduceCredit = resourceOccupy.getCreditPerHour() * hours;
            deduceCredit = deduceCredit > 0 ? deduceCredit : 0.01;
            resourceOccupyDao.deduceCredit(deduceCredit, resourceOccupy.getUserId());

            resourceOccupy.setDeduceCredit(new BigDecimal(resourceOccupy.getDeduceCredit() + deduceCredit).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
            resourceOccupy.setDeduceLastTime(now);
            resourceOccupy.setState(Constant.State_valid);
            resourceOccupyDao.edit(resourceOccupy);
        }
    }

    @Override
    public Page<ResourceOccupy> queryByPage(PageRequest pageRequest) {
        long total = resourceOccupyDao.count(SecurityUtils.getLoginUser().getUserid());
        return new PageImpl<>(resourceOccupyDao.queryByPage(SecurityUtils.getLoginUser().getUserid(), pageRequest), pageRequest, total);
    }

    @Override
    public Map<String, Double> queryCredit() {
        Double userCredit = resourceOccupyDao.getUserCredit(SecurityUtils.getLoginUser().getUserid());
        Double deduceCredit = resourceOccupyDao.getDeduceCredit(SecurityUtils.getLoginUser().getUserid());
        deduceCredit = deduceCredit != null ? deduceCredit : 0;
        HashMap<String, Double> result = new HashMap<>();
        result.put("userCredit", new BigDecimal(userCredit).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
        result.put("deduceCredit", new BigDecimal(deduceCredit).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue());
        return result;
    }

    @Override
    @Transactional
    public void update(String taskType, Long taskId, Long taskInsId, Integer computingResourceId, Integer replicas, String taskName) {
        List<ResourceOccupy> resourceOccupyList = resourceOccupyDao.getResourceOccupyByTask(taskType, taskId, taskInsId, null);
        if (resourceOccupyList.isEmpty()) {
            startDeduce(computingResourceId, replicas, taskType, taskId, taskInsId, null, taskName, null, Constant.State_building);
        } else {
            ResourceOccupy resourceOccupy = resourceOccupyList.get(0);
            ComputingResource oldComputingResource = computingResourceDao.queryById(resourceOccupy.getComputingResourceId());
            ComputingResource computingResource = computingResourceDao.queryById(computingResourceId);

            int occupy_num = (int) (resourceOccupy.getCreditPerHour() / oldComputingResource.getCreditPerHour());

            if (Constant.Computing_Resource_GPU.equals(oldComputingResource.getComputingResource())) {
                resourceOccupyDao.updateUnUsed(oldComputingResource.getResourceId(), oldComputingResource.getGpuNums() * occupy_num);
            } else {
                resourceOccupyDao.updateUnUsed(oldComputingResource.getResourceId(), computingResource.getCpuCores() * occupy_num);
            }

            if (Constant.Computing_Resource_GPU.equals(computingResource.getComputingResource())) {
                resourceOccupyDao.updateUsed(computingResource.getResourceId(), computingResource.getGpuNums() * replicas);
            } else {
                resourceOccupyDao.updateUsed(computingResource.getResourceId(), computingResource.getCpuCores() * replicas);
            }

            if (replicas > 1) {
                resourceOccupy.setDescription(replicas + " * [" + computingResource.getDescription() + "]");
            } else {
                resourceOccupy.setDescription(computingResource.getDescription());
            }
            resourceOccupy.setCreditPerHour(computingResource.getCreditPerHour() * replicas);
            resourceOccupy.setComputingResourceId(computingResourceId);
            resourceOccupyDao.edit(resourceOccupy);
        }
    }

    @Override
    public void deleteTaskState(String taskType, Long taskId, Long taskInsId) {
        resourceOccupyDao.deleteTaskState(taskType, taskId, taskInsId);
    }
}
