SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;


-- 使用 utf8mb4 连接字符集，保证中文注释、中文名称和 emoji 在导入时不被降级。
CREATE DATABASE IF NOT EXISTS hyapp_user DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE hyapp_user;

CREATE TABLE IF NOT EXISTS apps (
  app_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '应用 ID',
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  app_name VARCHAR(128) NOT NULL COMMENT '应用名称',
  logo_url VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '应用 Logo 的公网访问地址',
  package_name VARCHAR(191) NOT NULL COMMENT '包名称',
  platform VARCHAR(32) NOT NULL DEFAULT '' COMMENT '平台',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  UNIQUE KEY uk_apps_code (app_code),
  UNIQUE KEY uk_apps_package_platform (package_name, platform),
  KEY idx_apps_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='应用注册表';

INSERT INTO apps (app_code, app_name, logo_url, package_name, platform, status, created_at_ms, updated_at_ms)
VALUES
  ('lalu', 'Lalu', 'https://media.haiyihy.com/admin/apps/logos/lalu.png', 'com.org.laluparty', '', 'active', 0, 0),
  ('huwaa', 'Huwaa', 'https://media.haiyihy.com/admin/apps/logos/huwaa.png', 'com.app.huwaa', '', 'active', 0, 0),
  ('fami', 'Fami', 'https://media.haiyihy.com/admin/apps/logos/fami.png', 'com.chat.fami', '', 'active', 0, 0),
  ('yumi', 'Yumi', 'https://media.haiyihy.com/admin/apps/logos/yumi.png', 'com.org.yumi', '', 'active', 0, 0)
ON DUPLICATE KEY UPDATE
  app_name = VALUES(app_name),
  logo_url = VALUES(logo_url),
  package_name = VALUES(package_name),
  platform = VALUES(platform),
  status = VALUES(status),
  updated_at_ms = VALUES(updated_at_ms);

CREATE TABLE IF NOT EXISTS auth_risk_configs (
  app_code VARCHAR(32) NOT NULL PRIMARY KEY COMMENT '应用编码，用于多租户隔离',
  max_accounts_per_device INT NOT NULL DEFAULT 1 COMMENT '单设备最大注册账号数',
  updated_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '最近修改后台管理员 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='认证注册风控配置';

INSERT INTO auth_risk_configs (app_code, max_accounts_per_device, updated_by_admin_id, created_at_ms, updated_at_ms)
SELECT app_code, 1, 0, 0, 0
FROM apps
ON DUPLICATE KEY UPDATE
  max_accounts_per_device = max_accounts_per_device;

CREATE TABLE IF NOT EXISTS role_scope_policies (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  scene VARCHAR(64) NOT NULL COMMENT '角色范围业务场景',
  base_scope VARCHAR(16) NOT NULL COMMENT '默认范围：country/region/global',
  expanded_scope VARCHAR(16) NOT NULL COMMENT '扩展开关开启后的范围',
  region_expansion_configurable BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否允许经理个人扩区开关改变范围',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, scene)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='App 角色范围 owner 配置';

-- 旧版跨区能力只通过运行时代码存在；初始数据仅固化已有行为，后续一律由后台写 owner 表。
INSERT IGNORE INTO role_scope_policies (
  app_code, scene, base_scope, expanded_scope,
  region_expansion_configurable, created_at_ms, updated_at_ms
) VALUES
  ('huwaa', 'organization_role_expansion', 'global', 'global', FALSE, 0, 0),
  ('huwaa', 'manager_operations', 'global', 'global', FALSE, 0, 0);

CREATE TABLE IF NOT EXISTS users (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID',
  default_display_user_id VARCHAR(64) NOT NULL COMMENT '默认展示用户 ID',
  current_display_user_id VARCHAR(64) NOT NULL COMMENT '当前展示用户 ID',
  current_display_user_id_kind VARCHAR(32) NOT NULL COMMENT '当前展示用户 ID 类型',
  current_display_user_id_expires_at_ms BIGINT NULL COMMENT '当前展示用户 ID 过期时间，UTC epoch ms',
  username VARCHAR(64) NULL COMMENT '用户名',
  gender VARCHAR(32) NULL COMMENT '性别',
  country VARCHAR(64) NULL COMMENT '国家',
  region_id BIGINT NULL COMMENT '区域 ID',
  invite_code VARCHAR(64) NULL COMMENT '邀请编码',
  register_ip VARCHAR(64) NULL COMMENT '注册IP',
  register_user_agent VARCHAR(512) NULL COMMENT '注册用户代理',
  country_by_ip VARCHAR(64) NULL COMMENT 'IP 解析国家',
  register_device_id VARCHAR(128) NULL COMMENT '注册设备 ID',
  register_device VARCHAR(128) NULL COMMENT '注册设备',
  os_version VARCHAR(64) NULL COMMENT 'os版本',
  avatar VARCHAR(512) NULL COMMENT '头像',
  profile_bg_img VARCHAR(512) NOT NULL DEFAULT '' COMMENT '个人主页信息卡背景图',
  contact_info VARCHAR(128) NOT NULL DEFAULT '' COMMENT '用户联系信息',
  withdraw_usdt_trc20_address VARCHAR(128) NOT NULL DEFAULT '' COMMENT '提现 USDT-TRC20 地址',
  birth_date DATE NULL COMMENT '出生日期',
  app_version VARCHAR(64) NULL COMMENT '应用版本',
  build_number VARCHAR(64) NULL COMMENT '构建编号',
  source VARCHAR(64) NULL COMMENT '来源',
  install_channel VARCHAR(64) NULL COMMENT '安装渠道',
  campaign VARCHAR(128) NULL COMMENT '投放活动',
  platform VARCHAR(16) NULL COMMENT '平台',
  language VARCHAR(32) NULL COMMENT '语言',
  timezone VARCHAR(64) NULL COMMENT '时区',
  profile_completed TINYINT(1) NOT NULL DEFAULT 0 COMMENT '资料完成',
  profile_completed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '资料完成时间，UTC epoch ms',
  onboarding_status VARCHAR(32) NOT NULL DEFAULT 'profile_required' COMMENT '新手引导状态',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  UNIQUE KEY uk_users_default_display_user_id (app_code, default_display_user_id),
  UNIQUE KEY uk_users_current_display_user_id (app_code, current_display_user_id),
  KEY idx_users_register_device_id (app_code, register_device_id),
  KEY idx_users_status (app_code, status),
  KEY idx_users_region_status (app_code, region_id, status),
  KEY idx_users_country_region (app_code, country, region_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户主数据表';

CREATE TABLE IF NOT EXISTS user_avatar_upload_registrations (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL COMMENT '上传用户 ID',
  avatar_upload_id VARCHAR(64) NOT NULL COMMENT '资料更新消费凭证',
  client_command_id VARCHAR(128) NOT NULL COMMENT '客户端上传幂等键',
  media_payload_sha256 CHAR(64) NOT NULL COMMENT '规范化媒体事实摘要',
  expected_object_key VARCHAR(512) NOT NULL COMMENT 'user-service 授权的 COS 对象键',
  object_url VARCHAR(1024) NOT NULL DEFAULT '' COMMENT 'COS 上传完成后的受信访问地址',
  status VARCHAR(16) NOT NULL COMMENT 'authorized/active/consumed',
  content_type VARCHAR(64) NOT NULL COMMENT '按魔数识别的 MIME',
  media_format VARCHAR(16) NOT NULL COMMENT 'jpeg/png/gif/webp',
  size_bytes BIGINT NOT NULL COMMENT '原始文件字节数',
  width INT NOT NULL COMMENT '画布宽度',
  height INT NOT NULL COMMENT '画布高度',
  animated TINYINT(1) NOT NULL COMMENT '是否多帧动画',
  frame_count INT NOT NULL COMMENT '帧数',
  duration_ms BIGINT NOT NULL COMMENT '动画总时长，静态为 0',
  sha256 CHAR(64) NOT NULL COMMENT '原始文件 SHA-256',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, user_id, avatar_upload_id),
  UNIQUE KEY uk_user_avatar_upload_command (app_code, user_id, client_command_id),
  UNIQUE KEY uk_user_avatar_upload_object (app_code, expected_object_key),
  KEY idx_user_avatar_upload_status_time (app_code, status, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户头像上传授权与消费记录';

CREATE TABLE IF NOT EXISTS user_profile_stats (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  visitors_count BIGINT NOT NULL DEFAULT 0 COMMENT '访客数量',
  following_count BIGINT NOT NULL DEFAULT 0 COMMENT '关注中数量',
  followers_count BIGINT NOT NULL DEFAULT 0 COMMENT '粉丝数量',
  friends_count BIGINT NOT NULL DEFAULT 0 COMMENT '好友数量',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户资料统计表';

CREATE TABLE IF NOT EXISTS user_profile_visits (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  target_user_id BIGINT NOT NULL COMMENT '目标用户 ID',
  visitor_user_id BIGINT NOT NULL COMMENT '访客用户 ID',
  visit_count BIGINT NOT NULL DEFAULT 1 COMMENT '访问数量',
  first_visited_at_ms BIGINT NOT NULL COMMENT '首次访问时间，UTC epoch ms',
  last_visited_at_ms BIGINT NOT NULL COMMENT '最后访问时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, target_user_id, visitor_user_id),
  KEY idx_user_profile_visits_visitor (app_code, visitor_user_id, last_visited_at_ms),
  KEY idx_user_profile_visits_target_time (app_code, target_user_id, last_visited_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户资料访问表';

CREATE TABLE IF NOT EXISTS user_profile_anonymous_visits (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  target_user_id BIGINT NOT NULL COMMENT '目标用户 ID',
  visitor_user_id BIGINT NOT NULL COMMENT '仅 user-service 内部用于聚合的真实访客 ID，禁止对外返回',
  anonymous_visit_id VARCHAR(64) NOT NULL COMMENT '首次匿名访问生成的随机展示 ID，不包含真实用户信息',
  visit_count BIGINT NOT NULL DEFAULT 1 COMMENT '匿名阶段累计访问数量',
  first_visited_at_ms BIGINT NOT NULL COMMENT '首次匿名访问时间，UTC epoch ms',
  last_visited_at_ms BIGINT NOT NULL COMMENT '最后匿名访问时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, target_user_id, visitor_user_id),
  UNIQUE KEY uk_user_profile_anonymous_visit_id (app_code, target_user_id, anonymous_visit_id),
  KEY idx_user_profile_anonymous_visits_target_time (app_code, target_user_id, last_visited_at_ms, anonymous_visit_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户资料历史匿名访问兼容表，新匿名访问不再写入';

CREATE TABLE IF NOT EXISTS user_follows (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  follower_user_id BIGINT NOT NULL COMMENT '关注人用户 ID',
  followee_user_id BIGINT NOT NULL COMMENT '被关注人用户 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  followed_at_ms BIGINT NOT NULL COMMENT '关注时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, follower_user_id, followee_user_id),
  KEY idx_user_follows_followee_status (app_code, followee_user_id, status, updated_at_ms),
  KEY idx_user_follows_follower_status (app_code, follower_user_id, status, followed_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户关注表';

CREATE TABLE IF NOT EXISTS user_follow_events (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  event_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '关注状态变化事件 ID',
  follower_user_id BIGINT NOT NULL COMMENT '关注人用户 ID',
  followee_user_id BIGINT NOT NULL COMMENT '被关注人用户 ID',
  delta TINYINT NOT NULL COMMENT '关注 +1，取消关注 -1',
  occurred_at_ms BIGINT NOT NULL COMMENT '事件时间，UTC epoch ms',
  PRIMARY KEY (event_id),
  KEY idx_user_follow_events_followee_time (app_code, followee_user_id, occurred_at_ms, event_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户关注净增统计事件表';

CREATE TABLE IF NOT EXISTS user_private_message_events (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  event_id VARCHAR(128) NOT NULL COMMENT '腾讯 IM 私信事件幂等 ID',
  sender_user_id BIGINT NOT NULL COMMENT '消息发送用户 ID',
  target_user_id BIGINT NOT NULL COMMENT '消息接收用户 ID',
  occurred_at_ms BIGINT NOT NULL COMMENT '发送时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '落库时间，UTC epoch ms',
  PRIMARY KEY (app_code, event_id),
  KEY idx_private_message_target_time (app_code, target_user_id, occurred_at_ms, sender_user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户私信发送事实，仅保存统计字段';

CREATE TABLE IF NOT EXISTS user_private_message_history_sync (
  sdk_app_id BIGINT NOT NULL COMMENT '腾讯 IM SDKAppID',
  hour_start_ms BIGINT NOT NULL COMMENT '待同步 UTC 小时起点 epoch ms',
  status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/processing/retry/completed',
  attempts INT NOT NULL DEFAULT 0 COMMENT '外部归档拉取尝试次数',
  next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '失败后最早重试时间',
  locked_by VARCHAR(96) NOT NULL DEFAULT '' COMMENT '当前 cron run ID',
  locked_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '处理租约截止时间',
  source_message_count INT NOT NULL DEFAULT 0 COMMENT '归档原始消息数',
  imported_event_count INT NOT NULL DEFAULT 0 COMMENT '新增 Host 私信事实数',
  last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最近一次脱敏错误',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (sdk_app_id, hour_start_ms),
  KEY idx_private_message_history_sync_ready (sdk_app_id, status, next_retry_at_ms, hour_start_ms, locked_until_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='腾讯 IM 小时归档离线同步状态';

CREATE TABLE IF NOT EXISTS user_friend_applications (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  requester_user_id BIGINT NOT NULL COMMENT '申请人用户 ID',
  target_user_id BIGINT NOT NULL COMMENT '目标用户 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, requester_user_id, target_user_id),
  KEY idx_friend_applications_target_status (app_code, target_user_id, status, updated_at_ms),
  KEY idx_friend_applications_requester_status (app_code, requester_user_id, status, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户好友申请表';

CREATE TABLE IF NOT EXISTS user_friendships (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  friend_user_id BIGINT NOT NULL COMMENT '好友用户 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  friended_at_ms BIGINT NOT NULL COMMENT '成为好友时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, user_id, friend_user_id),
  KEY idx_user_friendships_user_status (app_code, user_id, status, friended_at_ms),
  KEY idx_user_friendships_friend_status (app_code, friend_user_id, status, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户好友关系表';

CREATE TABLE IF NOT EXISTS user_status_change_logs (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID',
  target_user_id BIGINT NOT NULL COMMENT '目标用户 ID',
  old_status VARCHAR(32) NOT NULL COMMENT '原状态',
  new_status VARCHAR(32) NOT NULL COMMENT '新状态',
  operator_type VARCHAR(32) NOT NULL COMMENT '操作人类型',
  operator_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '操作人用户 ID',
  reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '原因',
  request_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '链路请求 ID，用于排查问题',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  KEY idx_user_status_logs_target (app_code, target_user_id, created_at_ms),
  KEY idx_user_status_logs_status (app_code, new_status, created_at_ms),
  KEY idx_user_status_logs_operator (app_code, operator_type, operator_user_id, created_at_ms),
  KEY idx_user_status_logs_request (app_code, request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户状态变更日志表';

CREATE TABLE IF NOT EXISTS manager_user_blocks (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  block_id VARCHAR(96) NOT NULL COMMENT '经理封禁记录 ID',
  command_id VARCHAR(128) NOT NULL COMMENT '幂等命令 ID',
  manager_user_id BIGINT NOT NULL COMMENT '经理用户 ID',
  target_user_id BIGINT NOT NULL COMMENT '被封禁用户 ID',
  target_display_user_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '被封禁用户短 ID 快照',
  target_username VARCHAR(128) NOT NULL DEFAULT '' COMMENT '被封禁用户昵称快照',
  target_avatar VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '被封禁用户头像快照',
  country VARCHAR(16) NOT NULL DEFAULT '' COMMENT '封禁时双方国家码',
  target_old_status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '封禁前用户状态',
  blocked_until_ms BIGINT NOT NULL COMMENT '计划解封时间，UTC epoch ms',
  status VARCHAR(32) NOT NULL COMMENT 'active/released/expired',
  reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '封禁原因',
  released_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '释放原因',
  released_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '释放时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, block_id),
  UNIQUE KEY uk_manager_user_blocks_command (app_code, command_id),
  KEY idx_manager_user_blocks_manager (app_code, manager_user_id, status, created_at_ms),
  KEY idx_manager_user_blocks_target (app_code, target_user_id, status),
  KEY idx_manager_user_blocks_expire (app_code, status, blocked_until_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='经理中心用户封禁记录表';

CREATE TABLE IF NOT EXISTS admin_user_bans (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  ban_id VARCHAR(96) NOT NULL COMMENT '管理员封禁事实 ID',
  command_id VARCHAR(128) NOT NULL COMMENT '后台请求幂等命令 ID',
  target_user_id BIGINT NOT NULL COMMENT '被封禁用户 ID',
  target_old_status VARCHAR(32) NOT NULL COMMENT '创建事实前的用户状态，仅供审计',
  expires_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '到期时间，UTC epoch ms；0 表示永久',
  status VARCHAR(32) NOT NULL COMMENT 'active/released/expired',
  reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '封禁原因',
  operator_admin_id BIGINT NOT NULL COMMENT '创建封禁的后台管理员 ID',
  released_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '手动解封管理员 ID；系统到期为 0',
  released_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '释放原因',
  released_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '释放时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, ban_id),
  UNIQUE KEY uk_admin_user_bans_command (app_code, command_id),
  KEY idx_admin_user_bans_target (app_code, target_user_id, status, created_at_ms),
  KEY idx_admin_user_bans_expire (app_code, status, expires_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台管理员用户封禁事实表';

CREATE TABLE IF NOT EXISTS user_content_blacklist (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL COMMENT '被屏蔽用户 ID',
  status VARCHAR(16) NOT NULL COMMENT 'active/removed',
  operator_admin_id BIGINT NOT NULL COMMENT '最近操作后台管理员 ID',
  reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '最近操作原因',
  created_at_ms BIGINT NOT NULL COMMENT '本次进入黑名单时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '最近操作时间，UTC epoch ms',
  PRIMARY KEY (app_code, user_id),
  KEY idx_user_content_blacklist_status_time (app_code, status, created_at_ms, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户运营内容屏蔽当前态';

-- 空 ban_id 解封会按用户选择最新 active 管理员事实；独立命令表把一次选择固定到 resolved_ban_id，
-- 避免网络重试继续释放下一条重叠封禁。
CREATE TABLE IF NOT EXISTS admin_user_ban_release_commands (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  command_id VARCHAR(128) NOT NULL COMMENT '稳定解封命令 ID，通常取 request_id',
  target_user_id BIGINT NOT NULL COMMENT '目标用户 ID',
  requested_ban_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '调用方指定 ban_id；空表示选择最新 active 管理员封禁',
  resolved_ban_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '本命令实际释放的 ban_id',
  release_status VARCHAR(32) NOT NULL COMMENT 'released/expired',
  operator_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '手动操作管理员 ID；系统到期为 0',
  reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '释放原因快照，用于校验幂等载荷',
  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),
  KEY idx_admin_ban_release_resolved (app_code, resolved_ban_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台管理员解封命令幂等事实表';

-- 后台强制解封会同时释放管理员和经理封禁；独立命令事实保证 gRPC 超时重试不会释放操作完成后新产生的封禁。
CREATE TABLE IF NOT EXISTS admin_user_force_unban_commands (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  command_id VARCHAR(128) NOT NULL COMMENT '稳定强制解封命令 ID，取 request_id',
  target_user_id BIGINT NOT NULL COMMENT '目标用户 ID',
  operator_admin_id BIGINT NOT NULL COMMENT '执行强制解封的后台管理员 ID',
  reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '管理员解封原因快照，用于校验幂等载荷',
  released_admin_ban_count BIGINT NOT NULL DEFAULT 0 COMMENT '本命令释放的管理员封禁数量',
  released_manager_block_count BIGINT NOT NULL DEFAULT 0 COMMENT '本命令释放的经理封禁数量',
  old_status VARCHAR(32) NOT NULL DEFAULT '' COMMENT '首次执行前用户主状态',
  new_status VARCHAR(32) NOT NULL DEFAULT '' COMMENT '首次执行后用户主状态',
  status_restored BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否把 banned/disabled 恢复为 active',
  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),
  KEY idx_admin_force_unban_target (app_code, target_user_id, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='后台管理员强制解封幂等事实表';

CREATE TABLE IF NOT EXISTS user_reports (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  report_id VARCHAR(96) NOT NULL COMMENT '举报单业务 ID',
  reporter_user_id BIGINT NOT NULL COMMENT '举报发起人用户 ID',
  target_type VARCHAR(16) NOT NULL COMMENT '举报目标类型：user/room',
  target_user_id BIGINT NULL COMMENT '被举报用户 ID',
  room_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '被举报房间 ID',
  report_type VARCHAR(64) NOT NULL COMMENT '举报类型',
  reason VARCHAR(512) NOT NULL DEFAULT '' COMMENT '举报理由',
  image_urls_json JSON NOT NULL COMMENT '举报图片 URL 数组',
  status VARCHAR(32) NOT NULL COMMENT '处理状态',
  request_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '链路请求 ID，用于排查问题',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, report_id),
  KEY idx_user_reports_reporter (app_code, reporter_user_id, created_at_ms),
  KEY idx_user_reports_target_user (app_code, target_user_id, created_at_ms),
  KEY idx_user_reports_room (app_code, room_id, created_at_ms),
  KEY idx_user_reports_status (app_code, status, created_at_ms),
  KEY idx_user_reports_request (app_code, request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户举报表';

CREATE TABLE IF NOT EXISTS countries (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  country_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '国家 ID',
  country_name VARCHAR(128) NOT NULL COMMENT '国家名称',
  country_code VARCHAR(3) NOT NULL COMMENT '国家或地区编码',
  iso_alpha3 VARCHAR(3) NULL COMMENT 'ISO三字码',
  iso_numeric CHAR(3) NULL COMMENT 'ISO数字',
  country_display_name VARCHAR(128) NOT NULL COMMENT '国家展示名称',
  phone_country_code VARCHAR(8) NULL COMMENT '电话国家编码',
  flag VARCHAR(16) NOT NULL DEFAULT '' COMMENT '国旗',
  enabled BOOLEAN NOT NULL DEFAULT TRUE COMMENT '启用',
  sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重，数值越大越靠前',
  created_by_user_id BIGINT NULL COMMENT '创建人用户 ID',
  updated_by_user_id BIGINT NULL COMMENT '更新人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  UNIQUE KEY uk_countries_code (app_code, country_code),
  UNIQUE KEY uk_countries_iso_numeric (app_code, iso_numeric),
  KEY idx_countries_phone_country_code (app_code, phone_country_code),
  KEY idx_countries_app_enabled_sort (app_code, enabled, sort_order, country_code),
  KEY idx_countries_sort (app_code, sort_order, country_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='国家表';

CREATE TABLE IF NOT EXISTS regions (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  region_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '区域 ID',
  region_code VARCHAR(64) NOT NULL COMMENT '区域编码',
  name VARCHAR(128) NOT NULL COMMENT '名称',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重，数值越大越靠前',
  created_by_user_id BIGINT NULL COMMENT '创建人用户 ID',
  updated_by_user_id BIGINT NULL COMMENT '更新人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  UNIQUE KEY uk_regions_code (app_code, region_code),
  KEY idx_regions_status_sort (app_code, status, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='区域表';

CREATE TABLE IF NOT EXISTS region_countries (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID',
  region_id BIGINT NOT NULL COMMENT '区域 ID',
  country_code VARCHAR(3) NOT NULL COMMENT '国家或地区编码',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  created_by_user_id BIGINT NULL COMMENT '创建人用户 ID',
  updated_by_user_id BIGINT NULL COMMENT '更新人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  active_country_code VARCHAR(3)
    GENERATED ALWAYS AS (
      CASE WHEN status = 'active' THEN country_code ELSE NULL END
    ) STORED COMMENT '启用国家编码',
  UNIQUE KEY uk_region_countries_active_country (app_code, active_country_code),
  KEY idx_region_countries_country_status (app_code, country_code, status),
  KEY idx_region_countries_region_status (app_code, region_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='区域国家表';

CREATE TABLE IF NOT EXISTS region_change_logs (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID',
  region_id BIGINT NULL COMMENT '区域 ID',
  operation VARCHAR(64) NOT NULL COMMENT '操作',
  before_json JSON NULL COMMENT '变更前 JSON 快照',
  after_json JSON NULL COMMENT '变更后 JSON 快照',
  operator_user_id BIGINT NULL COMMENT '操作人用户 ID',
  request_id VARCHAR(96) NOT NULL COMMENT '链路请求 ID，用于排查问题',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  KEY idx_region_change_logs_region (app_code, region_id, created_at_ms),
  KEY idx_region_change_logs_request_id (app_code, request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='区域变更日志表';

CREATE TABLE IF NOT EXISTS user_region_rebuild_tasks (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  task_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '任务 ID',
  region_id BIGINT NULL COMMENT '区域 ID',
  country_code VARCHAR(3) NOT NULL COMMENT '国家或地区编码',
  target_region_id BIGINT NULL COMMENT '目标区域 ID',
  mapping_revision BIGINT NOT NULL COMMENT '映射版本',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  cursor_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '游标用户 ID',
  locked_by VARCHAR(128) NULL COMMENT '当前锁定 worker',
  locked_until_ms BIGINT NULL COMMENT '锁定截止时间，UTC epoch ms',
  attempt_count INT NOT NULL DEFAULT 0 COMMENT '尝试次数',
  error_message VARCHAR(512) NULL COMMENT '错误消息',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  KEY idx_user_region_rebuild_tasks_status (app_code, status, updated_at_ms),
  KEY idx_user_region_rebuild_tasks_country (app_code, country_code, status),
  KEY idx_user_region_rebuild_tasks_lock (app_code, status, locked_until_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户区域重建任务表';

CREATE TABLE IF NOT EXISTS user_push_tokens (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  device_id VARCHAR(128) NOT NULL COMMENT '设备 ID',
  push_token VARCHAR(512) NOT NULL COMMENT '推送令牌',
  provider VARCHAR(32) NOT NULL COMMENT '提供方',
  platform VARCHAR(16) NOT NULL COMMENT '平台',
  app_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '应用版本',
  language VARCHAR(32) NOT NULL DEFAULT '' COMMENT '语言',
  timezone VARCHAR(64) NOT NULL DEFAULT '' COMMENT '时区',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  bound_at_ms BIGINT NOT NULL COMMENT '绑定时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  last_request_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '最后请求 ID',
  PRIMARY KEY (app_code, user_id, device_id),
  UNIQUE KEY uk_user_push_tokens_token (app_code, provider, push_token),
  KEY idx_user_push_tokens_user_status (app_code, user_id, status),
  KEY idx_user_push_tokens_updated (app_code, status, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户推送令牌表';

CREATE TABLE IF NOT EXISTS user_invite_codes (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  invite_code_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '邀请码 ID',
  owner_user_id BIGINT NOT NULL COMMENT '房主用户 ID',
  code VARCHAR(64) NOT NULL COMMENT '编码',
  code_type VARCHAR(32) NOT NULL DEFAULT 'primary' COMMENT '编码类型',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  UNIQUE KEY uk_user_invite_codes_code (app_code, code),
  UNIQUE KEY uk_user_invite_codes_owner_primary (app_code, owner_user_id, code_type),
  KEY idx_user_invite_codes_owner (app_code, owner_user_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户邀请编码表';

CREATE TABLE IF NOT EXISTS user_invite_relations (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  relation_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '关系 ID',
  invited_user_id BIGINT NOT NULL COMMENT '被邀请人用户 ID',
  inviter_user_id BIGINT NOT NULL COMMENT '邀请人用户 ID',
  invite_code_id BIGINT NOT NULL COMMENT '邀请码 ID',
  invite_code VARCHAR(64) NOT NULL COMMENT '邀请编码',
  source VARCHAR(64) NOT NULL COMMENT '来源',
  request_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '链路请求 ID，用于排查问题',
  bound_at_ms BIGINT NOT NULL COMMENT '绑定时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  UNIQUE KEY uk_user_invite_relations_invited (app_code, invited_user_id),
  KEY idx_user_invite_relations_inviter (app_code, inviter_user_id, bound_at_ms),
  KEY idx_user_invite_relations_code (app_code, invite_code, bound_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户邀请关系表';

CREATE TABLE IF NOT EXISTS user_invite_validity_policies (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  policy_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '策略 ID',
  region_id BIGINT NOT NULL DEFAULT 0 COMMENT '区域 ID',
  policy_version VARCHAR(64) NOT NULL COMMENT '策略版本',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  required_recharge_coin_amount BIGINT NOT NULL COMMENT '要求充值金币数量',
  eligible_recharge_types JSON NOT NULL COMMENT '可计入充值类型',
  effective_from_ms BIGINT NOT NULL COMMENT '生效开始时间，UTC epoch ms',
  effective_to_ms BIGINT NULL COMMENT '生效结束时间，UTC epoch ms',
  created_by_user_id BIGINT NULL COMMENT '创建人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  UNIQUE KEY uk_user_invite_policy_version (app_code, region_id, policy_version),
  KEY idx_user_invite_policies_active (app_code, region_id, status, effective_from_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户邀请有效性策略表';

INSERT INTO user_invite_validity_policies (
  app_code, region_id, policy_version, status, required_recharge_coin_amount,
  eligible_recharge_types, effective_from_ms, effective_to_ms, created_by_user_id, created_at_ms, updated_at_ms
) VALUES (
  'lalu', 0, 'invite-valid-default-v1', 'active', 80000,
  JSON_ARRAY('coin_seller_transfer', 'google_play', 'google', 'mifapay', 'v5pay', 'usdt_trc20'), 0, NULL, NULL, 0, 0
) ON DUPLICATE KEY UPDATE
  status = VALUES(status),
  required_recharge_coin_amount = VALUES(required_recharge_coin_amount),
  eligible_recharge_types = VALUES(eligible_recharge_types),
  effective_from_ms = VALUES(effective_from_ms),
  effective_to_ms = VALUES(effective_to_ms),
  updated_at_ms = VALUES(updated_at_ms);

CREATE TABLE IF NOT EXISTS user_invite_recharge_progress (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  invited_user_id BIGINT NOT NULL COMMENT '被邀请人用户 ID',
  inviter_user_id BIGINT NOT NULL COMMENT '邀请人用户 ID',
  policy_id BIGINT NOT NULL COMMENT '策略 ID',
  policy_version VARCHAR(64) NOT NULL COMMENT '策略版本',
  required_recharge_coin_amount BIGINT NOT NULL COMMENT '要求充值金币数量',
  accumulated_recharge_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '累计充值金币数量',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  valid_at_ms BIGINT NULL COMMENT '有效时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, invited_user_id),
  KEY idx_user_invite_progress_inviter_status (app_code, inviter_user_id, status, updated_at_ms),
  KEY idx_user_invite_progress_policy (app_code, policy_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户邀请充值进度表';

CREATE TABLE IF NOT EXISTS user_invite_event_consumption (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  event_id VARCHAR(128) NOT NULL COMMENT '事件幂等 ID',
  event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
  transaction_id VARCHAR(96) NOT NULL COMMENT '钱包交易 ID',
  invited_user_id BIGINT NOT NULL COMMENT '被邀请人用户 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  skip_reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '跳过原因',
  consumed_at_ms BIGINT NULL COMMENT '消费时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, event_id),
  UNIQUE KEY uk_user_invite_event_tx (app_code, transaction_id, event_type),
  KEY idx_user_invite_event_user (app_code, invited_user_id, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户邀请事件消费位点表';

CREATE TABLE IF NOT EXISTS user_invite_counters (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '邀请数量',
  valid_invite_count BIGINT NOT NULL DEFAULT 0 COMMENT '有效邀请数量',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户邀请计数表';

CREATE TABLE IF NOT EXISTS user_cp_relation_configs (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  relation_type VARCHAR(32) NOT NULL COMMENT '关系类型：cp、brother、sister',
  max_count_per_user INT NOT NULL COMMENT '每个用户可拥有的该类型 active 关系数量上限',
  application_expire_hours INT NOT NULL COMMENT '申请过期小时数',
  breakup_cost_coins BIGINT NOT NULL DEFAULT 0 COMMENT '用户主动解除该类型关系需要支付的金币数',
  status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '业务状态',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, relation_type)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 CP 关系配置表';

CREATE TABLE IF NOT EXISTS user_cp_level_rules (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  relation_type VARCHAR(32) NOT NULL COMMENT '关系类型：cp、brother、sister',
  level_no INT NOT NULL COMMENT '关系等级，1-5',
  intimacy_threshold BIGINT NOT NULL COMMENT '达到该等级需要的亲密值',
  reward_resource_group_id BIGINT NOT NULL DEFAULT 0 COMMENT '升级发放的装扮资源组 ID，0 表示不发放',
  level_icon_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '等级展示图标',
  status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '业务状态',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, relation_type, level_no),
  KEY idx_user_cp_level_rules_status (app_code, status, relation_type, level_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 CP 等级规则表';

CREATE TABLE IF NOT EXISTS user_cp_applications (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  application_id VARCHAR(128) NOT NULL COMMENT '申请 ID',
  requester_user_id BIGINT NOT NULL COMMENT '发起用户 ID',
  target_user_id BIGINT NOT NULL COMMENT '接收用户 ID',
  relation_type VARCHAR(32) NOT NULL COMMENT '关系类型：cp、brother、sister',
  status VARCHAR(32) NOT NULL COMMENT '申请状态：pending、accepted、rejected、expired、blocked',
  room_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '触发礼物所在房间 ID',
  room_region_id BIGINT NOT NULL DEFAULT 0 COMMENT '触发礼物所在房间区域 ID',
  room_locked TINYINT(1) NOT NULL DEFAULT 0 COMMENT '触发送礼命令在 Room Cell 内固化的锁房状态',
  gift_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '触发申请的礼物 ID',
  gift_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '触发申请的礼物名称',
  gift_icon_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '触发申请的礼物图标',
  gift_animation_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '触发申请的礼物动画',
  gift_count INT NOT NULL DEFAULT 0 COMMENT '触发申请的礼物数量',
  gift_value BIGINT NOT NULL DEFAULT 0 COMMENT '触发申请的礼物亲密值/热度值',
  gift_coin_value BIGINT NOT NULL DEFAULT 0 COMMENT '触发申请时 wallet 结算出的礼物金币价值；历史缺失时保持 0',
  formation_feed_eligible TINYINT(1) NOT NULL DEFAULT 0 COMMENT '成立礼物 feed 固定资格：金币价值严格大于 5000',
  billing_receipt_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '钱包扣费回执 ID',
  source_room_event_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '来源 room outbox 事件 ID',
  source_command_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '来源房间命令 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  expires_at_ms BIGINT NOT NULL COMMENT '过期时间，UTC epoch ms',
  decided_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '同意或拒绝时间，UTC epoch ms',
  PRIMARY KEY (app_code, application_id),
  UNIQUE KEY uk_user_cp_app_source_event (app_code, source_room_event_id),
  KEY idx_user_cp_app_target_status (app_code, target_user_id, status, updated_at_ms),
  KEY idx_user_cp_app_requester_status (app_code, requester_user_id, status, updated_at_ms),
  KEY idx_user_cp_app_pair_status (app_code, requester_user_id, target_user_id, status, relation_type),
  KEY idx_user_cp_app_admin_created (app_code, created_at_ms, application_id),
  KEY idx_user_cp_app_admin_filter (app_code, status, relation_type, created_at_ms, application_id),
  KEY idx_user_cp_app_formation_feed (app_code, status, relation_type, formation_feed_eligible, decided_at_ms, application_id),
  KEY idx_user_cp_app_expire (app_code, status, expires_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 CP 关系申请表';

CREATE TABLE IF NOT EXISTS user_cp_relationships (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  relationship_id VARCHAR(128) NOT NULL COMMENT '关系 ID',
  user_a_id BIGINT NOT NULL COMMENT '较小的用户 ID',
  user_b_id BIGINT NOT NULL COMMENT '较大的用户 ID',
  relation_type VARCHAR(32) NOT NULL COMMENT '关系类型：cp、brother、sister',
  status VARCHAR(32) NOT NULL COMMENT '关系状态：active、ended',
  intimacy_value BIGINT NOT NULL DEFAULT 0 COMMENT '两人关系亲密值',
  level_no INT NOT NULL DEFAULT 1 COMMENT '当前关系等级',
  source_application_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '建立关系的申请 ID',
  formed_at_ms BIGINT NOT NULL COMMENT '建立时间，UTC epoch ms',
  ended_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '解除时间，UTC epoch ms',
  ended_by_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '主动解除关系的用户 ID',
  breakup_cost_coins BIGINT NOT NULL DEFAULT 0 COMMENT '本次解除实际支付金币数',
  breakup_command_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '解除关系命令 ID',
  breakup_wallet_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '解除扣费钱包交易 ID',
  breakup_coin_balance_after BIGINT NOT NULL DEFAULT 0 COMMENT '解除扣费后用户金币余额',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, relationship_id),
  UNIQUE KEY uk_user_cp_rel_active_pair (app_code, user_a_id, user_b_id, status),
  KEY idx_user_cp_rel_user_a (app_code, user_a_id, status, relation_type, updated_at_ms),
  KEY idx_user_cp_rel_user_b (app_code, user_b_id, status, relation_type, updated_at_ms),
  KEY idx_user_cp_rel_admin_formed (app_code, formed_at_ms, relationship_id),
  KEY idx_user_cp_rel_admin_filter (app_code, status, relation_type, formed_at_ms, relationship_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 CP 关系表';

CREATE TABLE IF NOT EXISTS user_cp_relationship_breakups (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  command_id VARCHAR(128) NOT NULL COMMENT '解除关系命令 ID',
  relationship_id VARCHAR(128) NOT NULL COMMENT '关系 ID',
  requester_user_id BIGINT NOT NULL COMMENT '发起解除的用户 ID',
  relation_type VARCHAR(32) NOT NULL COMMENT '关系类型：cp、brother、sister',
  breakup_cost_coins BIGINT NOT NULL DEFAULT 0 COMMENT '创建解除占位时读取到的金币费用',
  status VARCHAR(32) NOT NULL COMMENT '解除状态：pending、confirmed、canceled',
  wallet_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '解除扣费钱包交易 ID',
  coin_balance_after BIGINT NOT NULL DEFAULT 0 COMMENT '扣费后金币余额',
  reason VARCHAR(256) NOT NULL DEFAULT '' COMMENT '取消原因',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  confirmed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '确认解除时间，UTC epoch ms',
  canceled_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '取消解除时间，UTC epoch ms',
  PRIMARY KEY (app_code, command_id),
  KEY idx_user_cp_breakup_relationship_status (app_code, relationship_id, status, updated_at_ms),
  KEY idx_user_cp_breakup_requester_status (app_code, requester_user_id, status, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 CP 关系解除占位表';

CREATE TABLE IF NOT EXISTS user_cp_gift_event_consumption (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  room_event_id VARCHAR(128) NOT NULL COMMENT 'room outbox 事件 ID',
  status VARCHAR(32) NOT NULL COMMENT '消费状态',
  application_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '生成或刷新申请 ID',
  relationship_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '命中的 active 关系 ID',
  skip_reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '跳过原因',
  gift_value BIGINT NOT NULL DEFAULT 0 COMMENT '本次送礼对 CP 关系贡献的积分',
  occurred_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'room outbox 事件发生时间，UTC epoch ms',
  consumed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '消费时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, room_event_id),
  KEY idx_user_cp_gift_consumption_status (app_code, status, updated_at_ms),
  KEY idx_user_cp_gift_consumption_weekly_rank (app_code, status, occurred_at_ms, relationship_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 CP 礼物事件消费位点表';

CREATE TABLE IF NOT EXISTS user_outbox (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  event_id VARCHAR(128) NOT NULL COMMENT '事件幂等 ID',
  event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
  aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型',
  aggregate_id BIGINT NOT NULL COMMENT '聚合 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  worker_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'MQ 投递 worker ID',
  lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT 'MQ 投递锁过期时间，UTC epoch ms',
  retry_count INT NOT NULL DEFAULT 0 COMMENT '重试次数',
  next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下一次 MQ 投递重试时间，UTC epoch ms',
  last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最后一次失败原因',
  payload_json JSON NOT NULL COMMENT '业务负载 JSON 快照',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, event_id),
  KEY idx_user_outbox_status_created (app_code, status, next_retry_at_ms, created_at_ms, event_id),
  KEY idx_user_outbox_lock (app_code, status, lock_until_ms, created_at_ms, event_id),
  KEY idx_user_outbox_retention (app_code, status, updated_at_ms, event_id),
  KEY idx_user_outbox_aggregate (app_code, aggregate_type, aggregate_id, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户发件箱表';

-- 老测试库可能已经存在 user_outbox，CREATE TABLE IF NOT EXISTS 不会补齐后续新增的 MQ 投递字段。
-- 这里用 information_schema 生成幂等 ALTER，保证重复执行 initdb 时不会因重复列名失败。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND COLUMN_NAME = 'worker_id') = 0,
  'ALTER TABLE user_outbox ADD COLUMN worker_id VARCHAR(128) NOT NULL DEFAULT '''' COMMENT ''MQ 投递 worker ID'' AFTER status',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- 投递锁过期时间用于多实例 claim user_outbox，老库缺这个列时 CP 通知 worker 会直接 SQL 1054。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND COLUMN_NAME = 'lock_until_ms') = 0,
  'ALTER TABLE user_outbox ADD COLUMN lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''MQ 投递锁过期时间，UTC epoch ms'' AFTER worker_id',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- 重试次数和下一次重试时间跟 outbox 补偿投递扫描绑定，缺列会导致 pending 查询或失败回写不可用。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND COLUMN_NAME = 'retry_count') = 0,
  'ALTER TABLE user_outbox ADD COLUMN retry_count INT NOT NULL DEFAULT 0 COMMENT ''重试次数'' AFTER lock_until_ms',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND COLUMN_NAME = 'next_retry_at_ms') = 0,
  'ALTER TABLE user_outbox ADD COLUMN next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''下一次 MQ 投递重试时间，UTC epoch ms'' AFTER retry_count',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- 失败原因用于排查投递链路，缺列时 MarkFailed 会再次失败并掩盖真实业务错误。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND COLUMN_NAME = 'last_error') = 0,
  'ALTER TABLE user_outbox ADD COLUMN last_error VARCHAR(512) NOT NULL DEFAULT '''' COMMENT ''最后一次失败原因'' AFTER next_retry_at_ms',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

-- pending 索引列顺序跟 user_outbox 扫描条件绑定；历史索引存在时先删除再按当前结构重建。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND INDEX_NAME = 'idx_user_outbox_status_created') > 0,
  'ALTER TABLE user_outbox DROP INDEX idx_user_outbox_status_created',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := 'ALTER TABLE user_outbox ADD INDEX idx_user_outbox_status_created (app_code, status, next_retry_at_ms, created_at_ms, event_id)';
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND INDEX_NAME = 'idx_user_outbox_lock') = 0,
  'ALTER TABLE user_outbox ADD INDEX idx_user_outbox_lock (app_code, status, lock_until_ms, created_at_ms, event_id)',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND INDEX_NAME = 'idx_user_outbox_retention') = 0,
  'ALTER TABLE user_outbox ADD INDEX idx_user_outbox_retention (app_code, status, updated_at_ms, event_id)',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_outbox' AND INDEX_NAME = 'idx_user_outbox_aggregate') = 0,
  'ALTER TABLE user_outbox ADD INDEX idx_user_outbox_aggregate (app_code, aggregate_type, aggregate_id, created_at_ms)',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

CREATE TABLE IF NOT EXISTS user_mic_sessions (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  mic_session_id VARCHAR(128) NOT NULL COMMENT '麦位会话 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  room_id VARCHAR(64) NOT NULL COMMENT '房间 ID',
  first_seat_no INT NOT NULL COMMENT '首次麦位编号',
  current_seat_no INT NOT NULL COMMENT '当前麦位编号',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  seat_started_at_ms BIGINT NOT NULL COMMENT '麦位开始时间，UTC epoch ms',
  publishing_started_at_ms BIGINT NULL COMMENT '推流开始时间，UTC epoch ms',
  ended_at_ms BIGINT NULL COMMENT '结束时间，UTC epoch ms',
  end_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '结束原因',
  seat_occupied_ms BIGINT NOT NULL DEFAULT 0 COMMENT '麦位占用毫秒值',
  mic_online_ms BIGINT NOT NULL DEFAULT 0 COMMENT '麦位在线毫秒值',
  opened_event_id VARCHAR(128) NOT NULL COMMENT '打开事件 ID',
  publish_event_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '推流事件 ID',
  closed_event_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '关闭事件 ID',
  last_event_id VARCHAR(128) NOT NULL COMMENT '最后事件 ID',
  last_room_version BIGINT NOT NULL DEFAULT 0 COMMENT '最后房间版本',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, mic_session_id),
  KEY idx_user_mic_sessions_user_status (app_code, user_id, status, updated_at_ms),
  KEY idx_user_mic_sessions_room_status (app_code, room_id, status),
  KEY idx_user_mic_sessions_open_timeout (app_code, status, seat_started_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户麦位会话表';

CREATE TABLE IF NOT EXISTS user_mic_daily_stats (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  stat_date CHAR(10) NOT NULL COMMENT '统计日期',
  seat_occupied_ms BIGINT NOT NULL DEFAULT 0 COMMENT '麦位占用毫秒值',
  mic_online_ms BIGINT NOT NULL DEFAULT 0 COMMENT '麦位在线毫秒值',
  session_count BIGINT NOT NULL DEFAULT 0 COMMENT '会话数量',
  room_count BIGINT NOT NULL DEFAULT 0 COMMENT '房间数量',
  first_mic_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '首次麦位时间，UTC epoch ms',
  last_mic_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最后麦位时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, user_id, stat_date),
  KEY idx_user_mic_daily_date (app_code, stat_date, mic_online_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户麦位每日统计表';

CREATE TABLE IF NOT EXISTS user_mic_lifetime_stats (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  seat_occupied_ms BIGINT NOT NULL DEFAULT 0 COMMENT '麦位占用毫秒值',
  mic_online_ms BIGINT NOT NULL DEFAULT 0 COMMENT '麦位在线毫秒值',
  session_count BIGINT NOT NULL DEFAULT 0 COMMENT '会话数量',
  first_mic_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '首次麦位时间，UTC epoch ms',
  last_mic_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最后麦位时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, user_id),
  KEY idx_user_mic_lifetime_mic_online (app_code, mic_online_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户麦位累计统计表';

CREATE TABLE IF NOT EXISTS user_mic_event_consumption (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  event_id VARCHAR(128) NOT NULL COMMENT '事件幂等 ID',
  event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
  mic_session_id VARCHAR(128) NOT NULL COMMENT '麦位会话 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  action VARCHAR(32) NOT NULL COMMENT '操作',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  skip_reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '跳过原因',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  consumed_at_ms BIGINT NULL COMMENT '消费时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, event_id),
  KEY idx_user_mic_event_session (app_code, mic_session_id, created_at_ms),
  KEY idx_user_mic_event_user (app_code, user_id, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户麦位事件消费位点表';

CREATE TABLE IF NOT EXISTS user_room_sessions (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  room_session_id VARCHAR(128) NOT NULL COMMENT '在房会话 ID，取 RoomUserJoined 事件 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  room_id VARCHAR(64) NOT NULL COMMENT '房间 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态 open/ended',
  is_robot TINYINT(1) NOT NULL DEFAULT 0 COMMENT '进房时固化的机器人标记，机器人会话不产生统计事实',
  joined_at_ms BIGINT NOT NULL COMMENT '进房时间，UTC epoch ms',
  ended_at_ms BIGINT NULL COMMENT '离房时间，UTC epoch ms',
  end_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '结束原因',
  room_online_ms BIGINT NOT NULL DEFAULT 0 COMMENT '在房毫秒值',
  opened_event_id VARCHAR(128) NOT NULL COMMENT '进房事件 ID',
  closed_event_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '闭合事件 ID',
  last_event_id VARCHAR(128) NOT NULL COMMENT '最后事件 ID',
  last_room_version BIGINT NOT NULL DEFAULT 0 COMMENT '最后房间版本',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, room_session_id),
  KEY idx_user_room_sessions_user_status (app_code, user_id, status, joined_at_ms),
  KEY idx_user_room_sessions_room_user (app_code, room_id, user_id, status),
  KEY idx_user_room_sessions_open_timeout (app_code, status, joined_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户在房会话表';

CREATE TABLE IF NOT EXISTS user_room_daily_stats (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  stat_date CHAR(10) NOT NULL COMMENT '统计日期，UTC',
  room_online_ms BIGINT NOT NULL DEFAULT 0 COMMENT '在房毫秒值',
  session_count BIGINT NOT NULL DEFAULT 0 COMMENT '会话数量',
  first_room_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '首次在房时间，UTC epoch ms',
  last_room_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最后在房时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, user_id, stat_date),
  KEY idx_user_room_daily_date (app_code, stat_date, room_online_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户在房每日统计表';

CREATE TABLE IF NOT EXISTS user_room_orphan_leaves (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  event_id VARCHAR(128) NOT NULL COMMENT '离房事件 ID',
  room_id VARCHAR(64) NOT NULL COMMENT '房间 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  left_at_ms BIGINT NOT NULL COMMENT '离房时间，UTC epoch ms',
  reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '离房原因',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  PRIMARY KEY (app_code, event_id),
  KEY idx_user_room_orphan_room_user (app_code, room_id, user_id, left_at_ms),
  KEY idx_user_room_orphan_left_at (app_code, left_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='先于进房事实到达的离房事件暂存表';

CREATE TABLE IF NOT EXISTS user_room_event_consumption (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  event_id VARCHAR(128) NOT NULL COMMENT '事件幂等 ID',
  event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
  room_id VARCHAR(64) NOT NULL COMMENT '房间 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  skip_reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '跳过原因',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  consumed_at_ms BIGINT NULL COMMENT '消费时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, event_id),
  KEY idx_user_room_event_user (app_code, user_id, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户在房事件消费位点表';

CREATE TABLE IF NOT EXISTS host_profiles (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  current_agency_id BIGINT NULL COMMENT '当前公会 ID',
  current_membership_id BIGINT NULL COMMENT '当前成员关系 ID',
  source VARCHAR(64) NOT NULL COMMENT '来源',
  first_became_host_at_ms BIGINT NOT NULL COMMENT '首次成为主播时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  KEY idx_host_profiles_agency (app_code, current_agency_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主播资料表';

CREATE TABLE IF NOT EXISTS bd_profiles (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID',
  role VARCHAR(32) NOT NULL COMMENT '角色',
  parent_leader_user_id BIGINT NULL COMMENT '父级负责人用户 ID',
  parent_owner_user_id BIGINT NULL COMMENT '外管团队直接 owner 用户 ID；普通 App BD 始终为空',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  created_by_user_id BIGINT NOT NULL COMMENT '创建人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  KEY idx_bd_profiles_parent_leader (app_code, parent_leader_user_id, status),
  KEY idx_bd_profiles_parent_owner (app_code, parent_owner_user_id, status, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BD资料表';

CREATE TABLE IF NOT EXISTS bd_leader_profiles (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  created_by_user_id BIGINT NOT NULL COMMENT '创建人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  KEY idx_bd_leader_profiles_created_by (app_code, created_by_user_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BD Leader资料表';

CREATE TABLE IF NOT EXISTS manager_profiles (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL PRIMARY KEY COMMENT '经理用户 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  contact_info VARCHAR(128) NOT NULL DEFAULT '' COMMENT '联系信息',
  can_grant_avatar_frame TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心赠送头像框',
  can_grant_vehicle TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心赠送座驾',
  can_grant_badge TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心赠送徽章',
  can_grant_vip TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心赠送 VIP',
  can_update_user_level TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心升级用户等级',
  can_add_bd_leader TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 BD Leader',
  can_add_bd_leader_in_region TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否允许把 BD Leader 邀请范围从本国家扩展到经理所属区域',
  can_manage_cross_region TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否允许经理中心所有目标用户操作跨区域执行',
  can_add_admin TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 Admin',
  can_add_superadmin TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心添加 Superadmin',
  can_block_user TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心封禁用户',
  can_transfer_user_country TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在经理中心转移用户国家',
  created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建后台管理员 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  KEY idx_manager_profiles_status (app_code, status, updated_at_ms),
  KEY idx_manager_profiles_created_by (app_code, created_by_admin_id, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='经理资料表';

CREATE TABLE IF NOT EXISTS coin_seller_profiles (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL PRIMARY KEY COMMENT '用户 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  merchant_asset_type VARCHAR(32) NOT NULL DEFAULT 'COIN_SELLER_COIN' COMMENT '商户资产类型',
  contact_info VARCHAR(128) NOT NULL DEFAULT '' COMMENT '联系信息',
  can_manage_sub_coin_sellers TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否允许在币商中心管理直属子币商',
  created_by_user_id BIGINT NOT NULL COMMENT '创建人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  KEY idx_coin_seller_profiles_status (app_code, status, updated_at_ms),
  KEY idx_coin_seller_profiles_created_by (app_code, created_by_user_id, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='金币商户资料表';

CREATE TABLE IF NOT EXISTS coin_seller_relations (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  relation_id BIGINT NOT NULL PRIMARY KEY COMMENT '直属关系 ID',
  parent_user_id BIGINT NOT NULL COMMENT '父币商用户 ID',
  child_user_id BIGINT NOT NULL COMMENT '子币商用户 ID',
  status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '关系状态',
  created_by_user_id BIGINT NOT NULL COMMENT '创建人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  active_child_user_id BIGINT GENERATED ALWAYS AS (
    CASE WHEN status = 'active' THEN child_user_id ELSE NULL END
  ) STORED COMMENT '当前有效子币商用户 ID',
  UNIQUE KEY uk_coin_seller_relation_active_child (app_code, active_child_user_id),
  UNIQUE KEY uk_coin_seller_relation_parent_child_status (app_code, parent_user_id, child_user_id, status),
  KEY idx_coin_seller_relation_parent (app_code, parent_user_id, status, created_at_ms),
  KEY idx_coin_seller_relation_child (app_code, child_user_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='币商直属子币商关系表';

CREATE TABLE IF NOT EXISTS coin_seller_sub_applications (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  application_id BIGINT NOT NULL PRIMARY KEY COMMENT '子币商申请 ID',
  command_id VARCHAR(128) NOT NULL COMMENT '提交申请幂等命令 ID',
  parent_user_id BIGINT NOT NULL COMMENT '父币商用户 ID',
  target_user_id BIGINT NOT NULL COMMENT '目标子币商用户 ID',
  status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT '审核状态 pending/approved/rejected',
  relation_id BIGINT NOT NULL DEFAULT 0 COMMENT '审核通过后创建的直属关系 ID',
  reviewed_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '审核管理员 ID',
  review_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '审核备注',
  reviewed_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '审核时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  pending_target_user_id BIGINT GENERATED ALWAYS AS (
    CASE WHEN status = 'pending' THEN target_user_id ELSE NULL END
  ) STORED COMMENT '当前待审目标用户 ID',
  UNIQUE KEY uk_coin_seller_sub_application_command (app_code, command_id),
  UNIQUE KEY uk_coin_seller_sub_application_pending_target (app_code, pending_target_user_id),
  KEY idx_coin_seller_sub_application_parent (app_code, parent_user_id, status, created_at_ms),
  KEY idx_coin_seller_sub_application_target (app_code, target_user_id, status, created_at_ms),
  KEY idx_coin_seller_sub_application_status (app_code, status, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='币商子币商邀请审核申请表';

CREATE TABLE IF NOT EXISTS agencies (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  agency_id BIGINT NOT NULL PRIMARY KEY COMMENT '公会 ID',
  owner_user_id BIGINT NOT NULL COMMENT '房主用户 ID',
  parent_bd_user_id BIGINT NOT NULL COMMENT '父级BD用户 ID',
  name VARCHAR(128) NOT NULL COMMENT '名称',
  avatar VARCHAR(512) NOT NULL DEFAULT '' COMMENT '公会头像',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  join_enabled BOOLEAN NOT NULL COMMENT '加入是否启用',
  max_hosts INT NOT NULL COMMENT '最大主播',
  created_by_user_id BIGINT NOT NULL COMMENT '创建人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  active_owner_user_id BIGINT GENERATED ALWAYS AS (
    CASE WHEN status = 'active' THEN owner_user_id ELSE NULL END
  ) STORED COMMENT '启用房主用户 ID',
  UNIQUE KEY uk_agencies_active_owner (app_code, active_owner_user_id),
  KEY idx_agencies_parent_bd (app_code, parent_bd_user_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公会表';

CREATE TABLE IF NOT EXISTS agency_memberships (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  membership_id BIGINT NOT NULL PRIMARY KEY COMMENT '成员关系 ID',
  agency_id BIGINT NOT NULL COMMENT '公会 ID',
  host_user_id BIGINT NOT NULL COMMENT '主播用户 ID',
  membership_type VARCHAR(32) NOT NULL COMMENT '成员关系类型',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  joined_at_ms BIGINT NOT NULL COMMENT '加入时间，UTC epoch ms',
  ended_at_ms BIGINT NULL COMMENT '结束时间，UTC epoch ms',
  ended_by_user_id BIGINT NULL COMMENT '结束操作用户 ID',
  ended_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '结束原因',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  active_host_user_id BIGINT GENERATED ALWAYS AS (
    CASE WHEN status = 'active' THEN host_user_id ELSE NULL END
  ) STORED COMMENT '启用主播用户 ID',
  UNIQUE KEY uk_agency_memberships_active_host (app_code, active_host_user_id),
  KEY idx_agency_memberships_agency_status (app_code, agency_id, status),
  KEY idx_agency_memberships_host_status (app_code, host_user_id, status),
  KEY idx_agency_memberships_effective (app_code, host_user_id, joined_at_ms, ended_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公会成员关系表';

CREATE TABLE IF NOT EXISTS agency_applications (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  application_id BIGINT NOT NULL PRIMARY KEY COMMENT '申请 ID',
  command_id VARCHAR(96) NOT NULL COMMENT '业务命令幂等 ID',
  applicant_user_id BIGINT NOT NULL COMMENT '申请人用户 ID',
  agency_id BIGINT NOT NULL COMMENT '公会 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  reviewed_by_user_id BIGINT NULL COMMENT '审核用户 ID',
  review_reason VARCHAR(512) NOT NULL DEFAULT '' COMMENT '审核原因',
  reviewed_at_ms BIGINT NULL COMMENT '审核时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  pending_applicant_user_id BIGINT GENERATED ALWAYS AS (
    CASE WHEN status = 'pending' THEN applicant_user_id ELSE NULL END
  ) STORED COMMENT '待处理申请人用户 ID',
  UNIQUE KEY uk_agency_applications_command (app_code, command_id),
  UNIQUE KEY uk_agency_applications_pending_user (app_code, pending_applicant_user_id),
  KEY idx_agency_applications_agency_status (app_code, agency_id, status, created_at_ms),
  KEY idx_agency_applications_applicant_status (app_code, applicant_user_id, status, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公会申请表';

CREATE TABLE IF NOT EXISTS role_invitations (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  invitation_id BIGINT NOT NULL PRIMARY KEY COMMENT '邀请 ID',
  command_id VARCHAR(96) NOT NULL COMMENT '业务命令幂等 ID',
  invitation_type VARCHAR(32) NOT NULL COMMENT '邀请类型',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  inviter_user_id BIGINT NOT NULL COMMENT '邀请人用户 ID',
  inviter_bd_user_id BIGINT NOT NULL COMMENT '邀请人BD用户 ID',
  target_user_id BIGINT NOT NULL COMMENT '目标用户 ID',
  external_operator_user_id BIGINT NULL COMMENT '外管邀请操作者的 App 用户 ID，普通 App 邀请为空',
  agency_name VARCHAR(128) NOT NULL DEFAULT '' COMMENT '公会名称',
  parent_bd_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '父级BD用户 ID',
  parent_leader_user_id BIGINT NULL COMMENT '父级负责人用户 ID',
  parent_owner_user_id BIGINT NULL COMMENT '邀请创建时固化的外管团队 owner 用户 ID',
  parent_agency_id BIGINT NULL COMMENT 'Host 邀请创建时固化的 Agency ID',
  processed_by_user_id BIGINT NULL COMMENT '处理用户 ID',
  process_reason VARCHAR(512) NOT NULL DEFAULT '' COMMENT '处理原因',
  processed_at_ms BIGINT NULL COMMENT '处理时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  pending_target_key VARCHAR(128) GENERATED ALWAYS AS (
    CASE WHEN status = 'pending' THEN CONCAT(invitation_type, ':', target_user_id) ELSE NULL END
  ) STORED COMMENT '待处理目标键',
  UNIQUE KEY uk_role_invitations_command (app_code, command_id),
  UNIQUE KEY uk_role_invitations_pending_target (app_code, pending_target_key),
  KEY idx_role_invitations_target_status (app_code, target_user_id, status, created_at_ms),
  KEY idx_role_invitations_inviter_status (app_code, inviter_user_id, status, created_at_ms),
  KEY idx_role_invitations_parent_owner (app_code, parent_owner_user_id, status, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色邀请表';

CREATE TABLE IF NOT EXISTS host_command_results (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  command_id VARCHAR(96) NOT NULL PRIMARY KEY COMMENT '业务命令幂等 ID',
  command_type VARCHAR(64) NOT NULL COMMENT '命令类型',
  result_type VARCHAR(64) NOT NULL COMMENT '结果类型',
  result_id BIGINT NOT NULL COMMENT '结果 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  KEY idx_host_command_results_result (app_code, result_type, result_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主播命令结果表';


CREATE TABLE IF NOT EXISTS host_outbox (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  event_id BIGINT NOT NULL PRIMARY KEY COMMENT '事件幂等 ID',
  event_type VARCHAR(64) NOT NULL COMMENT '事件类型',
  aggregate_type VARCHAR(64) NOT NULL COMMENT '聚合类型',
  aggregate_id VARCHAR(96) NOT NULL COMMENT '聚合 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  payload_json JSON NOT NULL COMMENT '业务负载 JSON 快照',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  retry_count INT NOT NULL DEFAULT 0 COMMENT '重试次数',
  last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最后一次失败原因',
  KEY idx_host_outbox_status_created (app_code, status, created_at_ms),
  KEY idx_host_outbox_aggregate (app_code, aggregate_type, aggregate_id, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主播发件箱表';


-- 区域种子使用产品定义的业务分区，不按原始地理来源自动推断。
INSERT INTO regions (region_code, name, status, sort_order, created_at_ms, updated_at_ms) VALUES
  ('MIDDLE_EAST', '中东区', 'active', 10, 0, 0),
  ('INDIA', '印度区', 'active', 20, 0, 0),
  ('EURASIA_AMERICA', '欧亚美区', 'active', 30, 0, 0)
ON DUPLICATE KEY UPDATE
  name = VALUES(name),
  status = VALUES(status),
  sort_order = VALUES(sort_order),
  updated_at_ms = VALUES(updated_at_ms);

-- 区域管理只暴露产品定义的业务分区；GLOBAL 仅作为内部兜底，不作为后台可管理区域。
DELETE FROM regions
WHERE app_code = 'lalu'
  AND region_code NOT IN ('MIDDLE_EAST', 'INDIA', 'EURASIA_AMERICA');

CREATE TABLE IF NOT EXISTS user_display_user_ids (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID',
  display_user_id VARCHAR(64) NOT NULL COMMENT '展示用户 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  display_user_id_kind VARCHAR(32) NOT NULL COMMENT '展示用户 ID分类',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  assigned_at_ms BIGINT NOT NULL COMMENT '分配时间，UTC epoch ms',
  activated_at_ms BIGINT NULL COMMENT '激活时间，UTC epoch ms',
  expires_at_ms BIGINT NULL COMMENT '过期时间，UTC epoch ms',
  released_at_ms BIGINT NULL COMMENT '释放时间，UTC epoch ms',
  release_reason VARCHAR(64) NULL COMMENT '释放原因',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  live_display_user_id VARCHAR(64)
    GENERATED ALWAYS AS (
      CASE
        WHEN status IN ('active', 'held', 'reserved', 'blocked') THEN display_user_id
        ELSE NULL
      END
    ) STORED COMMENT '实时展示用户 ID',
  active_user_id BIGINT
    GENERATED ALWAYS AS (
      CASE WHEN status = 'active' THEN user_id ELSE NULL END
    ) STORED COMMENT '启用用户 ID',
  UNIQUE KEY uk_user_display_user_ids_live (app_code, live_display_user_id),
  UNIQUE KEY uk_user_display_user_ids_active_user (app_code, active_user_id),
  KEY idx_user_display_user_ids_user_id (app_code, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户展示 ID 表';

CREATE TABLE IF NOT EXISTS display_user_id_sequences (
  app_code VARCHAR(32) NOT NULL PRIMARY KEY COMMENT '应用编码，用于多租户隔离',
  next_value BIGINT NOT NULL DEFAULT 0 COMMENT '下一个内部序号；对外展示号由置换算法生成',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='展示用户 ID 内部序列表';

INSERT INTO display_user_id_sequences (app_code, next_value, updated_at_ms)
SELECT
  a.app_code,
  GREATEST(COALESCE(du.occupied_count, 0), COALESCE(u.user_count, 0)),
  0
FROM apps a
LEFT JOIN (
  SELECT app_code, COUNT(*) AS occupied_count
  FROM user_display_user_ids
  WHERE live_display_user_id IS NOT NULL
  GROUP BY app_code
) du ON du.app_code = a.app_code
LEFT JOIN (
  SELECT app_code, COUNT(*) AS user_count
  FROM users
  GROUP BY app_code
) u ON u.app_code = a.app_code
ON DUPLICATE KEY UPDATE
  next_value = GREATEST(next_value, VALUES(next_value));

CREATE TABLE IF NOT EXISTS pretty_display_user_id_leases (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  lease_id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '租约 ID',
  display_user_id VARCHAR(64) NOT NULL COMMENT '展示用户 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  starts_at_ms BIGINT NOT NULL COMMENT '开始时间，UTC epoch ms',
  expires_at_ms BIGINT NOT NULL COMMENT '过期时间，UTC epoch ms',
  released_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '释放时间，UTC epoch ms',
  release_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '释放原因',
  payment_receipt_id VARCHAR(64) NULL COMMENT '支付凭据 ID',
  source VARCHAR(32) NOT NULL COMMENT '来源',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  active_display_user_id VARCHAR(64)
    GENERATED ALWAYS AS (
      CASE WHEN status = 'active' THEN display_user_id ELSE NULL END
    ) STORED COMMENT '启用展示用户 ID',
  active_user_id BIGINT
    GENERATED ALWAYS AS (
      CASE WHEN status = 'active' THEN user_id ELSE NULL END
    ) STORED COMMENT '启用用户 ID',
  UNIQUE KEY uk_pretty_display_user_id_active (app_code, active_display_user_id),
  UNIQUE KEY uk_pretty_display_user_id_active_user (app_code, active_user_id),
  KEY idx_pretty_display_user_id_leases_user (app_code, user_id, status),
  KEY idx_pretty_display_user_id_leases_expire (app_code, status, expires_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号展示用户 ID 租约表';

CREATE TABLE IF NOT EXISTS pretty_display_id_pools (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  pool_id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '靓号池 ID',
  name VARCHAR(128) NOT NULL COMMENT '靓号池名称',
  level_track VARCHAR(32) NOT NULL COMMENT '等级轨道：wealth/game/charm',
  min_level INT NOT NULL COMMENT '最小等级，包含',
  max_level INT NOT NULL COMMENT '最大等级，包含',
  rule_type VARCHAR(32) NOT NULL COMMENT '生成规则',
  rule_config_json JSON NULL COMMENT '生成规则配置',
  status VARCHAR(32) NOT NULL COMMENT '业务状态：active/disabled',
  sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重',
  created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建管理员 ID',
  updated_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '更新管理员 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  UNIQUE KEY uk_pretty_pool_range_rule (app_code, level_track, min_level, max_level, name),
  KEY idx_pretty_pool_status (app_code, status, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号池配置表';

CREATE TABLE IF NOT EXISTS pretty_display_ids (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  pretty_id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '靓号记录 ID',
  pool_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '所属靓号池 ID，后台发放为空',
  source VARCHAR(32) NOT NULL DEFAULT 'pool' COMMENT '来源：pool/admin_grant',
  display_user_id VARCHAR(64) NOT NULL COMMENT '靓号展示内容',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  assigned_user_id BIGINT NOT NULL DEFAULT 0 COMMENT '当前占用用户 ID',
  assigned_lease_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '当前占用租约 ID',
  assigned_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '占用时间，UTC epoch ms',
  released_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '释放时间，UTC epoch ms',
  release_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '释放原因',
  generated_batch_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '生成批次 ID',
  created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '创建管理员 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  live_display_user_id VARCHAR(64)
    GENERATED ALWAYS AS (
      CASE WHEN status IN ('available', 'assigned', 'disabled', 'reserved') THEN display_user_id ELSE NULL END
    ) STORED COMMENT '未删除且未释放的靓号内容',
  UNIQUE KEY uk_pretty_display_ids_live (app_code, live_display_user_id),
  KEY idx_pretty_display_ids_pool_status (app_code, pool_id, status),
  KEY idx_pretty_display_ids_assigned_user (app_code, assigned_user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号池号码表';

CREATE TABLE IF NOT EXISTS pretty_display_id_generation_batches (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  batch_id VARCHAR(64) NOT NULL PRIMARY KEY COMMENT '生成批次 ID',
  pool_id VARCHAR(64) NOT NULL COMMENT '靓号池 ID',
  rule_type VARCHAR(32) NOT NULL COMMENT '生成规则',
  rule_config_json JSON NULL COMMENT '生成规则配置',
  requested_count INT NOT NULL COMMENT '请求生成数量',
  generated_count INT NOT NULL COMMENT '实际生成数量',
  skipped_conflict_count INT NOT NULL COMMENT '冲突跳过数量',
  status VARCHAR(32) NOT NULL COMMENT '批次状态',
  operator_admin_id BIGINT NOT NULL COMMENT '操作管理员 ID',
  request_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '链路请求 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  KEY idx_pretty_batch_pool (app_code, pool_id, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='靓号批量生成记录表';

CREATE TABLE IF NOT EXISTS display_user_id_change_logs (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  old_display_user_id VARCHAR(64) NOT NULL COMMENT '原展示用户 ID',
  new_display_user_id VARCHAR(64) NOT NULL COMMENT '新展示用户 ID',
  old_display_user_id_kind VARCHAR(32) NOT NULL COMMENT '原展示用户 ID分类',
  new_display_user_id_kind VARCHAR(32) NOT NULL COMMENT '新展示用户 ID分类',
  change_type VARCHAR(32) NOT NULL COMMENT '变更类型',
  lease_id VARCHAR(64) NULL COMMENT '租约 ID',
  reason VARCHAR(64) NOT NULL COMMENT '原因',
  operator_user_id BIGINT NULL COMMENT '操作人用户 ID',
  request_id VARCHAR(96) NOT NULL COMMENT '链路请求 ID，用于排查问题',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  KEY idx_display_user_id_change_logs_user_id (app_code, user_id),
  KEY idx_display_user_id_change_logs_request_id (app_code, request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='展示用户 ID变更日志表';

CREATE TABLE IF NOT EXISTS user_country_change_logs (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  old_country VARCHAR(64) NULL COMMENT '原国家',
  new_country VARCHAR(64) NOT NULL COMMENT '新国家',
  request_id VARCHAR(96) NOT NULL COMMENT '链路请求 ID，用于排查问题',
  changed_at_ms BIGINT NOT NULL COMMENT '变更时间，UTC epoch ms',
  KEY idx_user_country_change_logs_user_changed (app_code, user_id, changed_at_ms),
  KEY idx_user_country_change_logs_request_id (app_code, request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户国家变更日志表';

CREATE TABLE IF NOT EXISTS password_accounts (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  password_hash VARCHAR(255) NOT NULL COMMENT '密码哈希值',
  hash_alg VARCHAR(32) NOT NULL COMMENT '哈希算法',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='密码账户表';

CREATE TABLE IF NOT EXISTS third_party_identities (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  provider VARCHAR(64) NOT NULL COMMENT '提供方',
  provider_subject VARCHAR(191) NOT NULL COMMENT '提供方主体',
  provider_union_id VARCHAR(191) NULL COMMENT '提供方联合 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  UNIQUE KEY uk_third_party_provider_subject (app_code, provider, provider_subject),
  KEY idx_third_party_user_id (app_code, user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='第三方身份表';

CREATE TABLE IF NOT EXISTS auth_sessions (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  session_id VARCHAR(96) NOT NULL PRIMARY KEY COMMENT '会话 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  refresh_token_hash VARCHAR(128) NOT NULL COMMENT '刷新令牌哈希值',
  token_family_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '令牌族根 session ID',
  generation BIGINT NOT NULL DEFAULT 0 COMMENT '令牌族内单调代际，首代为 0',
  parent_session_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '直接父代 session ID',
  rotated_to_session_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '轮换产生的直接子代 session ID',
  rotation_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '轮换时间，UTC epoch ms',
  rotation_request_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '首次轮换 refresh 幂等键',
  device_id VARCHAR(128) NOT NULL COMMENT '设备 ID',
  expires_at_ms BIGINT NOT NULL COMMENT '过期时间，UTC epoch ms',
  last_heartbeat_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最近 App 心跳时间，UTC epoch ms',
  last_heartbeat_request_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '最近 App 心跳请求 ID',
  revoked_at_ms BIGINT NULL COMMENT '吊销时间，UTC epoch ms',
  revoked_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '吊销原因',
  revoked_request_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '吊销请求 ID',
  revoked_by VARCHAR(64) NOT NULL DEFAULT '' COMMENT '吊销操作方',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  UNIQUE KEY uk_auth_sessions_refresh_token_hash (app_code, refresh_token_hash),
  KEY idx_auth_sessions_user_id (app_code, user_id),
  KEY idx_auth_sessions_token_family (app_code, token_family_id, generation),
  KEY idx_auth_sessions_heartbeat (app_code, last_heartbeat_at_ms),
  KEY idx_auth_sessions_expires_at (app_code, expires_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='认证会话表';

-- refresh 轮换结果必须和 session 替换同事务落 MySQL，才能覆盖多实例并发和提交后响应丢失；这里只保存 AES-GCM 密文。
CREATE TABLE IF NOT EXISTS auth_refresh_outcomes (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
  parent_session_id VARCHAR(96) NOT NULL COMMENT '被消费的父 session ID',
  child_session_id VARCHAR(96) NOT NULL COMMENT '首次轮换产生的子 session ID',
  refresh_request_id VARCHAR(96) NOT NULL COMMENT '客户端 refresh 幂等键；旧客户端由 token hash 派生',
  token_ciphertext VARBINARY(4096) NOT NULL COMMENT 'AES-GCM 加密后的完整 token pair',
  created_at_ms BIGINT NOT NULL COMMENT '轮换时间，UTC epoch ms',
  expires_at_ms BIGINT NOT NULL COMMENT '幂等密文过期时间，UTC epoch ms',
  PRIMARY KEY (app_code, parent_session_id),
  KEY idx_auth_refresh_outcomes_request (app_code, refresh_request_id),
  KEY idx_auth_refresh_outcomes_expires (expires_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='refresh token 轮换短期幂等结果';

CREATE TABLE IF NOT EXISTS auth_session_denylist_jobs (
  job_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '补偿任务 ID',
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
  session_id VARCHAR(96) NOT NULL COMMENT '需要写入 gateway Redis denylist 的 session ID',
  reason VARCHAR(64) NOT NULL COMMENT 'REFRESH_ROTATED、USER_LOGOUT 或 REFRESH_TOKEN_REUSE',
  status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/processing/retryable/delivered',
  attempts INT NOT NULL DEFAULT 0 COMMENT '认领次数',
  next_retry_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '下次重试时间，UTC epoch ms',
  locked_by VARCHAR(96) NOT NULL DEFAULT '' COMMENT '当前 worker ID',
  locked_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '处理租约截止时间，UTC epoch ms',
  last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最近失败摘要，不含 token 原文',
  deny_until_ms BIGINT NOT NULL COMMENT '该 sid 历史 access JWT 最晚拒绝时刻（含安全余量），UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  UNIQUE KEY uk_auth_session_denylist_job (app_code, session_id),
  KEY idx_auth_session_denylist_pending (status, next_retry_at_ms, job_id),
  KEY idx_auth_session_denylist_cleanup (deny_until_ms, job_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='session family 撤销到 Redis denylist 的持久补偿任务';

-- 老环境已经存在 auth_sessions 时，CREATE TABLE IF NOT EXISTS 不会补 App 心跳字段。
SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'auth_sessions' AND COLUMN_NAME = 'last_heartbeat_at_ms') = 0,
  'ALTER TABLE auth_sessions ADD COLUMN last_heartbeat_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT ''最近 App 心跳时间，UTC epoch ms'' AFTER expires_at_ms',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'auth_sessions' AND COLUMN_NAME = 'last_heartbeat_request_id') = 0,
  'ALTER TABLE auth_sessions ADD COLUMN last_heartbeat_request_id VARCHAR(96) NOT NULL DEFAULT '''' COMMENT ''最近 App 心跳请求 ID'' AFTER last_heartbeat_at_ms',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @ddl := IF(
  (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'auth_sessions' AND INDEX_NAME = 'idx_auth_sessions_heartbeat') = 0,
  'ALTER TABLE auth_sessions ADD INDEX idx_auth_sessions_heartbeat (app_code, last_heartbeat_at_ms)',
  'SELECT 1'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

CREATE TABLE IF NOT EXISTS login_audit (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键 ID',
  request_id VARCHAR(96) NOT NULL COMMENT '链路请求 ID，用于排查问题',
  user_id BIGINT NULL COMMENT '用户 ID',
  login_type VARCHAR(32) NOT NULL COMMENT '登录类型',
  provider VARCHAR(64) NULL COMMENT '提供方',
  channel VARCHAR(32) NOT NULL DEFAULT '' COMMENT '渠道',
  platform VARCHAR(16) NOT NULL DEFAULT '' COMMENT '平台',
  app_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端版本',
  build_number VARCHAR(64) NOT NULL DEFAULT '' COMMENT '本次认证客户端构建号',
  result VARCHAR(32) NOT NULL COMMENT '结果',
  failure_code VARCHAR(64) NULL COMMENT '失败编码',
  client_ip VARCHAR(64) NULL COMMENT '客户端IP',
  ip_country_code VARCHAR(3) NOT NULL DEFAULT '' COMMENT 'IP国家编码',
  blocked TINYINT(1) NOT NULL DEFAULT 0 COMMENT '已拦截',
  block_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '拦截原因',
  user_agent VARCHAR(512) NULL COMMENT '用户代理',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  KEY idx_login_audit_user_id (app_code, user_id),
  KEY idx_login_audit_request_id (app_code, request_id),
  KEY idx_login_audit_created_at (app_code, created_at_ms),
  KEY idx_login_audit_user_created (app_code, user_id, created_at_ms),
  KEY idx_login_audit_latest_success (app_code, user_id, result, blocked, login_type, created_at_ms, id),
  KEY idx_login_audit_blocked (app_code, blocked, created_at_ms),
  KEY idx_login_audit_ip_country (app_code, ip_country_code, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录审计表';

CREATE TABLE IF NOT EXISTS login_ip_risk_jobs (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  job_id VARCHAR(96) NOT NULL COMMENT '任务 ID',
  session_id VARCHAR(96) NOT NULL COMMENT '会话 ID',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  client_ip VARCHAR(64) NOT NULL COMMENT '客户端IP',
  client_ip_hash VARCHAR(128) NOT NULL COMMENT '客户端IP哈希值',
  channel VARCHAR(32) NOT NULL COMMENT '渠道',
  platform VARCHAR(16) NOT NULL COMMENT '平台',
  language VARCHAR(32) NOT NULL DEFAULT '' COMMENT '语言',
  timezone VARCHAR(64) NOT NULL DEFAULT '' COMMENT '时区',
  request_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '链路请求 ID，用于排查问题',
  edge_country_code VARCHAR(3) NOT NULL DEFAULT '' COMMENT '边缘国家编码',
  status VARCHAR(32) NOT NULL COMMENT '业务状态',
  decision VARCHAR(32) NOT NULL DEFAULT '' COMMENT '决策',
  country_code VARCHAR(3) NOT NULL DEFAULT '' COMMENT '国家或地区编码',
  failure_reason VARCHAR(128) NOT NULL DEFAULT '' COMMENT '失败原因',
  locked_by VARCHAR(128) NULL COMMENT '当前锁定 worker',
  locked_until_ms BIGINT NULL COMMENT '锁定截止时间，UTC epoch ms',
  attempt_count INT NOT NULL DEFAULT 0 COMMENT '尝试次数',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, job_id),
  KEY idx_login_ip_risk_jobs_status (app_code, status, updated_at_ms),
  KEY idx_login_ip_risk_jobs_session (app_code, session_id),
  KEY idx_login_ip_risk_jobs_user (app_code, user_id, created_at_ms),
  KEY idx_login_ip_risk_jobs_ip (app_code, client_ip_hash, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录 IP风险任务表';

CREATE TABLE IF NOT EXISTS login_ip_risk_decisions (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  decision_id VARCHAR(96) NOT NULL COMMENT '决策 ID',
  client_ip VARCHAR(64) NOT NULL COMMENT '客户端IP',
  client_ip_hash VARCHAR(128) NOT NULL COMMENT '客户端IP哈希值',
  decision VARCHAR(32) NOT NULL COMMENT '决策',
  country_code VARCHAR(3) NOT NULL DEFAULT '' COMMENT '国家或地区编码',
  confidence INT NOT NULL DEFAULT 0 COMMENT '置信度',
  provider_attempts_json JSON NOT NULL COMMENT '提供方尝试记录 JSON 配置或快照',
  decided_at_ms BIGINT NOT NULL COMMENT '决策时间，UTC epoch ms',
  expires_at_ms BIGINT NOT NULL COMMENT '过期时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  PRIMARY KEY (app_code, decision_id),
  KEY idx_login_ip_risk_decisions_ip (app_code, client_ip_hash, decided_at_ms),
  KEY idx_login_ip_risk_decisions_country (app_code, country_code, decided_at_ms),
  KEY idx_login_ip_risk_decisions_expires (app_code, expires_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录 IP风险决策表';

CREATE TABLE IF NOT EXISTS login_risk_country_blocks (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  block_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '拦截 ID',
  keyword VARCHAR(128) NOT NULL COMMENT '关键词',
  country_code VARCHAR(3) NOT NULL DEFAULT '' COMMENT '国家或地区编码',
  enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '启用',
  created_by_user_id BIGINT NULL COMMENT '创建人用户 ID',
  updated_by_user_id BIGINT NULL COMMENT '更新人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (block_id),
  UNIQUE KEY uk_login_risk_country_blocks_keyword (app_code, keyword),
  KEY idx_login_risk_country_blocks_enabled (app_code, enabled, country_code),
  KEY idx_login_risk_country_blocks_updated (app_code, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录风险国家拦截表';

CREATE TABLE IF NOT EXISTS login_risk_ip_whitelist (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  whitelist_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '白名单 ID',
  ip_address VARCHAR(64) NOT NULL COMMENT '允许跳过登录地区屏蔽的客户端 IP',
  enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '启用',
  created_by_user_id BIGINT NULL COMMENT '创建人用户 ID',
  updated_by_user_id BIGINT NULL COMMENT '更新人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (whitelist_id),
  UNIQUE KEY uk_login_risk_ip_whitelist_ip (app_code, ip_address),
  KEY idx_login_risk_ip_whitelist_enabled (app_code, enabled, ip_address),
  KEY idx_login_risk_ip_whitelist_updated (app_code, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录风险 IP 白名单表';

CREATE TABLE IF NOT EXISTS login_risk_user_whitelist (
  app_code VARCHAR(32) NOT NULL COMMENT '应用编码，用于多租户隔离',
  whitelist_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '白名单 ID',
  user_id BIGINT NOT NULL COMMENT '允许跳过登录地区屏蔽复核的用户 ID',
  enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '启用',
  created_by_user_id BIGINT NULL COMMENT '创建人用户 ID',
  updated_by_user_id BIGINT NULL COMMENT '更新人用户 ID',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (whitelist_id),
  UNIQUE KEY uk_login_risk_user_whitelist_user (app_code, user_id),
  KEY idx_login_risk_user_whitelist_enabled (app_code, enabled, user_id),
  KEY idx_login_risk_user_whitelist_updated (app_code, updated_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='登录风险用户白名单表';

CREATE TABLE IF NOT EXISTS user_devices (
  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码，用于多租户隔离',
  user_id BIGINT NOT NULL COMMENT '用户 ID',
  device_id VARCHAR(128) NOT NULL COMMENT '设备 ID，与 auth_sessions.device_id 同源',
  platform VARCHAR(32) NOT NULL DEFAULT '' COMMENT '平台，android/ios',
  device_model VARCHAR(128) NOT NULL DEFAULT '' COMMENT '设备型号，例如 Redmi Note 8 Pro',
  manufacturer VARCHAR(64) NOT NULL DEFAULT '' COMMENT '设备厂商，例如 Xiaomi/Apple',
  os_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '系统版本',
  imei VARCHAR(64) NOT NULL DEFAULT '' COMMENT '设备 IMEI，Android 10+ 无特权权限时为空',
  app_version VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最近一次认证客户端版本',
  build_number VARCHAR(64) NOT NULL DEFAULT '' COMMENT '最近一次认证客户端构建号',
  first_seen_at_ms BIGINT NOT NULL COMMENT '首次认证成功时间，UTC epoch ms',
  last_seen_at_ms BIGINT NOT NULL COMMENT '最近认证成功时间，UTC epoch ms',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
  PRIMARY KEY (app_code, user_id, device_id),
  KEY idx_user_devices_user_last_seen (app_code, user_id, last_seen_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户设备档案表，认证成功后按设备聚合最新上报资料';
