package tech.easyflow.skill.mapper;

import com.easyagents.skill.util.SkillHashes;
import org.junit.Test;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

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

/**
 * V27 旧 Skill 数据迁移的失败前置与摘要初值契约测试。
 */
public class SkillMigrationGuardContractTest {

    /**
     * 验证分类重复和旧资源冲突检查均位于业务表 DDL 之前。
     *
     * @throws Exception 读取迁移文件失败
     */
    @Test
    public void dataGuardsShouldRunBeforePersistentDdl() throws Exception {
        String sql = migrationSql();
        int firstPersistentDdl = sql.indexOf("ALTER TABLE `tb_skill`");

        assertTrue(firstPersistentDdl > 0);
        assertTrue(sql.indexOf("tmp_skill_category_migration_guard") < firstPersistentDdl);
        assertTrue(sql.indexOf("HAVING COUNT(1) > 1") < firstPersistentDdl);
        assertTrue(sql.indexOf("tmp_skill_resource_owner_guard") < firstPersistentDdl);
        assertTrue(sql.indexOf("tmp_skill_content_migration_guard") < firstPersistentDdl);
        assertTrue(sql.indexOf("tmp_skill_resource_migration_source") < firstPersistentDdl);
        assertTrue(sql.indexOf("UNION ALL") < firstPersistentDdl);
    }

    /**
     * 验证旧 Skill 的空能力配置 hash 与运行时算法一致，且迁移不改业务审计列。
     *
     * @throws Exception 读取迁移文件失败
     */
    @Test
    public void emptyCapabilityHashShouldMatchRuntimeCanonicalValue() throws Exception {
        String sql = migrationSql();
        String emptyHash = SkillHashes.sha256Hex("[]".getBytes(StandardCharsets.UTF_8));

        assertTrue(sql.contains("`capability_hash` = '" + emptyHash + "'"));
        assertTrue(sql.contains("`modified` = `modified`"));
        assertTrue(sql.contains("`modified_by` = `modified_by`"));
    }

    /**
     * 验证旧资源迁移不会使用 INSERT IGNORE 静默吞掉冲突数据。
     *
     * @throws Exception 读取迁移文件失败
     */
    @Test
    public void resourceMigrationShouldNeverSilentlyIgnoreConflicts() throws Exception {
        String sql = migrationSql();

        assertFalse(sql.contains("INSERT IGNORE INTO `tb_skill_resource`"));
        assertFalse(sql.contains("INSERT IGNORE INTO `tb_skill_content`"));
        assertTrue(sql.contains("tmp_skill_target_migration_guard"));
        assertTrue(sql.contains("WHERE NOT EXISTS ("));
        assertTrue(sql.contains("information_schema.statistics"));
        assertFalse(sql.contains("ADD COLUMN IF NOT EXISTS"));
    }

    /**
     * 验证旧文本 hash、二进制大小与引用数都从可验证真相源修复。
     *
     * @throws Exception 读取迁移文件失败
     */
    @Test
    public void legacyContentSummaryShouldBeRepairedFromCanonicalSources() throws Exception {
        String sql = migrationSql();

        assertTrue(sql.contains("LOWER(SHA2(COALESCE(reference.`content`, ''), 256))"));
        assertTrue(sql.contains("LOWER(SHA2(COALESCE(script.`content`, ''), 256))"));
        assertTrue(sql.contains("content.`content_hash`,"));
        assertTrue(sql.contains("SELECT MAX(asset.`size`)"));
        assertTrue(sql.contains("SELECT COUNT(1) FROM `tb_skill_asset` asset"));
        assertTrue(sql.contains("tmp_skill_snapshot_content_ref"));
        assertTrue(sql.contains("+ COALESCE((SELECT snapshot_ref.`ref_count`"));
        assertFalse(sql.contains("GREATEST(COALESCE(content.`ref_count`, 0)"));
    }

    /**
     * 验证新旧快照字段同时存在时优先读取 resources，避免兼容字段重复计数。
     *
     * @throws Exception 读取迁移文件失败
     */
    @Test
    public void snapshotResourcesShouldTakePrecedenceOverLegacyAssets() throws Exception {
        String sql = migrationSql();

        assertTrue(sql.contains("JSON_EXTRACT(skill.`published_snapshot_json`, '$.resources')"));
        assertTrue(sql.contains("JSON_EXTRACT(approval.`snapshot_json`, '$.resourceSnapshot.resources')"));
        assertTrue(sql.contains("approval.`status` IN ('PENDING', 'PROCESSING')"));
        assertTrue(sql.contains("<> 'ARRAY'"));
        assertTrue(sql.contains("= 'ARRAY'"));
    }

    /**
     * 验证迁移后所有资源可被租户查询，历史计数与根分类语义也同步归一。
     *
     * @throws Exception 读取迁移文件失败
     */
    @Test
    public void tenantCountsAndRootCategoryShouldBeNormalizedWithoutAuditPollution() throws Exception {
        String sql = migrationSql();

        assertTrue(sql.contains("COALESCE(reference.`tenant_id`, skill.`tenant_id`)"));
        assertTrue(sql.contains("COALESCE(script.`tenant_id`, skill.`tenant_id`)"));
        assertTrue(sql.contains("COALESCE(asset.`tenant_id`, skill.`tenant_id`)"));
        assertTrue(sql.contains("`tenant_id` BIGINT NOT NULL COMMENT '租户ID'"));
        assertTrue(sql.contains("resource.`tenant_id` <> skill.`tenant_id`"));
        assertTrue(sql.contains("SET `parent_id` = NULL, `modified` = `modified`, `modified_by` = `modified_by`"));
        assertTrue(sql.contains("`reference_count` = (SELECT COUNT(1)"));
        assertTrue(sql.contains("`script_count` = (SELECT COUNT(1)"));
        assertTrue(sql.contains("`asset_count` = (SELECT COUNT(1)"));
    }

    /**
     * 读取工作区中的 V27 MySQL 迁移。
     *
     * @return 迁移 SQL
     * @throws Exception 迁移文件不存在或不可读
     */
    private String migrationSql() throws Exception {
        Path root = Path.of(System.getProperty("maven.multiModuleProjectDirectory",
                Path.of(System.getProperty("user.dir")).toAbsolutePath().toString()));
        while (root != null) {
            Path migration = root.resolve("easyflow-starter/easyflow-starter-all/src/main/resources/"
                    + "db/migration/mysql/V27__mysql_skill_resource_capability.sql");
            if (Files.isRegularFile(migration)) {
                return Files.readString(migration, StandardCharsets.UTF_8);
            }
            root = root.getParent();
        }
        throw new IllegalStateException("未找到 V27 Skill MySQL 迁移文件");
    }
}
