/**
 * Copyright &copy; 2021-2026 <a href="http://www.jeeplus.org/">JeePlus</a> All rights reserved.
 */
package com.jeeplus.flowable.service;

import com.jeeplus.flowable.mapper.FlowArchiveMapper;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.impl.persistence.entity.HistoricActivityInstanceEntityImpl;
import org.flowable.engine.history.HistoricActivityInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.*;

/**
 * 流程归档数据查询服务（冷表回查）
 * 当热表中查不到数据时，从归档表中查询历史数据
 *
 * @author jeeplus
 * @version 2024-01-01
 */
@Slf4j
@Service
public class FlowArchiveService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private FlowArchiveMapper flowArchiveMapper;

    /**
     * 从归档表查询活动实例列表
     */
    public List<HistoricActivityInstance> findActivityInstances(String processInstanceId) {
        String sql = "SELECT * FROM act_hi_actinst_archive WHERE PROC_INST_ID_ = ? ORDER BY START_TIME_ ASC";
        return jdbcTemplate.query(sql, new Object[]{processInstanceId}, new HistoricActivityInstanceRowMapper());
    }

    /**
     * 从归档表查询已结束的活动实例列表
     */
    public List<HistoricActivityInstance> findFinishedActivityInstances(String processInstanceId) {
        String sql = "SELECT * FROM act_hi_actinst_archive WHERE PROC_INST_ID_ = ? AND END_TIME_ IS NOT NULL ORDER BY END_TIME_ ASC";
        return jdbcTemplate.query(sql, new Object[]{processInstanceId}, new HistoricActivityInstanceRowMapper());
    }

    /**
     * 从归档表查询历史流程实例
     */
    public Map<String, Object> findHistoricProcessInstance(String processInstanceId) {
        String sql = "SELECT * FROM act_hi_procinst_archive WHERE PROC_INST_ID_ = ?";
        List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, processInstanceId);
        return result.isEmpty() ? null : result.get(0);
    }

    /**
     * 从归档表查询历史变量
     */
    public List<Map<String, Object>> findHistoricVariables(String processInstanceId) {
        String sql = "SELECT * FROM act_hi_varinst_archive WHERE PROC_INST_ID_ = ?";
        return jdbcTemplate.queryForList(sql, processInstanceId);
    }

    /**
     * 从归档表查询指定流程变量的值
     */
    public Map<String, Object> findHistoricVariable(String processInstanceId, String variableName) {
        String sql = "SELECT * FROM act_hi_varinst_archive WHERE PROC_INST_ID_ = ? AND NAME_ = ?";
        List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, processInstanceId, variableName);
        return result.isEmpty() ? null : result.get(0);
    }

    /**
     * 从归档表查询任务评论
     */
    public List<Map<String, Object>> findTaskComments(String taskId) {
        String sql = "SELECT * FROM act_hi_comment_archive WHERE TASK_ID_ = ? ORDER BY TIME_ DESC";
        return jdbcTemplate.queryForList(sql, taskId);
    }

    /**
     * 从归档表查询历史任务实例
     */
    public List<Map<String, Object>> findHistoricTaskInstances(String processInstanceId) {
        String sql = "SELECT * FROM act_hi_taskinst_archive WHERE PROC_INST_ID_ = ?";
        return jdbcTemplate.queryForList(sql, processInstanceId);
    }

    /**
     * 检查流程实例是否在归档表中
     */
    public boolean isArchived(String processInstanceId) {
        String sql = "SELECT COUNT(1) FROM act_hi_procinst_archive WHERE PROC_INST_ID_ = ?";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class, processInstanceId);
        return count != null && count > 0;
    }

    /**
     * 执行流程历史数据归档迁移（通过 MyBatis Mapper 实现）
     * 将3个月前已结束的流程数据从热表迁移到冷表（归档表）
     * SQL 写在 XML 文件中，不使用 @Transactional 让每条 SQL 自动提交
     *
     * @return 迁移统计信息
     */
    public Map<String, Object> executeArchive() {
        log.info("===== 开始执行流程数据归档迁移 =====");
        Map<String, Object> result = new LinkedHashMap<>();

        // 设置截止日期为3个月前（增量迁移）
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.MONTH, -3);
        Date cutoffDate = cal.getTime();
        String cutoffStr = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(cutoffDate);
        log.info("迁移截止日期: {}", cutoffStr);

        // 1. 检查是否有需要归档的数据
        Integer pendingCount = flowArchiveMapper.countPendingArchive(cutoffDate);
        if (pendingCount == null || pendingCount == 0) {
            log.info("没有需要归档的数据");
            result.put("status", "SUCCESS");
            result.put("msg", "没有需要归档的数据");
            return result;
        }
        log.info("待归档流程实例数: {}", pendingCount);

        int totalProcinst = 0;
        int batchSize = 500;

        // 2. 分批迁移流程实例（每次500条）
        log.info("开始迁移 act_hi_procinst...");
        while (true) {
            int inserted = flowArchiveMapper.insertProcinstArchive(cutoffDate, batchSize);
            totalProcinst += inserted;
            log.info("act_hi_procinst 本批迁移 {} 条, 累计 {} 条", inserted, totalProcinst);
            if (inserted < batchSize) break;
        }
        log.info("act_hi_procinst 迁移完成, 共 {} 条", totalProcinst);

        // 3. 迁移关联数据
        log.info("开始迁移 act_hi_actinst...");
        int actinst = flowArchiveMapper.insertActinstArchive();
        log.info("act_hi_actinst 迁移完成, 共 {} 条", actinst);

        log.info("开始迁移 act_hi_varinst...");
        int varinst = flowArchiveMapper.insertVarinstArchive();
        log.info("act_hi_varinst 迁移完成, 共 {} 条", varinst);

        log.info("开始迁移 act_hi_taskinst...");
        int taskinst = flowArchiveMapper.insertTaskinstArchive();
        log.info("act_hi_taskinst 迁移完成, 共 {} 条", taskinst);

        log.info("开始迁移 act_hi_identitylink...");
        int identitylink = flowArchiveMapper.insertIdentitylinkArchive();
        log.info("act_hi_identitylink 迁移完成, 共 {} 条", identitylink);

        log.info("开始迁移 act_hi_comment...");
        int comment = flowArchiveMapper.insertCommentArchive();
        log.info("act_hi_comment 迁移完成, 共 {} 条", comment);

        // 4. 从热表删除已归档的数据（先删子表，再删主表）
        log.info("开始清理热表数据...");
        flowArchiveMapper.deleteActinst();
        flowArchiveMapper.deleteVarinst();
        flowArchiveMapper.deleteTaskinst();
        flowArchiveMapper.deleteIdentitylink();
        flowArchiveMapper.deleteComment();
        flowArchiveMapper.deleteProcinst();
        log.info("热表数据清理完成");

        // 5. 记录归档日志
        try {
            flowArchiveMapper.insertArchiveLog(cutoffDate, totalProcinst, actinst, varinst, taskinst, identitylink, comment);
        } catch (Exception e) {
            log.warn("记录归档日志失败（flow_archive_log表可能不存在）: {}", e.getMessage());
        }

        // 6. 组装返回结果
        result.put("status", "SUCCESS");
        result.put("cutoffDate", cutoffStr);
        result.put("procinst", totalProcinst);
        result.put("actinst", actinst);
        result.put("varinst", varinst);
        result.put("taskinst", taskinst);
        result.put("identitylink", identitylink);
        result.put("comment", comment);
        log.info("===== 流程数据归档迁移完成, 共迁移 {} 条流程实例 =====", totalProcinst);
        return result;
    }

    /**
     * HistoricActivityInstance 行映射器
     * 将归档表数据映射为 Flowable 的 HistoricActivityInstance 对象
     */
    private static class HistoricActivityInstanceRowMapper implements RowMapper<HistoricActivityInstance> {
        @Override
        public HistoricActivityInstance mapRow(ResultSet rs, int rowNum) throws SQLException {
            HistoricActivityInstanceEntityImpl entity = new HistoricActivityInstanceEntityImpl();
            entity.setId(rs.getString("ID_"));
            entity.setRevision(rs.getInt("REV_"));
            entity.setProcessDefinitionId(rs.getString("PROC_DEF_ID_"));
            entity.setProcessInstanceId(rs.getString("PROC_INST_ID_"));
            entity.setExecutionId(rs.getString("EXECUTION_ID_"));
            entity.setActivityId(rs.getString("ACT_ID_"));
            entity.setTaskId(rs.getString("TASK_ID_"));
            // entity.setCallProcessInstanceId - 跳过，非关键字段
            entity.setActivityName(rs.getString("ACT_NAME_"));
            entity.setActivityType(rs.getString("ACT_TYPE_"));
            entity.setAssignee(rs.getString("ASSIGNEE_"));
            Timestamp startTime = rs.getTimestamp("START_TIME_");
            if (startTime != null) {
                entity.setStartTime(new Date(startTime.getTime()));
            }
            Timestamp endTime = rs.getTimestamp("END_TIME_");
            if (endTime != null) {
                entity.setEndTime(new Date(endTime.getTime()));
            }
            entity.setDurationInMillis(rs.getLong("DURATION_"));
            entity.setDeleteReason(rs.getString("DELETE_REASON_"));
            entity.setTenantId(rs.getString("TENANT_ID_"));
            return entity;
        }
    }
}
