package cn.iocoder.yudao.module.bpm.service.oa;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.pojo.BpmOALeaveDTO;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.api.oa.vo.BpmOALeaveRpcVO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.leave.BpmOALeaveCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.leave.BpmOALeavePageReqVO;
import cn.iocoder.yudao.framework.common.pojo.UploadUserFile;
import cn.iocoder.yudao.module.bpm.convert.oa.BpmOALeaveConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOALeaveMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmHistoryProcessInstanceService;
import cn.iocoder.yudao.module.system.api.attendance.AttendanceApi;
import cn.iocoder.yudao.module.system.api.attendance.dto.AttendancePunchRecordDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.FAILED_TO_APPLY_FOR_LEAVE;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.OA_LEAVE_NOT_EXISTS;

/**
 * OA 请假申请 Service 实现类
 *
 * @author jason
 */
@Service
@Validated
public class BpmOALeaveServiceImpl extends BpmOABaseService implements BpmOALeaveService {

    /**
     * OA 请假对应的流程定义 KEY
     */
    public static final String PROCESS_KEY = "oa_leave";

    @Resource
    private BpmOALeaveMapper leaveMapper;

    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private AttendanceApi attendanceApi;

    @Resource
    private BpmHistoryProcessInstanceService historyProcessInstanceService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createLeave(Long userId, BpmOALeaveCreateReqVO createReqVO) {
        // 插入 OA 请假单
        long day = LocalDateTimeUtil.between(createReqVO.getStartTime(), createReqVO.getEndTime()).toDays();
        BpmOALeaveDO leave = BpmOALeaveConvert.INSTANCE.convert(createReqVO).setUserId(userId).setDay(day)
                .setFileItems(createReqVO.getFileItems())
                .setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
        leaveMapper.insert(leave);

        // 发起 BPM 流程
        Map<String, Object> processInstanceVariables = new HashMap<>();
        processInstanceVariables.put("day", day);
        String processInstanceId = processInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY)
                        .setVariables(processInstanceVariables).setBusinessKey(String.valueOf(leave.getId()))).getCheckedData();

        // 将工作流的编号，更新到 OA 请假单中
        leaveMapper.updateById(new BpmOALeaveDO().setId(leave.getId()).setProcessInstanceId(processInstanceId));

        // 判断是否为重新发起的流程
        if (createReqVO.getProcessInstanceId() != null && createReqVO.getResult() == 3) {

            historyProcessInstanceService.createHistoryProcessInstance(processInstanceId, createReqVO.getProcessInstanceId());
        }

        List<UploadUserFile> fileItems = createReqVO.getFileItems();
        //这里的逻辑，如果fileItems不为空，且有数据，那么说明是上传了附件的，则需要更工作流文件表对应的实例Id
        if (fileItems != null && !fileItems.isEmpty()) {
            uploadBpmFileProcessInstanceId(processInstanceId, fileItems);
        }
        return leave.getId();
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateLeaveResult(Long id, Integer result) {
        BpmOALeaveDO bpmOALeaveDO = validateLeaveExists(id);
        leaveMapper.updateById(new BpmOALeaveDO().setId(id).setResult(result));
        // -- 如果是通过 - 则判断当前时间是否是请假开始时间前
        // -- 如果是的话 先插入到redis中 - (事前请假)
        // -- 如果不是 则查询相应的考勤 修改考勤状态 （事后请假 - 还需要判断结束时间是否）
        //
        LocalDateTime now = LocalDateTimeUtil.now();
        if (result.equals(BpmProcessInstanceResultEnum.APPROVE.getResult())) {
            // 事后请假修改考勤 = 考勤的预设已经生成过了 - 并且已经在表里面存在了 - 所以要找到表中的数据 - 修改考勤状态
            CommonResult commonResult = attendanceApi.askingForLeaveAfterwardsToModifyAttendance(new AttendancePunchRecordDTO()
                    .setUserId(bpmOALeaveDO.getUserId())
                    .setStartTime(bpmOALeaveDO.getStartTime())
                    .setEndTime(bpmOALeaveDO.getEndTime())
                    .setLeaveId(id)
                    .setLeaveType(bpmOALeaveDO.getType())
            );
            if (!commonResult.isSuccess()) {
                throw exception(FAILED_TO_APPLY_FOR_LEAVE);
            }
            if (now.isBefore(bpmOALeaveDO.getEndTime())) {
                // 事前请假 = 考勤预设可能还没有生成 - 因为可能请假好几天 - 所以这里处理就比较麻烦点 - 先看下考勤表里面有没有在这个区间的考勤 - 如果有的话先修改考勤状态 -
                // 然后将数据先存入redis - 在设置考勤预设的时候 就去redis 中查询是否有请假 - 有的话预设的时候就预设进去
                BpmOALeaveDTO dto = new BpmOALeaveDTO();
                BeanUtil.copyProperties(bpmOALeaveDO, dto);
                String key = "leave" + "_" + bpmOALeaveDO.getUserId().toString();
                stringRedisTemplate.opsForHash().put(key, id.toString(), JSONUtil.toJsonStr(dto));
                // -- 将请假put到redis的map中 -
            }
        }
    }

    private BpmOALeaveDO validateLeaveExists(Long id) {
        BpmOALeaveDO bpmOALeaveDO = leaveMapper.selectById(id);
        if (bpmOALeaveDO == null) {
            throw exception(OA_LEAVE_NOT_EXISTS);
        }
        return bpmOALeaveDO;
    }

    @Override
    public BpmOALeaveDO getLeave(Long id) {
        return leaveMapper.selectById(id);
    }

    @Override
    public PageResult<BpmOALeaveDO> getLeavePage(Long userId, BpmOALeavePageReqVO pageReqVO) {
        return leaveMapper.selectPage(userId, pageReqVO);
    }

    @Override
    public List<BpmOALeaveRpcVO> getLeaveListByTime(String time) {
        return leaveMapper.getLeaveListByTime(time);
    }

    @Override
    public BpmOALeaveDO getByProcessInstanceId(String processInstanceId) {

        List<BpmOALeaveDO> list = leaveMapper.selectList(BpmOALeaveDO::getProcessInstanceId, processInstanceId);
        if (CollectionUtil.isEmpty(list)) {
            return null;
        }
        return list.get(0);
    }
}
