package com.srs.routine.service.impl;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.srs.common.core.domain.AjaxResult;
import com.srs.common.core.domain.entity.SysUser;
import com.srs.common.doman.dto.ProcessResultDto;
import com.srs.common.doman.vo.TeacherVo;
import com.srs.common.exception.ServiceException;
import com.srs.common.utils.DateUtils;
import com.srs.common.utils.SecurityUtils;
import com.srs.dormitory.domain.DmsOutsideAccommodationApply;
import com.srs.dormitory.mapper.DmsOutsideAccommodationApplyMapper;
import com.srs.flowable.service.IFlowDefinitionService;
import com.srs.routine.domain.RtEnlistmentReserveApproval;
import com.srs.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
import com.srs.routine.mapper.RtStuLeaveApplicationMapper;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.IdentityService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import com.srs.routine.mapper.RtEnlistmentReserveMapper;
import com.srs.routine.domain.RtEnlistmentReserve;
import com.srs.routine.service.IRtEnlistmentReserveService;

/**
 * 应征入伍保留学籍申请Service业务层处理
 *
 * @author srs
 * @date 2025-10-31
 */
@Service
public class RtEnlistmentReserveServiceImpl extends ServiceImpl<RtEnlistmentReserveMapper,RtEnlistmentReserve> implements IRtEnlistmentReserveService {
    @Autowired
    private RtEnlistmentReserveMapper rtEnlistmentReserveMapper;

    @Autowired
    private DmsOutsideAccommodationApplyMapper dmsOutsideAccommodationApplyMapper; // 外宿申请

    @Autowired
    IFlowDefinitionService flowDefinitionService;

    @Autowired
    TaskService taskService;

    @Autowired
    IdentityService identityService;

    @Autowired
    ISysUserService sysUserService;


    // 注入RuntimeService
    @Autowired
    private RuntimeService runtimeService;

    // 辅导员审批节点的名称（直接使用中文名称匹配）
    private static final String COUNSELOR_TASK_NAME = "辅导员审批";


    /**
     * 查询应征入伍保留学籍申请
     *
     * @param id 应征入伍保留学籍申请主键
     * @return 应征入伍保留学籍申请
     */
    @Override
    public RtEnlistmentReserve selectRtEnlistmentReserveById(Long id) {
        return rtEnlistmentReserveMapper.selectRtEnlistmentReserveById(id);
    }

    @Override
    public RtEnlistmentReserve selectRtEnlistmentReserveByProcessInstanceId(String processInstanceId) {
        return rtEnlistmentReserveMapper.selectRtEnlistmentReserveByProcessInstanceId(processInstanceId);
    }

    /**
     * 查询应征入伍保留学籍申请列表
     *
     * @param rtEnlistmentReserve 应征入伍保留学籍申请
     * @return 应征入伍保留学籍申请
     */
    @Override
    public List<RtEnlistmentReserve> selectRtEnlistmentReserveList(RtEnlistmentReserve rtEnlistmentReserve) {
        return rtEnlistmentReserveMapper.selectRtEnlistmentReserveList(rtEnlistmentReserve);
    }

    /**
     * 新增应征入伍保留学籍申请
     *
     * @param rtEnlistmentReserve 应征入伍保留学籍申请
     * @return 结果
     */
    @Override
    public RtEnlistmentReserve insertRtEnlistmentReserve(RtEnlistmentReserve rtEnlistmentReserve) {
        // 新增之前先查询申请记录是否存在，如果存在就不能新增重复的数据
        RtEnlistmentReserve enlistmentReserveByStuNameAndStuNo = rtEnlistmentReserveMapper.getEnlistmentReserveByStuNameAndStuNo(rtEnlistmentReserve.getStudentName(), rtEnlistmentReserve.getStudentNo());
        if (enlistmentReserveByStuNameAndStuNo != null) {
            throw new ServiceException("您已经申请过了，请勿重新申请！", 400);
        }

        SysUser sysUser = sysUserService.selectUserByUserName(rtEnlistmentReserve.getStudentNo());
        if (sysUser == null) {
            throw new ServiceException("该学生不存在", 500);
        }

        // ========== 生成保留学籍编号 ==========
        // 1. 获取当前系统年份（用于编号和序号重置判断）
        SimpleDateFormat yearSdf = new SimpleDateFormat("yyyy");
        String currentYear = yearSdf.format(new Date()); // 如2026

        // 2. 查询「当前年份」的记录总数（按年份分组统计，实现每年序号重置）
        // 统计rt_enlistment_reserve表中，reserve_no以"BLXJ("+currentYear+")"开头的记录数
        int yearTotal = rtEnlistmentReserveMapper.countByYear(currentYear);

        // 3. 计算当年自增序号（总数+1），补零为4位（0001、0002...9999）
        int seq = yearTotal + 1;
        String seqStr = String.format("%04d", seq); // 不足4位补0，如1→0001，10→0010，100→0100

        // 4. 拼接最终编号：BLXJ(年份)序号
        String reserveNo = "BLXJ(" + currentYear + ")" + seqStr;

        // 5. 设置到实体对象中
        rtEnlistmentReserve.setReserveNo(reserveNo);

        rtEnlistmentReserve.setCreateTime(DateUtils.getNowDate());

        // 先插入申请表数据，获取自增 ID（数据库自动为 applyStatus 赋值 0 若未传值）
        int insertCount = rtEnlistmentReserveMapper.insertRtEnlistmentReserve(rtEnlistmentReserve);
        if (insertCount <= 0) {
            throw new ServiceException("申请表插入失败", 500);
        }

        Long applyId = rtEnlistmentReserve.getId();
        if (applyId == null) {
            throw new ServiceException("申请表 ID 生成失败，无法继续操作", 500);
        }

        // 仅当 applyStatus = 1 时，启动流程（1=提交申请，进入审批）
        // 注意：若前端未传 applyStatus，数据库默认 0，不会触发流程
        if (1 == rtEnlistmentReserve.getApplyStatus()) {
            // 绑定当前学生 ID（提交人即学生本人，确保流程变量准确）
            if (rtEnlistmentReserve.getStudentId() == null) {
                rtEnlistmentReserve.setStudentId(SecurityUtils.getUserId());
            }

            // 启动流程（此时 applyId 已存在，可正常传递）
            ProcessResultDto processResultDto = startEnlistmentReserveProcess(rtEnlistmentReserve);

            // 同步更新流程实例 ID + 申请状态为“审批中”（2），形成业务闭环
            if (processResultDto != null && StringUtils.isNotBlank(processResultDto.getProcessInstanceId())) {
                RtEnlistmentReserve updateEntity = new RtEnlistmentReserve();
                updateEntity.setId(applyId);
                updateEntity.setProcessInstanceId(processResultDto.getProcessInstanceId());
                updateEntity.setDeployId(processResultDto.getDeploymentId());
                updateEntity.setApplyStatus(1L); // 审批中状态（根据实际枚举调整）
                rtEnlistmentReserveMapper.updateRtEnlistmentReserve(updateEntity);
                System.out.println("申请表[" + applyId + "]已启动流程，实例ID：" + processResultDto.getProcessInstanceId() + "，状态更新为审批中");
            } else {
                throw new ServiceException("流程启动失败，请重试", 500);
            }
        } else {
            // 若 applyStatus 为 0（数据库默认）或其他值，仅保存表单，不启动流程
            String statusDesc = rtEnlistmentReserve.getApplyStatus() == 0 ? "草稿" : "非提交状态";
            System.out.println("申请表[" + applyId + "]状态为" + rtEnlistmentReserve.getApplyStatus() + "（" + statusDesc + "），暂不启动流程");
        }

        return rtEnlistmentReserve;
    }

    @Override
    public int batchInsertRtEnlistmentReserve(List<RtEnlistmentReserve> list) {
        // 1. 校验入参是否为空
        if (CollectionUtils.isEmpty(list)) {
            return 0;
        }

        // 2. 过滤掉姓名或学号为空的数据（避免拼接异常）
        List<RtEnlistmentReserve> validList = list.stream()
                .filter(item -> item.getStudentName() != null && !item.getStudentName().isEmpty()
                        && item.getStudentNo() != null && !item.getStudentNo().isEmpty())
                .collect(Collectors.toList());

        if (CollectionUtils.isEmpty(validList)) {
            throw new IllegalArgumentException("待插入数据中无有效学生信息（姓名/学号为空）");
        }

        // 3. 查询数据库中已存在的学生（姓名_学号）
        List<String> existStudentKeys = rtEnlistmentReserveMapper.selectExistStudents(validList);
        Set<String> existStudentKeySet = new HashSet<>(existStudentKeys);

        // 4. 拆分重复数据和待插入数据
        List<RtEnlistmentReserve> duplicateList = validList.stream()
                .filter(item -> existStudentKeySet.contains(item.getStudentName() + "_" + item.getStudentNo()))
                .collect(Collectors.toList());

        List<RtEnlistmentReserve> insertList = validList.stream()
                .filter(item -> !existStudentKeySet.contains(item.getStudentName() + "_" + item.getStudentNo()))
                .collect(Collectors.toList());

        // 5. 存在重复数据则抛出异常提示
        if (!CollectionUtils.isEmpty(duplicateList)) {
            String duplicateMsg = duplicateList.stream()
                    .map(item -> "学生姓名：" + item.getStudentName() + "，学号：" + item.getStudentNo())
                    .collect(Collectors.joining("；"));
            throw new RuntimeException("提交申请失败，以下学生已提交申请：" + duplicateMsg);
        }

        // 6. 无重复数据时，为每条待插入数据生成唯一的reserveNo（核心新增逻辑）
        if (!CollectionUtils.isEmpty(insertList)) {
            // ========== 生成保留学籍编号（适配批量场景） ==========
            // 6.1 获取当前系统年份（用于编号和序号重置判断）
            SimpleDateFormat yearSdf = new SimpleDateFormat("yyyy");
            String currentYear = yearSdf.format(new Date()); // 如2026

            // 6.2 查询「当前年份」的记录总数（按年份分组统计，实现每年序号重置）
            // 统计rt_enlistment_reserve表中，reserve_no以"BLXJ("+currentYear+")"开头的记录数
            int yearTotal = rtEnlistmentReserveMapper.countByYear(currentYear);

            // ========== 保留学籍开始和结束日期生成核心逻辑 ==========
            Calendar calendar = Calendar.getInstance();
            int currentMonth = calendar.get(Calendar.MONTH) + 1; // 月份从0开始，+1转为实际月份（1-12）

            // 6.3 为批量数据逐个生成递增的reserveNo
            for (int i = 0; i < insertList.size(); i++) {
                RtEnlistmentReserve item = insertList.get(i);
                // 计算当前条目的序号（年份总数 + 1 + 循环索引，保证批量递增）
                int seq = yearTotal + 1 + i;
                // 补零为4位（0001、0002...9999）
                String seqStr = String.format("%04d", seq);
                // 拼接最终编号：BLXJ(年份)序号
                String reserveNo = "BLXJ(" + currentYear + ")" + seqStr;
                // 设置到当前实体对象中
                item.setReserveNo(reserveNo);

                // ========== 保留学籍开始和结束日期生成核心逻辑 ==========
                // --- 生成reserveStartDate（按学期判断） ---
                // 重置Calendar为当前年份的起始
                calendar.clear();
                calendar.set(Calendar.YEAR, Integer.parseInt(currentYear));
                // 判断学期：春季（1-6月）→3月1日，秋季（7-12月）→9月1日
                if (currentMonth >= 1 && currentMonth <= 6) {
                    calendar.set(Calendar.MONTH, Calendar.MARCH); // 3月（Calendar中3月是2，这里直接用常量更清晰）
                } else {
                    calendar.set(Calendar.MONTH, Calendar.SEPTEMBER); // 9月
                }
                calendar.set(Calendar.DAY_OF_MONTH, 1); // 1日
                Date reserveStartDate = calendar.getTime();
                item.setReserveStartDate(reserveStartDate);

                // --- 生成reserveEndDate（startDate加2年） ---
                Calendar endCalendar = (Calendar) calendar.clone(); // 克隆避免影响原日期
                endCalendar.add(Calendar.YEAR, 2); // 加2年
                Date reserveEndDate = endCalendar.getTime();
                item.setReserveEndDate(reserveEndDate);
            }
        }

        // 7. 执行批量插入
        if (CollectionUtils.isEmpty(insertList)) {
            return 0;
        }
        return rtEnlistmentReserveMapper.batchInsertRtEnlistmentReserve(insertList);
    }

    /**
     * 修改应征入伍保留学籍申请
     *
     * @param rtEnlistmentReserve 应征入伍保留学籍申请
     * @return 结果
     */
    @Override
    public int updateRtEnlistmentReserve(RtEnlistmentReserve rtEnlistmentReserve) {
        if (rtEnlistmentReserve.getId() == null) {
            throw new ServiceException("申请表ID不能为空，无法更新", 500);
        }
        rtEnlistmentReserve.setUpdateTime(DateUtils.getNowDate());
        rtEnlistmentReserve.setUpdateBy(SecurityUtils.getUsername());

        // 查询当前申请表的已有状态（判断流程是否已启动）
        RtEnlistmentReserve existing = rtEnlistmentReserveMapper.selectRtEnlistmentReserveById(rtEnlistmentReserve.getId());
        if (existing == null) {
            throw new ServiceException("未查询到申请表数据，无法更新", 500);
        }

        // 判断流程是否已启动（存在 processInstanceId 即为已启动）
        boolean isProcessStarted = StringUtils.isNotBlank(existing.getProcessInstanceId());

        if (isProcessStarted) {
            // 流程已启动：仅更新数据库，不操作流程（避免重复启动）
            System.out.println("申请表[" + rtEnlistmentReserve.getId() + "]流程已启动（实例ID：" + existing.getProcessInstanceId() + "），仅更新数据");
            // ========== 核心新增逻辑：学生自动完成自己的待办任务 ==========
            String processInstanceId = existing.getProcessInstanceId();
            Long studentId = SecurityUtils.getUserId(); // 获取当前操作的学生ID
            String studentUserId = studentId.toString(); // 转换为Flowable的Assignee字符串类型

            // 1. 查询学生当前的待办任务（流程实例ID+学生ID作为负责人）
            List<Task> studentTaskList = taskService.createTaskQuery()
                    .processInstanceId(processInstanceId)
                    .taskAssignee(studentUserId) // 学生作为任务负责人
                    .active() // 仅查询活跃任务
                    .taskName("学生申请")
                    .list();

            if (!studentTaskList.isEmpty()) {
                for (Task studentTask : studentTaskList) {
                    // 补充完成任务的流程变量（如审批意见、结果，根据你的流程需求设置）
                    Map<String, Object> completeVariables = new HashMap<>();
                    completeVariables.put("approvalResult", "AGREE"); // 学生提交（默认同意）
                    completeVariables.put("approvalOpinion", "学生自动提交申请，等待辅导员审批");
                    completeVariables.put("operator", "student"); // 操作人类型
                    // 添加任务分配的备注（便于流程追溯）
                    taskService.addComment(
                            studentTask.getId(),
                            processInstanceId,
                            "自动分配负责人",
                            "学生已经重新修改提交申请"
                    );
                    // 2. 学生自动完成待办任务
                    taskService.complete(studentTask.getId(), completeVariables);
                    System.out.println("学生[" + studentUserId + "]已自动完成待办任务，任务ID：" + studentTask.getId());
                }

                // 3. 分配下一节点（辅导员）的待办任务负责人【不再复用监听器，独立实现】
                assignCounselorTask(processInstanceId, existing.getId(), existing.getStudentNo());
            } else {
                System.out.println("流程实例[" + processInstanceId + "]中未查询到学生[" + studentUserId + "]的待办任务，无需自动完成");
            }

            // 同步更新流程变量（将业务表状态同步到流程中）
            runtimeService.setVariable(processInstanceId, "applyStatus", rtEnlistmentReserve.getApplyStatus());
            // 注意：若需更新流程相关变量，需调用 Flowable 的 runtimeService.setVariables()，此处仅更新业务表
            return rtEnlistmentReserveMapper.updateRtEnlistmentReserve(rtEnlistmentReserve);
        } else {
            // 流程未启动：仅当 applyStatus=1 时启动流程，否则仅更新数据
            Long applyStatus = rtEnlistmentReserve.getApplyStatus();
            if (1L == applyStatus) {
                // 绑定当前学生ID，启动流程
                // rtEnlistmentReserve.setStudentId(SecurityUtils.getUserId());
                ProcessResultDto processResultDto = startEnlistmentReserveProcess(rtEnlistmentReserve);
                if (processResultDto == null || StringUtils.isBlank(processResultDto.getProcessInstanceId())) {
                    throw new ServiceException("流程启动失败，请重试", 500);
                }

                // 关联流程实例ID，更新为“审批中”状态
                rtEnlistmentReserve.setProcessInstanceId(processResultDto.getProcessInstanceId());
                rtEnlistmentReserve.setDeployId(processResultDto.getDeploymentId());
                rtEnlistmentReserve.setApplyStatus(1L); // 审批中
                System.out.println("申请表[" + rtEnlistmentReserve.getId() + "]启动流程成功，实例ID：" + processResultDto.getProcessInstanceId());
            } else {
                // 非提交状态：仅更新基础数据（如草稿修改）
                String statusDesc = applyStatus == null ? "未指定状态" : (applyStatus == 0 ? "草稿" : "其他状态");
                System.out.println("申请表[" + rtEnlistmentReserve.getId() + "]状态为" + statusDesc + "，流程未启动，仅更新数据");
            }

            // 执行数据库更新（含流程实例ID绑定或基础数据更新）
            return rtEnlistmentReserveMapper.updateRtEnlistmentReserve(rtEnlistmentReserve);
        }
    }

    /**
     * 核心辅助方法：为辅导员审批节点分配负责人【独立实现，不复用监听器】
     * 支持三种常见的辅导员ID获取方式，根据你的业务场景选择其一即可
     * @param processInstanceId 流程实例ID
     * @param enlistmentId 入伍申请表ID（用于从业务表查询辅导员）
     */
    private void assignCounselorTask(String processInstanceId, Long enlistmentId, String studentNo) {
        // 1. 查询辅导员审批节点的待办任务
        List<Task> counselorTaskList = taskService.createTaskQuery()
                .processInstanceId(processInstanceId)
                .taskName(COUNSELOR_TASK_NAME) // 直接使用节点名称匹配
                .active() // 仅查询活跃任务
                .list();

        if (counselorTaskList.isEmpty()) {
            System.out.println("流程实例[" + processInstanceId + "]中未生成辅导员审批节点的待办任务，无需分配负责人");
            return;
        }

        // 2. 获取辅导员ID
        // 查询辅导员信息
        TeacherVo counselorInfo = rtEnlistmentReserveMapper.getCounselorInfo(studentNo);
        if (counselorInfo == null) {
            throw new ServiceException("申请表[" + enlistmentId + "]根据学生学号[" + studentNo + "]未查询到辅导员信息，分配失败", 500);
        }
        // 4. Long类型的用户ID转换为String（避免空指针+类型转换）
        Long counselorUserId = counselorInfo.getUserId();
        if (counselorUserId == null) {
            throw new ServiceException("辅导员的用户ID为空，无法分配审批任务", 500);
        }
        String counselorIdStr = counselorUserId.toString(); // Long转String

        if (StringUtils.isBlank(counselorIdStr)) {
            throw new ServiceException("申请表[" + enlistmentId + "]未查询到辅导员信息，分配失败", 500);
        }

        // 3. 为辅导员节点的待办任务设置负责人
        for (Task counselorTask : counselorTaskList) {
            taskService.setAssignee(counselorTask.getId(), counselorIdStr);
            // 可选：添加任务分配的备注（便于流程追溯）
            /*taskService.addComment(
                    counselorTask.getId(),
                    processInstanceId,
                    "自动分配负责人",
                    "学生已经重新修改提交申请"
            );*/
            System.out.println("辅导员审批节点任务[" + counselorTask.getId() + "]已分配负责人：" + counselorIdStr);
        }
    }


    /**
     * 开启应征入伍保留学籍申请工作流
     *
     * @param rtEnlistmentReserve
     */
    private ProcessResultDto startEnlistmentReserveProcess(RtEnlistmentReserve rtEnlistmentReserve) {
        Map<String, Object> variables = new HashMap<>();
        // 1. 传递关键变量（申请编号+主键ID，用于任务匹配）
        String applyNo = rtEnlistmentReserve.getApplyNo(); // 申请编号（如RY2024001）
        Long enlistmentId = rtEnlistmentReserve.getId();   // 申请表主键ID
        variables.put("applyNo", applyNo);
        variables.put("enlistmentId", enlistmentId); // 新增：用于任务匹配的主键变量
        variables.put("stuId", rtEnlistmentReserve.getStudentId());

        // 2. 查询辅导员信息
        TeacherVo counselorInfo = rtEnlistmentReserveMapper.getCounselorInfo(rtEnlistmentReserve.getStudentNo());
        if (counselorInfo == null) {
            throw new ServiceException("该学生暂无辅导员", 500);
        }
        variables.put("approval", counselorInfo.getUserId()); // 下一节点：辅导员
        variables.put("deptId", counselorInfo.getDeptId());

        // 3. 启动流程
        AjaxResult ajaxResult = flowDefinitionService.startProcessInstanceById("flow_r064jfpz:11:1282550", variables);
        String code = ajaxResult.get("code").toString();
        if (!"200".equals(code)) {
            throw new ServiceException("流程启动失败，错误码：" + code, 500);
        }

        ProcessResultDto dto = (ProcessResultDto) ajaxResult.get("data");
        System.out.println("流程启动成功，实例ID：" + dto.getProcessInstanceId());

        // 4. 学生自动完成自己的待办任务
        identityService.setAuthenticatedUserId(SecurityUtils.getUserId().toString());
        // 精准查询当前流程中属于学生的待办任务
        List<Task> tasks = taskService.createTaskQuery()
                .processInstanceId(dto.getProcessInstanceId())
                .taskAssignee(SecurityUtils.getUserId().toString())
                .active()
                .list();

        if (tasks.isEmpty()) {
            throw new ServiceException("未查询到学生的待办任务，请检查流程设计", 500);
        }

        // 5. 完成任务（匹配主键ID，避免错误）
        variables.put("approved", true); // 学生提交确认
        for (Task task : tasks) {
            // 安全获取变量：先判断是否为null，再转换类型
            Object varValue = taskService.getVariable(task.getId(), "enlistmentId");
            if (varValue == null) {
                continue;
            }
            // 转换为Long类型（与主键ID匹配）
            Long taskEnlistmentId;
            try {
                taskEnlistmentId = Long.parseLong(varValue.toString());
            } catch (NumberFormatException e) {
                continue; // 类型不匹配，跳过
            }
            // 匹配当前申请表的主键ID
            if (taskEnlistmentId.equals(enlistmentId)) {
                taskService.complete(task.getId(), variables);
                System.out.println("学生已完成待办任务，任务ID：" + task.getId());
                break; // 完成后退出循环，避免重复处理
            }
        }

        // 6. 推送消息给辅导员（待办提醒）
        // todo: 企业微信推送逻辑

        return dto;
    }

    /**
     * 批量删除应征入伍保留学籍申请
     *
     * @param ids 需要删除的应征入伍保留学籍申请主键
     * @return 结果
     */
    @Override
    public int deleteRtEnlistmentReserveByIds(Long[] ids) {
        return rtEnlistmentReserveMapper.deleteRtEnlistmentReserveByIds(ids);
    }

    /**
     * 删除应征入伍保留学籍申请信息
     *
     * @param id 应征入伍保留学籍申请主键
     * @return 结果
     */
    @Override
    public int deleteRtEnlistmentReserveById(Long id) {
        return rtEnlistmentReserveMapper.deleteRtEnlistmentReserveById(id);
    }

    /**
     * 新增外宿申请
     *
     * @param dmsOutsideAccommodationApply 外宿申请
     * @return 结果
     */
    @Override
    public DmsOutsideAccommodationApply insertDmsOutsideAccommodationApply(DmsOutsideAccommodationApply dmsOutsideAccommodationApply) {
        // 新增之前先查询申请记录是否存在，如果存在就不能新增重复的数据
        DmsOutsideAccommodationApply outsideAccommodationApply = dmsOutsideAccommodationApplyMapper.getAccommodationByStuNameAndStuNo(dmsOutsideAccommodationApply.getStudentName(), dmsOutsideAccommodationApply.getStudentNo());
        if (outsideAccommodationApply != null) {
            throw new ServiceException("您已经申请过了，请勿重新申请！", 400);
        }
        dmsOutsideAccommodationApply.setCreateTime(DateUtils.getNowDate());
        dmsOutsideAccommodationApply.setCreateBy(SecurityUtils.getUsername());
        dmsOutsideAccommodationApply.setStartDate(DateUtils.getNowDate());

        // 先插入申请表数据，获取自增 ID（数据库自动为 applyStatus 赋值 0 若未传值）
        int insertCount = dmsOutsideAccommodationApplyMapper.insertDmsOutsideAccommodationApply(dmsOutsideAccommodationApply);
        if (insertCount <= 0) {
            throw new ServiceException("申请表插入失败", 500);
        }

        Long applyId = dmsOutsideAccommodationApply.getId();
        if (applyId == null) {
            throw new ServiceException("申请表 ID 生成失败，无法继续操作", 500);
        }
        // 仅当 applyStatus = 1 时，启动流程（1=提交申请，进入审批）
        // 注意：若前端未传 applyStatus，数据库默认 0，不会触发流程
        if (1 == dmsOutsideAccommodationApply.getStatus()) {
            // 绑定当前学生 ID（提交人即学生本人，确保流程变量准确）
            dmsOutsideAccommodationApply.setStudentId(SecurityUtils.getUserId());

            // 启动流程（此时 applyId 已存在，可正常传递）
            ProcessResultDto processResultDto = startOutsideAccommodationProcess(dmsOutsideAccommodationApply);

            // 同步更新流程实例 ID + 申请状态为“审批中”（2），形成业务闭环
            if (processResultDto != null && StringUtils.isNotBlank(processResultDto.getProcessInstanceId())) {
                DmsOutsideAccommodationApply updateEntity = new DmsOutsideAccommodationApply();
                updateEntity.setId(applyId);
                updateEntity.setProcessInstanceId(processResultDto.getProcessInstanceId());
                updateEntity.setDeployId(processResultDto.getDeploymentId());
                /*updateEntity.setApplyStatus(1L); // 审批中状态（根据实际枚举调整）*/
                dmsOutsideAccommodationApplyMapper.updateDmsOutsideAccommodationApply(updateEntity);
                System.out.println("申请表[" + applyId + "]已启动流程，实例ID：" + processResultDto.getProcessInstanceId() + "，状态更新为审批中");
            } else {
                throw new ServiceException("流程启动失败，请重试", 500);
            }
        } else {
            // 若 applyStatus 为 0（数据库默认）或其他值，仅保存表单，不启动流程
            String statusDesc = dmsOutsideAccommodationApply.getStatus() == 0 ? "草稿" : "待辅导员审批";
            System.out.println("申请表[" + applyId + "]状态为" + dmsOutsideAccommodationApply.getStatus() + "（" + statusDesc + "），暂不启动流程");
        }

        return dmsOutsideAccommodationApply;
    }

    /**
     * 修改外宿申请
     *
     * @param dmsOutsideAccommodationApply 外宿申请
     * @return 结果
     */
    @Override
    public int updateDmsOutsideAccommodationApply(DmsOutsideAccommodationApply dmsOutsideAccommodationApply) {
        if (dmsOutsideAccommodationApply.getId() == null) {
            throw new ServiceException("申请表ID不能为空，无法更新", 500);
        }
        dmsOutsideAccommodationApply.setUpdateTime(DateUtils.getNowDate());
        dmsOutsideAccommodationApply.setUpdateBy(SecurityUtils.getUsername());

        // 查询当前申请表的已有状态（判断流程是否已启动）
        DmsOutsideAccommodationApply existing = dmsOutsideAccommodationApplyMapper.selectDmsOutsideAccommodationApplyById(dmsOutsideAccommodationApply.getId());
        if (existing == null) {
            throw new ServiceException("未查询到申请表数据，无法更新", 500);
        }
        // 判断流程是否已启动（存在 processInstanceId 即为已启动）
        boolean isProcessStarted = StringUtils.isNotBlank(existing.getProcessInstanceId());

        if (isProcessStarted) {
            // 流程已启动：如果节点回到学生申请，则学生申请自动完成
            System.out.println("申请表[" + dmsOutsideAccommodationApply.getId() + "]流程已启动（实例ID：" + existing.getProcessInstanceId() + "），仅更新数据");
            // ========== 核心新增逻辑：仅当任务是「学生申请」节点时，学生自动完成待办任务 ==========
            String processInstanceId = existing.getProcessInstanceId();
            Long studentId = SecurityUtils.getUserId(); // 获取当前操作的学生ID
            String studentUserId = studentId.toString(); // 转换为Flowable的Assignee字符串类型

            // 1. 查询学生当前的待办任务（流程实例ID+学生ID作为负责人）（增加：任务名称/任务定义Key过滤，仅匹配「学生申请」节点）
            List<Task> studentTaskList = taskService.createTaskQuery()
                    .processInstanceId(processInstanceId)
                    .taskAssignee(studentUserId) // 学生作为任务负责人
                    .active() // 仅查询活跃任务
                    .taskName("学生申请")
                    .list();

            if (!studentTaskList.isEmpty()) {
                for (Task studentTask : studentTaskList) {
                    // 补充完成任务的流程变量（如审批意见、结果，根据你的流程需求设置）
                    Map<String, Object> completeVariables = new HashMap<>();
                    completeVariables.put("approvalResult", "AGREE"); // 学生提交（默认同意）
                    completeVariables.put("approvalOpinion", "学生自动提交申请，等待辅导员审批");
                    completeVariables.put("operator", "student"); // 操作人类型
                    // 添加任务分配的备注（便于流程追溯）
                    taskService.addComment(
                            studentTask.getId(),
                            processInstanceId,
                            "自动分配负责人",
                            "学生已经重新修改提交申请"
                    );
                    // 2. 学生自动完成待办任务
                    taskService.complete(studentTask.getId(), completeVariables);
                    System.out.println("学生[" + studentUserId + "]已自动完成待办任务，任务ID：" + studentTask.getId());
                }

                // 3. 分配下一节点（辅导员）的待办任务负责人【不再复用监听器，独立实现】
                assignCounselorTask(processInstanceId, existing.getId(), existing.getStudentNo());
            } else {
                System.out.println("流程实例[" + processInstanceId + "]中未查询到学生[" + studentUserId + "]的待办任务，无需自动完成");
            }

            // 同步更新流程变量（将业务表状态同步到流程中）
            runtimeService.setVariable(processInstanceId, "applyStatus", dmsOutsideAccommodationApply.getStatus());
            // 注意：若需更新流程相关变量，需调用 Flowable 的 runtimeService.setVariables()，此处仅更新业务表
            return dmsOutsideAccommodationApplyMapper.updateDmsOutsideAccommodationApply(dmsOutsideAccommodationApply);
        } else {
            // 流程未启动：仅当 applyStatus=1 时启动流程，否则仅更新数据
            Long applyStatus = dmsOutsideAccommodationApply.getStatus();
            if (1L == applyStatus) {
                // 绑定当前学生ID，启动流程
                dmsOutsideAccommodationApply.setStudentId(SecurityUtils.getUserId());
                ProcessResultDto processResultDto = startOutsideAccommodationProcess(dmsOutsideAccommodationApply);
                if (processResultDto == null || StringUtils.isBlank(processResultDto.getProcessInstanceId())) {
                    throw new ServiceException("流程启动失败，请重试", 500);
                }

                // 关联流程实例ID，更新为“审批中”状态
                dmsOutsideAccommodationApply.setProcessInstanceId(processResultDto.getProcessInstanceId());
                dmsOutsideAccommodationApply.setDeployId(processResultDto.getDeploymentId());
                dmsOutsideAccommodationApply.setStatus(1L); // 审批中
                System.out.println("申请表[" + dmsOutsideAccommodationApply.getId() + "]启动流程成功，实例ID：" + processResultDto.getProcessInstanceId());
            } else {
                // 非提交状态：仅更新基础数据（如草稿修改）
                String statusDesc = applyStatus == null ? "未指定状态" : (applyStatus == 0 ? "草稿" : "其他状态");
                System.out.println("申请表[" + dmsOutsideAccommodationApply.getId() + "]状态为" + statusDesc + "，流程未启动，仅更新数据");
            }

            // 执行数据库更新（含流程实例ID绑定或基础数据更新）
            return dmsOutsideAccommodationApplyMapper.updateDmsOutsideAccommodationApply(dmsOutsideAccommodationApply);
        }

    }


    /**
     * 开启外宿申请工作流
     *
     * @param dmsOutsideAccommodationApply
     */
    private ProcessResultDto startOutsideAccommodationProcess(DmsOutsideAccommodationApply
                                                                      dmsOutsideAccommodationApply) {
        Map<String, Object> variables = new HashMap<>();
        // 1. 传递关键变量（申请编号+主键ID，用于任务匹配）
        String applyNo = dmsOutsideAccommodationApply.getApplyNo(); // 申请编号（如RY2024001）
        Long enlistmentId = dmsOutsideAccommodationApply.getId();   // 申请表主键ID
        variables.put("applyNo", applyNo);
        variables.put("accommodationId", enlistmentId); // 新增：用于任务匹配的主键变量
        variables.put("stuId", dmsOutsideAccommodationApply.getStudentId());

        // 2. 查询辅导员信息
        TeacherVo counselorInfo = dmsOutsideAccommodationApplyMapper.getCounselorInfo(dmsOutsideAccommodationApply.getStudentNo());
        if (counselorInfo == null) {
            throw new ServiceException("该学生暂无辅导员", 500);
        }
        variables.put("approval", counselorInfo.getUserId()); // 下一节点：辅导员
        variables.put("deptId", counselorInfo.getDeptId());

        // 3. 启动流程
        AjaxResult ajaxResult = flowDefinitionService.startProcessInstanceById("flow_dre1txpk:11:1375008", variables);
        String code = ajaxResult.get("code").toString();
        if (!"200".equals(code)) {
            throw new ServiceException("流程启动失败，错误码：" + code, 500);
        }

        ProcessResultDto dto = (ProcessResultDto) ajaxResult.get("data");
        System.out.println("流程启动成功，实例ID：" + dto.getProcessInstanceId());

        // 4. 学生自动完成自己的待办任务
        identityService.setAuthenticatedUserId(SecurityUtils.getUserId().toString());
        // 精准查询当前流程中属于学生的待办任务
        List<Task> tasks = taskService.createTaskQuery()
                .processInstanceId(dto.getProcessInstanceId())
                .taskAssignee(SecurityUtils.getUserId().toString())
                .active()
                .list();

        if (tasks.isEmpty()) {
            throw new ServiceException("未查询到学生的待办任务，请检查流程设计", 500);
        }

        // 5. 完成任务（匹配主键ID，避免错误）
        variables.put("approved", true); // 学生提交确认
        for (Task task : tasks) {
            // 安全获取变量：先判断是否为null，再转换类型
            Object varValue = taskService.getVariable(task.getId(), "accommodationId");
            if (varValue == null) {
                continue;
            }
            // 转换为Long类型（与主键ID匹配）
            Long taskEnlistmentId;
            try {
                taskEnlistmentId = Long.parseLong(varValue.toString());
            } catch (NumberFormatException e) {
                continue; // 类型不匹配，跳过
            }
            // 匹配当前申请表的主键ID
            if (taskEnlistmentId.equals(enlistmentId)) {
                taskService.complete(task.getId(), variables);
                System.out.println("学生已完成待办任务，任务ID：" + task.getId());
                break; // 完成后退出循环，避免重复处理
            }
        }

        // 6. 推送消息给辅导员（待办提醒）
        // todo: 企业微信推送逻辑

        return dto;
    }
}
