package tech.easyflow.ai.documentimport.task;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import tech.easyflow.ai.documentimport.ImportCallerContext;
import tech.easyflow.ai.documentimport.ImportCallerType;
import tech.easyflow.ai.entity.DocumentImportBatch;
import tech.easyflow.ai.mapper.DocumentImportBatchMapper;
import tech.easyflow.common.cache.DistributedScheduledLock;
import tech.easyflow.common.util.StringUtil;

import java.time.Duration;
import java.util.Date;
import java.util.List;

/**
 * 文档导入未完成批次与取消对象清理调度器。
 *
 * @author Codex
 * @since 2026-08-02
 */
@Component
public class DocumentImportStaleBatchMonitor {

    private static final Logger LOG =
        LoggerFactory.getLogger(DocumentImportStaleBatchMonitor.class);
    private static final Duration INCOMPLETE_TIMEOUT = Duration.ofMinutes(30);
    private static final int BATCH_SIZE = 100;

    private final DocumentImportBatchMapper batchMapper;
    private final DocumentImportBatchAppService batchAppService;

    /**
     * 创建未完成批次清理调度器。
     *
     * @param batchMapper 批次 Mapper
     * @param batchAppService 批次应用服务
     */
    public DocumentImportStaleBatchMonitor(
        DocumentImportBatchMapper batchMapper,
        DocumentImportBatchAppService batchAppService) {
        this.batchMapper = batchMapper;
        this.batchAppService = batchAppService;
    }

    /**
     * 分批回收长时间无进展的上传批次，并重试对象清理。
     */
    @Scheduled(
        fixedDelayString =
            "${easyflow.ai.document-import.bulk.stale-batch-scan-interval:60s}",
        initialDelayString =
            "${easyflow.ai.document-import.bulk.stale-batch-scan-interval:60s}"
    )
    @DistributedScheduledLock(
        key = "easyflow:schedule:document-import:stale-batch-cleanup",
        leaseSeconds = 50L
    )
    public void cleanupStaleBatches() {
        Date cutoff = new Date(
            System.currentTimeMillis() - INCOMPLETE_TIMEOUT.toMillis()
        );
        List<DocumentImportBatch> candidates =
            batchMapper.selectStaleIncompleteBatches(cutoff, BATCH_SIZE);
        for (DocumentImportBatch batch : candidates) {
            cancelCandidate(batch, cutoff);
        }
        batchAppService.cleanupCancelledStoredObjects(BATCH_SIZE);
    }

    /**
     * 通过数据库条件更新尝试取消单个候选批次。
     *
     * @param batch 候选批次
     * @param cutoff 最后进展截止时间
     */
    private void cancelCandidate(DocumentImportBatch batch, Date cutoff) {
        if (batch.getCallerType() == null || batch.getCallerId() == null) {
            LOG.warn("跳过调用者信息不完整的超时导入批次: batchId={}", batch.getId());
            return;
        }
        try {
            ImportCallerContext caller = new ImportCallerContext(
                ImportCallerType.valueOf(batch.getCallerType()),
                batch.getCallerId()
            );
            boolean cancelled = batchAppService.cancelStaleBatch(
                batch.getKnowledgeId(),
                batch.getId(),
                caller,
                cutoff
            );
            if (cancelled && StringUtil.hasText(batch.getIdempotencyKeyHash())) {
                int released = batchMapper.releaseSubmissionFingerprint(
                    batch.getId(),
                    batch.getIdempotencyKeyHash(),
                    cutoff,
                    new Date()
                );
                if (released <= 0) {
                    LOG.warn(
                        "超时导入批次已取消但提交指纹未释放: batchId={}",
                        batch.getId()
                    );
                }
            }
        } catch (IllegalArgumentException error) {
            LOG.error(
                "超时导入批次调用者类型无效: batchId={}, callerType={}",
                batch.getId(),
                batch.getCallerType(),
                error
            );
        } catch (RuntimeException error) {
            LOG.error("回收超时导入批次失败: batchId={}", batch.getId(), error);
        }
    }
}
