SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;

USE hyapp_wallet;

-- 装备幂等表是 append-only 小行写入；所有重放只按主键 (app_code,command_id) 点查，
-- 不修改 user_resource_equipment 主键，也不会为上线迁移扫描用户装备或 entitlement 大表。
CREATE TABLE IF NOT EXISTS user_resource_equipment_commands (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  command_id VARCHAR(128) NOT NULL COMMENT '装备业务幂等 ID',
  request_hash CHAR(64) NOT NULL COMMENT '用户、资源和指定 entitlement 的请求语义哈希',
  user_id BIGINT NOT NULL COMMENT '操作用户 ID',
  resource_id BIGINT NOT NULL COMMENT '目标资源 ID',
  requested_entitlement_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '客户端指定的 entitlement；空表示服务端选择',
  result_entitlement_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '首个成功命中的 entitlement',
  resource_type VARCHAR(32) NOT NULL DEFAULT '' COMMENT '首个成功结果的资源类型',
  response_json JSON NULL COMMENT '首个成功 UserResourceEntitlement 回包快照',
  created_at_ms BIGINT NOT NULL COMMENT '首次命令时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '结果完成时间，UTC epoch ms',
  PRIMARY KEY (app_code, command_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户资源装备命令幂等事实表';

-- 配置矩阵很小，历史表按 admin config_version 追加；装备和 appearance 只按资源覆盖索引点查。
-- 记录 program_type，避免 legacy_timed App 的普通资源被 tiered VIP 门禁误伤。
CREATE TABLE IF NOT EXISTS vip_benefit_resource_binding_history (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  history_id CHAR(64) NOT NULL COMMENT '配置版本、阶段、等级和权益的稳定哈希',
  config_version BIGINT NOT NULL COMMENT '写入该快照的 VIP 配置版本',
  change_phase VARCHAR(16) NOT NULL COMMENT 'baseline/before/after',
  program_type VARCHAR(32) NOT NULL COMMENT '快照时的 VIP program 类型',
  level INT NOT NULL COMMENT 'VIP 等级',
  benefit_code VARCHAR(64) NOT NULL COMMENT '权益编码',
  benefit_status VARCHAR(32) NOT NULL COMMENT '权益快照状态',
  trial_enabled BOOLEAN NOT NULL DEFAULT FALSE COMMENT '体验卡是否可用快照',
  resource_id BIGINT NOT NULL DEFAULT 0 COMMENT '当时绑定资源 ID；0 表示未绑定',
  resource_type VARCHAR(32) NOT NULL DEFAULT '' COMMENT '当时要求的资源类型',
  auto_equip BOOLEAN NOT NULL DEFAULT FALSE COMMENT '当时自动装备策略',
  recorded_at_ms BIGINT NOT NULL COMMENT '记录时间，UTC epoch ms',
  PRIMARY KEY (app_code, history_id),
  UNIQUE KEY uk_vip_binding_history_snapshot (app_code, config_version, change_phase, level, benefit_code),
  KEY idx_vip_binding_history_resource (app_code, resource_id, program_type, benefit_code, level)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='VIP 权益资源绑定不可变历史';

-- 装备门禁按单 App、单 resource_id 查询 active 绑定。该配置表规模远小于业务流水，
-- 仍显式补覆盖索引并使用在线 DDL，避免每次装备扫描完整权益矩阵。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.STATISTICS
   WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_level_benefits'
     AND INDEX_NAME = 'idx_vip_benefit_resource') = 0,
  'ALTER TABLE vip_level_benefits ADD INDEX idx_vip_benefit_resource (app_code, resource_id, status, benefit_code, level), ALGORITHM=INPLACE, LOCK=NONE',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @vip_resource_contract_now_ms := CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED);

-- 在任何修正规则执行前保存旧映射；后续 admin 更新还会为每个新版本分别追加 before/after。
INSERT IGNORE INTO vip_benefit_resource_binding_history (
  app_code, history_id, config_version, change_phase, program_type, level,
  benefit_code, benefit_status, trial_enabled, resource_id, resource_type,
  auto_equip, recorded_at_ms
)
SELECT b.app_code,
       SHA2(CONCAT_WS('|', b.app_code, p.config_version, 'baseline', b.level, b.benefit_code), 256),
       p.config_version, 'baseline', p.program_type, b.level, b.benefit_code,
       b.status, b.trial_enabled, b.resource_id, b.resource_type, b.auto_equip,
       @vip_resource_contract_now_ms
FROM vip_level_benefits AS b
JOIN vip_program_configs AS p ON p.app_code = b.app_code;

-- tiered Fami 的房间视觉权益使用稳定映射：mic_skin -> mic_seat_animation、
-- room_border -> room_border、colored_room_name -> room_name_style。三项都属于 decoration；
-- disabled 可以无素材，但若已有 resource_id 也不能保留错误类型或下架资源。
UPDATE vip_level_benefits
SET benefit_type = 'decoration',
    resource_type = CASE benefit_code
      WHEN 'mic_skin' THEN 'mic_seat_animation'
      WHEN 'room_border' THEN 'room_border'
      WHEN 'colored_room_name' THEN 'room_name_style'
    END,
    updated_at_ms = @vip_resource_contract_now_ms
WHERE app_code = 'fami'
  AND benefit_code IN ('mic_skin', 'room_border', 'colored_room_name')
  AND (
    benefit_type <> 'decoration'
    OR resource_type <> CASE benefit_code
      WHEN 'mic_skin' THEN 'mic_seat_animation'
      WHEN 'room_border' THEN 'room_border'
      WHEN 'colored_room_name' THEN 'room_name_style'
    END
  );

SET @vip_decoration_normalize_count := ROW_COUNT();

UPDATE vip_level_benefits AS benefit
LEFT JOIN resources AS resource
  ON resource.app_code = benefit.app_code
 AND resource.resource_id = benefit.resource_id
 AND resource.status = 'active'
 AND resource.resource_type = CASE benefit.benefit_code
   WHEN 'mic_skin' THEN 'mic_seat_animation'
   WHEN 'room_border' THEN 'room_border'
   WHEN 'colored_room_name' THEN 'room_name_style'
 END
SET benefit.resource_id = 0,
    benefit.updated_at_ms = @vip_resource_contract_now_ms
WHERE benefit.app_code = 'fami'
  AND benefit.benefit_code IN ('mic_skin', 'room_border', 'colored_room_name')
  AND benefit.resource_id > 0
  AND resource.resource_id IS NULL;

SET @vip_invalid_resource_clear_count := ROW_COUNT();

-- 本地现有 Fami VIP4..VIP8 资源组各自只有一个可核实的 active mic_seat_animation。
-- 这里按稳定 group_code + 类型解析 ID，并用 HAVING COUNT(DISTINCT)=1 防止运营新增候选后误绑；
-- VIP9、room_border、room_name_style 当前没有真实目录资源，必须保持未配置，不能伪造 ID/URL。
UPDATE vip_level_benefits AS benefit
JOIN (
  SELECT resource_group.group_code, MIN(resource.resource_id) AS resource_id
  FROM resource_groups AS resource_group
  JOIN resource_group_items AS group_item
    ON group_item.app_code = resource_group.app_code
   AND group_item.group_id = resource_group.group_id
   AND group_item.item_type = 'resource'
  JOIN resources AS resource
    ON resource.app_code = group_item.app_code
   AND resource.resource_id = group_item.resource_id
   AND resource.resource_type = 'mic_seat_animation'
   AND resource.status = 'active'
  WHERE resource_group.app_code = 'fami'
    AND resource_group.status = 'active'
    AND resource_group.group_code IN ('VIP4', 'VIP5', 'VIP6', 'VIP7', 'VIP8')
  GROUP BY resource_group.group_code
  HAVING COUNT(DISTINCT resource.resource_id) = 1
) AS mapping
  ON mapping.group_code = CONCAT('VIP', benefit.level)
SET benefit.resource_id = mapping.resource_id,
    benefit.resource_type = 'mic_seat_animation',
    benefit.updated_at_ms = @vip_resource_contract_now_ms
WHERE benefit.app_code = 'fami'
  AND benefit.level BETWEEN 4 AND 8
  AND benefit.benefit_code = 'mic_skin'
  AND benefit.status = 'active'
  AND benefit.resource_id = 0;

SET @vip_mic_skin_bind_count := ROW_COUNT();

-- 只给“已经被 active mic_skin 绑定”的真实麦座动画补显式 format：先信任后台已有的
-- 合法 animation_format，否则只从实际 animation/asset URL 的明确后缀推导。这个更新
-- 通过权益配置小表圈定 resource_id，再按 resources 主键点查，不会扫描或修改未绑定资源。
UPDATE vip_level_benefits AS benefit
JOIN resources AS resource FORCE INDEX (PRIMARY)
  ON resource.resource_id = benefit.resource_id
 AND resource.app_code = benefit.app_code
 AND resource.resource_type = 'mic_seat_animation'
 AND resource.status = 'active'
SET resource.metadata_json = JSON_SET(
      COALESCE(resource.metadata_json, JSON_OBJECT()),
      '$.format',
      CASE
        WHEN LOWER(TRIM(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(resource.metadata_json, '$.animation_format')), '')))
               IN ('png', 'jpeg', 'webp', 'gif', 'svga', 'pag', 'mp4')
          THEN LOWER(TRIM(JSON_UNQUOTE(JSON_EXTRACT(resource.metadata_json, '$.animation_format'))))
        WHEN LOWER(SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(resource.animation_url), '?', 1), '#', 1))
               REGEXP '\\.(png|jpeg|jpg|webp|gif|svga|pag|mp4)$'
          THEN CASE LOWER(SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(resource.animation_url), '?', 1), '#', 1), '.', -1))
            WHEN 'jpg' THEN 'jpeg'
            ELSE LOWER(SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(resource.animation_url), '?', 1), '#', 1), '.', -1))
          END
        ELSE CASE LOWER(SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(resource.asset_url), '?', 1), '#', 1), '.', -1))
          WHEN 'jpg' THEN 'jpeg'
          ELSE LOWER(SUBSTRING_INDEX(SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(resource.asset_url), '?', 1), '#', 1), '.', -1))
        END
      END
    ),
    resource.updated_at_ms = @vip_resource_contract_now_ms
WHERE benefit.app_code = 'fami'
  AND benefit.benefit_code = 'mic_skin'
  AND benefit.status = 'active'
  AND benefit.resource_type = 'mic_seat_animation'
  AND benefit.resource_id > 0
  AND (TRIM(resource.asset_url) <> '' OR TRIM(resource.animation_url) <> '')
  AND TRIM(resource.preview_url) <> ''
  AND LOWER(TRIM(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(resource.metadata_json, '$.format')), '')))
        NOT IN ('png', 'jpeg', 'webp', 'gif', 'svga', 'pag', 'mp4')
  AND (
    LOWER(TRIM(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(resource.metadata_json, '$.animation_format')), '')))
      IN ('png', 'jpeg', 'webp', 'gif', 'svga', 'pag', 'mp4')
    OR LOWER(SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(resource.animation_url), '?', 1), '#', 1))
      REGEXP '\\.(png|jpeg|jpg|webp|gif|svga|pag|mp4)$'
    OR LOWER(SUBSTRING_INDEX(SUBSTRING_INDEX(TRIM(resource.asset_url), '?', 1), '#', 1))
      REGEXP '\\.(png|jpeg|jpg|webp|gif|svga|pag|mp4)$'
  );

SET @vip_mic_format_backfill_count := ROW_COUNT();

-- active 必须能联结到同 App、active、精确类型的真实资源。麦座动画和房间边框还必须
-- 同时具备素材、preview、合法 format，且可选 animation_format 一旦配置就必须一致。
-- 缺少真实配置时只禁用权益，不创建占位资源，也不猜测素材 URL。
UPDATE vip_level_benefits AS benefit
LEFT JOIN resources AS resource
  ON resource.app_code = benefit.app_code
 AND resource.resource_id = benefit.resource_id
 AND resource.status = 'active'
 AND resource.resource_type = benefit.resource_type
SET benefit.status = 'disabled',
    benefit.updated_at_ms = @vip_resource_contract_now_ms
WHERE benefit.app_code = 'fami'
  AND benefit.benefit_code IN ('mic_skin', 'room_border', 'colored_room_name')
  AND benefit.status = 'active'
  AND (
    resource.resource_id IS NULL
    OR (
      benefit.resource_type IN ('mic_seat_animation', 'room_border')
      AND (
        (TRIM(resource.asset_url) = '' AND TRIM(resource.animation_url) = '')
        OR TRIM(resource.preview_url) = ''
        OR LOWER(TRIM(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(resource.metadata_json, '$.format')), '')))
          NOT IN ('png', 'jpeg', 'webp', 'gif', 'svga', 'pag', 'mp4')
        OR (
          LOWER(TRIM(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(resource.metadata_json, '$.animation_format')), ''))) <> ''
          AND LOWER(TRIM(JSON_UNQUOTE(JSON_EXTRACT(resource.metadata_json, '$.animation_format'))))
            <> LOWER(TRIM(JSON_UNQUOTE(JSON_EXTRACT(resource.metadata_json, '$.format'))))
        )
      )
    )
  );

SET @vip_decoration_disable_count := ROW_COUNT();

-- config_version 只在首次真实回填时递增；迁移重复执行不会制造无意义版本变化。
UPDATE vip_program_configs
SET config_version = config_version + 1,
    updated_at_ms = @vip_resource_contract_now_ms
WHERE app_code = 'fami'
  AND (
    @vip_decoration_normalize_count
    + @vip_invalid_resource_clear_count
    + @vip_mic_skin_bind_count
    + @vip_mic_format_backfill_count
    + @vip_decoration_disable_count
  ) > 0;

-- 本次迁移若提升了 config_version，立即写入新版本的最终 baseline。这样下一次重放开头的
-- baseline INSERT 会命中唯一键，不会仅因为上次版本递增再追加一轮完全相同的历史快照。
INSERT IGNORE INTO vip_benefit_resource_binding_history (
  app_code, history_id, config_version, change_phase, program_type, level,
  benefit_code, benefit_status, trial_enabled, resource_id, resource_type,
  auto_equip, recorded_at_ms
)
SELECT b.app_code,
       SHA2(CONCAT_WS('|', b.app_code, p.config_version, 'baseline', b.level, b.benefit_code), 256),
       p.config_version, 'baseline', p.program_type, b.level, b.benefit_code,
       b.status, b.trial_enabled, b.resource_id, b.resource_type, b.auto_equip,
       @vip_resource_contract_now_ms
FROM vip_level_benefits AS b
JOIN vip_program_configs AS p ON p.app_code = b.app_code;
