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 java.util.HashSet;
import java.util.Set;

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

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

    /**
     * 验证注解 SQL 方法名可以作为唯一的 MyBatis statement id，避免应用启动时重复注册。
     */
    @Test
    public void annotatedStatementsHaveUniqueMethodNames() {
        Set<String> statementIds = new HashSet<>();

        for (Method method : SkillContentMapper.class.getDeclaredMethods()) {
            boolean annotated = method.isAnnotationPresent(Select.class)
                    || method.isAnnotationPresent(Insert.class)
                    || method.isAnnotationPresent(Update.class)
                    || method.isAnnotationPresent(Delete.class);
            if (annotated) {
                assertTrue("Mapper 注解 SQL 方法不允许重载: " + method.getName(),
                        statementIds.add(method.getName()));
            }
        }
    }

    /**
     * 验证占位记录以零引用写入，并在物理路径完成后原子激活首个引用。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void reservationStartsInvisibleAndFinishesWithFirstReference() throws Exception {
        Method reserve = SkillContentMapper.class.getMethod(
                "reserve", String.class, String.class, String.class, String.class, long.class);
        Method finish = SkillContentMapper.class.getMethod("finishReservation", String.class, String.class);

        String reserveSql = String.join(" ", reserve.getAnnotation(Insert.class).value());
        String finishSql = String.join(" ", finish.getAnnotation(Update.class).value());

        assertTrue(reserveSql.contains("#{size},0,CURRENT_TIMESTAMP"));
        assertTrue(finishSql.contains("ref_count=1"));
        assertTrue(finishSql.contains("ref_count=0"));
        assertTrue(finishSql.contains("file_path LIKE '__PENDING__:%'"));
    }

    /**
     * 验证引用增加仅作用于已完成且仍可见的内容。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void retainExcludesPendingAndZeroReferenceRows() throws Exception {
        Method retain = SkillContentMapper.class.getMethod("retain", String.class);
        String sql = String.join(" ", retain.getAnnotation(Update.class).value());

        assertTrue(sql.contains("ref_count > 0"));
        assertTrue(sql.contains("file_path NOT LIKE '__PENDING__:%'"));
    }

    /**
     * 验证按内容大小复用时同时检查哈希一致性、正式路径与 locator 兼容状态。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void retainMatchingRequiresSizeHashAndActiveLocation() throws Exception {
        Method retain = SkillContentMapper.class.getMethod("retainMatching", String.class, long.class);
        String sql = String.join(" ", retain.getAnnotation(Update.class).value());

        assertTrue(sql.contains("size=#{size}"));
        assertTrue(sql.contains("CONCAT('sha256:',content_hash)=#{contentRef}"));
        assertTrue(sql.contains("file_path IS NOT NULL"));
        assertTrue(sql.contains("file_path<>''"));
        assertTrue(sql.contains("file_path NOT LIKE '__PENDING__:%'"));
        assertTrue(sql.contains("storage_locator IS NULL OR storage_locator<>''"));
    }

    /**
     * 验证引用状态转换使用锁定当前读，并且旧版恢复严格限制为已校验的零引用无 locator 行。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void currentReadAndLegacyResurrectionAreStateSafe() throws Exception {
        Method current = SkillContentMapper.class.getMethod("selectForUpdate", String.class);
        Method resurrect = SkillContentMapper.class.getMethod(
                "resurrectVerifiedLegacy", String.class, String.class, String.class, long.class);

        String currentSql = String.join(" ", current.getAnnotation(Select.class).value());
        String resurrectSql = String.join(" ", resurrect.getAnnotation(Update.class).value());

        assertTrue(currentSql.endsWith("FOR UPDATE"));
        assertTrue(resurrectSql.contains("ref_count=1"));
        assertTrue(resurrectSql.contains("ref_count=0"));
        assertTrue(resurrectSql.contains("storage_locator IS NULL"));
        assertTrue(resurrectSql.contains("content_hash=#{contentHash}"));
        assertTrue(resurrectSql.contains("file_path=#{filePath}"));
        assertTrue(resurrectSql.contains("size=#{size}"));
    }

    /**
     * 验证新内容直接以首个正式引用写入，并拒绝空 locator 或哈希不一致参数。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void insertActiveRequiresStableLocatorAndMatchingHash() throws Exception {
        Method insert = SkillContentMapper.class.getMethod("insertActive", String.class, String.class,
                String.class, String.class, String.class, long.class);
        String sql = String.join(" ", insert.getAnnotation(Insert.class).value());

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

    /**
     * 验证新释放流程在状态转换与索引删除时均精确匹配 locator。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void releaseMutationsMatchStorageLocatorExactly() throws Exception {
        Method mark = SkillContentMapper.class.getMethod(
                "markReleased", String.class, String.class, String.class);
        Method delete = SkillContentMapper.class.getMethod(
                "deleteReleased", String.class, String.class, String.class);

        String markSql = String.join(" ", mark.getAnnotation(Update.class).value());
        String deleteSql = String.join(" ", delete.getAnnotation(Delete.class).value());

        assertTrue(markSql.contains("storage_locator<=>#{storageLocator}"));
        assertTrue(deleteSql.contains("storage_locator<=>#{storageLocator}"));
        assertTrue(markSql.contains("file_path=#{filePath}"));
        assertTrue(deleteSql.contains("file_path=#{filePath}"));
    }

    /**
     * 验证所有返回内容实体的显式查询都读取 storage_locator。
     *
     * @throws Exception 反射读取方法失败
     */
    @Test
    public void contentEntityQueriesSelectStorageLocator() throws Exception {
        Method pending = SkillContentMapper.class.getMethod("findStalePending", java.util.Date.class, int.class);
        Method released = SkillContentMapper.class.getMethod("findReleasedBefore", java.util.Date.class, int.class);

        String pendingSql = String.join(" ", pending.getAnnotation(Select.class).value());
        assertTrue(pendingSql.contains("content_ref AS contentRef"));
        assertTrue(pendingSql.contains("content_hash AS contentHash"));
        assertTrue(pendingSql.contains("file_path AS filePath"));
        assertTrue(pendingSql.contains("storage_locator AS storageLocator"));
        assertTrue(pendingSql.contains("media_type AS mediaType"));
        assertTrue(pendingSql.contains("ref_count AS refCount"));
        String releasedSql = String.join(" ", released.getAnnotation(Select.class).value());
        assertTrue(releasedSql.contains("storage_locator AS storageLocator"));
        assertTrue(releasedSql.contains("storage_locator IS NOT NULL"));
        assertTrue(releasedSql.contains("storage_locator<>''"));
    }
}
