package tech.easyflow.ai.mapper;

import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import tech.easyflow.ai.entity.DocumentImportTask;

import java.math.BigInteger;
import java.util.Date;
import java.util.List;

/**
 * 文档导入任务映射层。
 *
 * @author Codex
 * @since 2026-04-14
 */
public interface DocumentImportTaskMapper extends BaseMapper<DocumentImportTask> {

    /**
     * 按任务阶段和批次轮转查询待投递任务，避免大批次独占投递窗口。
     *
     * @param redispatchBefore 允许重新投递的修改时间边界
     * @param limit 最大任务数
     * @return 公平排序后的待投递任务
     */
    @Select("SELECT task.* FROM tb_document_import_task task JOIN ("
        + "SELECT task.id, ROW_NUMBER() OVER ("
        + "PARTITION BY task.phase, COALESCE(task.batch_id, task.id) "
        + "ORDER BY task.created, task.id) AS lane_row "
        + "FROM tb_document_import_task task "
        + "LEFT JOIN tb_document_import_batch batch ON batch.id=task.batch_id "
        + "WHERE task.status='PENDING' AND task.modified <= #{redispatchBefore} "
        + "AND (task.batch_id IS NULL OR batch.status='RUNNING')"
        + ") ranked ON ranked.id=task.id "
        + "ORDER BY ranked.lane_row, task.created, task.id LIMIT #{limit}")
    List<DocumentImportTask> selectPendingFairly(
        @Param("redispatchBefore") Date redispatchBefore,
        @Param("limit") int limit
    );

    /**
     * 仅在任务仍待处理时原子更新时间戳，取得本轮重新投递资格。
     *
     * @param id 任务 ID
     * @param redispatchBefore 允许重新投递的修改时间边界
     * @param now 当前时间
     * @param operatorId 操作人 ID
     * @return 更新行数
     */
    @Update("UPDATE tb_document_import_task SET modified=#{now}, "
        + "modified_by=#{operatorId}, version=COALESCE(version, 0) + 1 "
        + "WHERE id=#{id} AND status='PENDING' "
        + "AND modified <= #{redispatchBefore} "
        + "AND (batch_id IS NULL OR EXISTS ("
        + "SELECT 1 FROM tb_document_import_batch batch "
        + "WHERE batch.id=tb_document_import_task.batch_id "
        + "AND batch.status='RUNNING'))")
    int touchPendingForDispatch(
        @Param("id") BigInteger id,
        @Param("redispatchBefore") Date redispatchBefore,
        @Param("now") Date now,
        @Param("operatorId") BigInteger operatorId
    );

    /**
     * 查询租约已过期的运行任务，并兼容迁移前没有租约的历史任务。
     *
     * @param now 当前时间
     * @param legacyCutoff 历史任务失联时间边界
     * @param limit 最大任务数
     * @return 已失去执行租约的运行任务
     */
    @Select("SELECT * FROM tb_document_import_task "
        + "WHERE status='RUNNING' AND ("
        + "(lease_until IS NOT NULL AND lease_until <= #{now}) OR "
        + "(lease_until IS NULL AND modified <= #{legacyCutoff})) "
        + "ORDER BY COALESCE(lease_until, modified), id LIMIT #{limit}")
    List<DocumentImportTask> selectExpiredRunningTasks(
        @Param("now") Date now,
        @Param("legacyCutoff") Date legacyCutoff,
        @Param("limit") int limit
    );

    /**
     * 使用执行令牌原子领取一个待处理任务。
     *
     * @param id 任务 ID
     * @param executionToken 本轮执行令牌
     * @param leaseUntil 租约到期时间
     * @param now 当前时间
     * @param operatorId 操作人 ID
     * @return 更新行数
     */
    @Update("UPDATE tb_document_import_task SET status='RUNNING', "
        + "attempt_no=COALESCE(attempt_no, 0) + 1, "
        + "execution_token=#{executionToken}, lease_until=#{leaseUntil}, "
        + "started_at=COALESCE(started_at, #{now}), finished_at=NULL, "
        + "error_summary=NULL, failure_code=NULL, modified=#{now}, "
        + "modified_by=#{operatorId}, version=COALESCE(version, 0) + 1 "
        + "WHERE id=#{id} AND status='PENDING' "
        + "AND (batch_id IS NULL OR EXISTS ("
        + "SELECT 1 FROM tb_document_import_batch batch "
        + "WHERE batch.id=tb_document_import_task.batch_id "
        + "AND batch.status='RUNNING'))")
    int claimPending(
        @Param("id") BigInteger id,
        @Param("executionToken") String executionToken,
        @Param("leaseUntil") Date leaseUntil,
        @Param("now") Date now,
        @Param("operatorId") BigInteger operatorId
    );

    /**
     * 在仍持有执行令牌时续租任务。
     *
     * @param id 任务 ID
     * @param executionToken 本轮执行令牌
     * @param leaseUntil 新租约到期时间
     * @param now 当前时间
     * @param operatorId 操作人 ID
     * @return 更新行数
     */
    @Update("UPDATE tb_document_import_task SET lease_until=#{leaseUntil}, "
        + "modified=#{now}, modified_by=#{operatorId}, "
        + "version=COALESCE(version, 0) + 1 "
        + "WHERE id=#{id} AND status='RUNNING' "
        + "AND execution_token=#{executionToken} "
        + "AND (batch_id IS NULL OR EXISTS ("
        + "SELECT 1 FROM tb_document_import_batch batch "
        + "WHERE batch.id=tb_document_import_task.batch_id "
        + "AND batch.status='RUNNING'))")
    int renewLease(
        @Param("id") BigInteger id,
        @Param("executionToken") String executionToken,
        @Param("leaseUntil") Date leaseUntil,
        @Param("now") Date now,
        @Param("operatorId") BigInteger operatorId
    );

    /**
     * 在仍持有执行令牌时写入任务终态。
     *
     * @param id 任务 ID
     * @param executionToken 本轮执行令牌
     * @param status 任务终态
     * @param errorSummary 错误摘要
     * @param failureCode 稳定失败码
     * @param now 当前时间
     * @param operatorId 操作人 ID
     * @return 更新行数
     */
    @Update("UPDATE tb_document_import_task SET status=#{status}, "
        + "error_summary=#{errorSummary}, failure_code=#{failureCode}, "
        + "lease_until=NULL, finished_at=#{now}, modified=#{now}, "
        + "modified_by=#{operatorId}, version=COALESCE(version, 0) + 1 "
        + "WHERE id=#{id} AND status='RUNNING' "
        + "AND execution_token=#{executionToken} "
        + "AND (batch_id IS NULL OR EXISTS ("
        + "SELECT 1 FROM tb_document_import_batch batch "
        + "WHERE batch.id=tb_document_import_task.batch_id "
        + "AND batch.status='RUNNING'))")
    int finishOwned(
        @Param("id") BigInteger id,
        @Param("executionToken") String executionToken,
        @Param("status") String status,
        @Param("errorSummary") String errorSummary,
        @Param("failureCode") String failureCode,
        @Param("now") Date now,
        @Param("operatorId") BigInteger operatorId
    );

    /**
     * 仅在当前执行令牌仍持有已过期租约时将任务标记为失败。
     *
     * @param id 任务 ID
     * @param executionToken 本轮执行令牌
     * @param errorSummary 错误摘要
     * @param failureCode 稳定失败码
     * @param now 当前时间
     * @param legacyCutoff 历史任务失联时间边界
     * @param operatorId 操作人 ID
     * @return 更新行数
     */
    @Update("UPDATE tb_document_import_task SET status='FAILED', "
        + "error_summary=#{errorSummary}, failure_code=#{failureCode}, "
        + "lease_until=NULL, finished_at=#{now}, modified=#{now}, "
        + "modified_by=#{operatorId}, version=COALESCE(version, 0) + 1 "
        + "WHERE id=#{id} AND status='RUNNING' "
        + "AND execution_token <=> #{executionToken} AND ("
        + "(lease_until IS NOT NULL AND lease_until <= #{now}) OR "
        + "(lease_until IS NULL AND modified <= #{legacyCutoff}))")
    int failExpiredOwned(
        @Param("id") BigInteger id,
        @Param("executionToken") String executionToken,
        @Param("errorSummary") String errorSummary,
        @Param("failureCode") String failureCode,
        @Param("now") Date now,
        @Param("legacyCutoff") Date legacyCutoff,
        @Param("operatorId") BigInteger operatorId
    );

    /**
     * 将中断批次中所有活跃任务强制收口并撤销执行令牌。
     *
     * @param batchId 批次 ID
     * @param errorSummary 用户可见错误摘要
     * @param failureCode 稳定失败码
     * @param now 中断时间
     * @param operatorId 操作人 ID
     * @return 更新任务数量
     */
    @Update("UPDATE tb_document_import_task task "
        + "INNER JOIN tb_document_import_batch batch ON batch.id=task.batch_id "
        + "SET task.status='FAILED', task.error_summary=#{errorSummary}, "
        + "task.failure_code=#{failureCode}, task.execution_token=NULL, "
        + "task.lease_until=NULL, task.finished_at=#{now}, "
        + "task.modified=#{now}, task.modified_by=#{operatorId}, "
        + "task.version=COALESCE(task.version, 0) + 1 "
        + "WHERE task.batch_id=#{batchId} AND batch.status='INTERRUPTED' "
        + "AND task.status IN ('PENDING','RUNNING')")
    int interruptActiveTasks(
        @Param("batchId") BigInteger batchId,
        @Param("errorSummary") String errorSummary,
        @Param("failureCode") String failureCode,
        @Param("now") Date now,
        @Param("operatorId") BigInteger operatorId
    );

    /**
     * 将中断任务关联文档同步为对应阶段失败，供文档列表展示。
     *
     * @param batchId 批次 ID
     * @param errorSummary 用户可见错误摘要
     * @param failureCode 稳定失败码
     * @param now 中断时间
     * @param operatorId 操作人 ID
     * @return 更新文档数量
     */
    @Update("UPDATE tb_document document "
        + "INNER JOIN tb_document_import_task task "
        + "ON task.document_id=document.id "
        + "INNER JOIN tb_document_import_batch batch "
        + "ON batch.id=task.batch_id SET "
        + "document.process_status=CASE task.phase "
        + "WHEN 'PARSE' THEN 'PARSE_FAILED' "
        + "WHEN 'SPLIT' THEN 'SPLIT_FAILED' "
        + "ELSE 'INDEX_FAILED' END, "
        + "document.progress_percent=0, "
        + "document.last_task_error=#{errorSummary}, "
        + "document.options=JSON_SET("
        + "CASE WHEN JSON_VALID(document.options) "
        + "THEN document.options ELSE JSON_OBJECT() END, "
        + "'$.\"task.errorCode\"', #{failureCode}), "
        + "document.task_modified_at=#{now}, document.modified=#{now}, "
        + "document.modified_by=#{operatorId} "
        + "WHERE task.batch_id=#{batchId} AND batch.status='INTERRUPTED' "
        + "AND task.status IN ('PENDING','RUNNING')")
    int interruptActiveDocuments(
        @Param("batchId") BigInteger batchId,
        @Param("errorSummary") String errorSummary,
        @Param("failureCode") String failureCode,
        @Param("now") Date now,
        @Param("operatorId") BigInteger operatorId
    );
}
