package tech.easyflow.skill.mapper;

import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import tech.easyflow.skill.entity.SkillContentWriteIntent;

import java.util.Date;
import java.util.List;

/**
 * Skill 二进制内容写入意图 Mapper。
 */
public interface SkillContentWriteIntentMapper extends BaseMapper<SkillContentWriteIntent> {

    /**
     * 原子预留指定内容引用的写入意图。
     *
     * @param contentRef 内容引用
     * @param reservationToken 写入预留令牌
     * @param contentHash 内容哈希
     * @param storageLocator 稳定存储定位符
     * @param mediaType 媒体类型
     * @param size 内容字节数
     * @return 成功插入为 1，参数不满足约束为 0
     * @throws org.springframework.dao.DuplicateKeyException 内容引用已被其他写入意图预留
     */
    @Insert("INSERT INTO tb_skill_content_write_intent("
            + "content_ref,reservation_token,content_hash,storage_locator,media_type,size,state,created,modified) "
            + "SELECT #{contentRef},#{reservationToken},#{contentHash},#{storageLocator},#{mediaType},#{size},"
            + "'PENDING',CURRENT_TIMESTAMP,CURRENT_TIMESTAMP "
            + "WHERE #{storageLocator} IS NOT NULL AND #{storageLocator}<>'' "
            + "AND CONCAT('sha256:',#{contentHash})=#{contentRef}")
    int reserve(@Param("contentRef") String contentRef,
                @Param("reservationToken") String reservationToken,
                @Param("contentHash") String contentHash,
                @Param("storageLocator") String storageLocator,
                @Param("mediaType") String mediaType,
                @Param("size") long size);

    /**
     * 在调用方事务中将预留意图原子声明为正在写入。
     *
     * <p>UPDATE 会持有目标行的排他锁直至调用方事务结束；事务回滚后状态恢复为 PENDING。</p>
     *
     * @param contentRef 内容引用
     * @param reservationToken 写入预留令牌
     * @return 成功声明为 1，令牌或状态不匹配为 0
     */
    @Update("UPDATE tb_skill_content_write_intent SET state='WRITING',modified=CURRENT_TIMESTAMP "
            + "WHERE content_ref=#{contentRef} AND reservation_token=#{reservationToken} AND state='PENDING'")
    int claimForWrite(@Param("contentRef") String contentRef,
                      @Param("reservationToken") String reservationToken);

    /**
     * 查询超过截止时间且尚未完成的写入意图。
     *
     * @param cutoff 截止时间
     * @param limit 最大返回数量
     * @return 按修改时间升序排列的过期意图
     */
    @Select("SELECT content_ref AS contentRef,reservation_token AS reservationToken," +
            "content_hash AS contentHash,storage_locator AS storageLocator,media_type AS mediaType," +
            "size,state,created,modified "
            + "FROM tb_skill_content_write_intent "
            + "WHERE state IN ('PENDING','WRITING','CLEANING') AND modified<#{cutoff} "
            + "ORDER BY modified ASC LIMIT #{limit}")
    List<SkillContentWriteIntent> findStale(@Param("cutoff") Date cutoff, @Param("limit") int limit);

    /**
     * 将过期意图原子声明为清理中。
     *
     * <p>expectedState 构成状态 CAS。传入 CLEANING 时，同一令牌可幂等重试；存在正式活动内容时
     * 不允许取得清理权。</p>
     *
     * @param contentRef 内容引用
     * @param reservationToken 写入预留令牌
     * @param expectedState 查询时观察到的状态
     * @param cutoff 截止时间
     * @return 成功声明为 1，状态已变化或存在活动内容为 0
     */
    @Update("UPDATE tb_skill_content_write_intent SET state='CLEANING',modified=CURRENT_TIMESTAMP "
            + "WHERE content_ref=#{contentRef} AND reservation_token=#{reservationToken} "
            + "AND state=#{expectedState} AND state IN ('PENDING','WRITING','CLEANING') "
            + "AND modified<#{cutoff} AND NOT EXISTS ("
            + "SELECT 1 FROM tb_skill_content active_content "
            + "WHERE active_content.content_ref=tb_skill_content_write_intent.content_ref "
            + "AND active_content.ref_count>0)")
    int claimForCleanup(@Param("contentRef") String contentRef,
                        @Param("reservationToken") String reservationToken,
                        @Param("expectedState") String expectedState,
                        @Param("cutoff") Date cutoff);

    /**
     * 删除当前令牌已经取得清理权的写入意图。
     *
     * @param contentRef 内容引用
     * @param reservationToken 写入预留令牌
     * @return 删除行数
     */
    @Delete("DELETE FROM tb_skill_content_write_intent WHERE content_ref=#{contentRef} "
            + "AND reservation_token=#{reservationToken} AND state='CLEANING'")
    int deleteClaimed(@Param("contentRef") String contentRef,
                      @Param("reservationToken") String reservationToken);

    /**
     * 正式内容已激活时删除残留写入意图。
     *
     * @param contentRef 内容引用
     * @param reservationToken 写入预留令牌
     * @return 删除行数
     */
    @Delete("DELETE FROM tb_skill_content_write_intent WHERE content_ref=#{contentRef} "
            + "AND reservation_token=#{reservationToken} AND EXISTS ("
            + "SELECT 1 FROM tb_skill_content active_content "
            + "WHERE active_content.content_ref=tb_skill_content_write_intent.content_ref "
            + "AND active_content.ref_count>0)")
    int deleteIfActiveExists(@Param("contentRef") String contentRef,
                             @Param("reservationToken") String reservationToken);

    /**
     * 删除调用方尚未声明写入、且确认不会产生物理对象的预留意图。
     *
     * @param contentRef 内容引用
     * @param reservationToken 写入预留令牌
     * @return 删除行数
     */
    @Delete("DELETE FROM tb_skill_content_write_intent WHERE content_ref=#{contentRef} "
            + "AND reservation_token=#{reservationToken} AND state='PENDING'")
    int deletePending(@Param("contentRef") String contentRef,
                      @Param("reservationToken") String reservationToken);

    /**
     * 按内容引用读取完整写入意图。
     *
     * @param contentRef 内容引用
     * @return 写入意图，不存在时为 null
     */
    @Select("SELECT content_ref AS contentRef,reservation_token AS reservationToken," +
            "content_hash AS contentHash,storage_locator AS storageLocator,media_type AS mediaType," +
            "size,state,created,modified "
            + "FROM tb_skill_content_write_intent WHERE content_ref=#{contentRef}")
    SkillContentWriteIntent getIntent(@Param("contentRef") String contentRef);
}
