package tech.easyflow.skill.mapper;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.junit.Test;

import java.lang.reflect.Method;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
 * {@link SkillContentWriteIntentMapper} 原子状态转换 SQL 契约测试。
 */
public class SkillContentWriteIntentMapperSqlTest {

    private static final String ALL_COLUMNS =
            "content_ref AS contentRef,reservation_token AS reservationToken,content_hash AS contentHash," +
            "storage_locator AS storageLocator,media_type AS mediaType,size,state,created,modified";

    /**
     * 验证预留通过普通 INSERT 竞争主键，避免静默吞掉主键之外的数据库错误。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void reserveCreatesValidatedPendingIntent() throws Exception {
        Method reserve = SkillContentWriteIntentMapper.class.getMethod("reserve", String.class, String.class,
                String.class, String.class, String.class, long.class);
        String sql = sql(reserve, Insert.class);

        assertTrue(sql.contains("INSERT INTO tb_skill_content_write_intent"));
        assertFalse(sql.contains("INSERT IGNORE"));
        assertTrue(sql.contains("'PENDING'"));
        assertTrue(sql.contains("#{storageLocator} IS NOT NULL"));
        assertTrue(sql.contains("#{storageLocator}<>''"));
        assertTrue(sql.contains("CONCAT('sha256:',#{contentHash})=#{contentRef}"));
    }

    /**
     * 验证写入声明仅允许同一令牌从 PENDING 原子进入 WRITING。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void claimForWriteMatchesTokenAndPendingState() throws Exception {
        Method claim = SkillContentWriteIntentMapper.class.getMethod(
                "claimForWrite", String.class, String.class);
        String sql = sql(claim, Update.class);

        assertTrue(sql.contains("SET state='WRITING'"));
        assertTrue(sql.contains("reservation_token=#{reservationToken}"));
        assertTrue(sql.contains("state='PENDING'"));
    }

    /**
     * 验证过期扫描覆盖全部未完成状态并返回完整实体字段。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void staleScanCoversAllStatesAndColumns() throws Exception {
        Method find = SkillContentWriteIntentMapper.class.getMethod("findStale", java.util.Date.class, int.class);
        String sql = sql(find, Select.class);

        assertTrue(sql.contains("SELECT " + ALL_COLUMNS));
        assertTrue(sql.contains("state IN ('PENDING','WRITING','CLEANING')"));
        assertTrue(sql.contains("modified<#{cutoff}"));
        assertTrue(sql.contains("ORDER BY modified ASC LIMIT #{limit}"));
    }

    /**
     * 验证清理声明使用 token、观察状态与截止时间做 CAS，且活动内容存在时禁止清理。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void cleanupClaimIsConditionalAndProtectsActiveContent() throws Exception {
        Method claim = SkillContentWriteIntentMapper.class.getMethod(
                "claimForCleanup", String.class, String.class, String.class, java.util.Date.class);
        String sql = sql(claim, Update.class);

        assertTrue(sql.contains("SET state='CLEANING'"));
        assertTrue(sql.contains("reservation_token=#{reservationToken}"));
        assertTrue(sql.contains("state=#{expectedState}"));
        assertTrue(sql.contains("state IN ('PENDING','WRITING','CLEANING')"));
        assertTrue(sql.contains("modified<#{cutoff}"));
        assertTrue(sql.contains("NOT EXISTS"));
        assertTrue(sql.contains("active_content.ref_count>0"));
    }

    /**
     * 验证清理完成只删除同一 token 的 CLEANING 意图，活动内容则仅清除残留意图。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void intentDeletesAreTokenScopedAndStateSafe() throws Exception {
        Method deleteClaimed = SkillContentWriteIntentMapper.class.getMethod(
                "deleteClaimed", String.class, String.class);
        Method deleteActive = SkillContentWriteIntentMapper.class.getMethod(
                "deleteIfActiveExists", String.class, String.class);
        Method deletePending = SkillContentWriteIntentMapper.class.getMethod(
                "deletePending", String.class, String.class);

        String claimedSql = sql(deleteClaimed, Delete.class);
        String activeSql = sql(deleteActive, Delete.class);
        String pendingSql = sql(deletePending, Delete.class);

        assertTrue(claimedSql.contains("reservation_token=#{reservationToken}"));
        assertTrue(claimedSql.contains("state='CLEANING'"));
        assertTrue(activeSql.contains("reservation_token=#{reservationToken}"));
        assertTrue(activeSql.contains("EXISTS"));
        assertTrue(activeSql.contains("active_content.ref_count>0"));
        assertTrue(pendingSql.contains("reservation_token=#{reservationToken}"));
        assertTrue(pendingSql.contains("state='PENDING'"));
    }

    /**
     * 验证单条意图查询读取完整字段。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void getIntentSelectsEveryMappedColumn() throws Exception {
        Method get = SkillContentWriteIntentMapper.class.getMethod("getIntent", String.class);
        String sql = sql(get, Select.class);

        assertTrue(sql.contains("SELECT " + ALL_COLUMNS));
        assertTrue(sql.contains("content_ref=#{contentRef}"));
    }

    /**
     * 读取方法上的单个 SQL 注解值。
     *
     * @param method Mapper 方法
     * @param annotationType SQL 注解类型
     * @return SQL 文本
     * @throws ReflectiveOperationException 注解 value 方法不可访问
     */
    private String sql(Method method, Class<?> annotationType) throws ReflectiveOperationException {
        Object annotation = method.getAnnotation(annotationType.asSubclass(java.lang.annotation.Annotation.class));
        String[] values = (String[]) annotationType.getMethod("value").invoke(annotation);
        return String.join(" ", values);
    }
}
