SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 外管账号与主后台账号、App 用户账号完全隔离；所有唯一键都带 app_code，避免 Fami/Lalu 同名账号互相占用。
-- 本迁移只创建小型认证/审计表并按唯一索引写权限元数据，不扫描 users、房间或钱包等大业务表。
SET @now_ms = CAST(UNIX_TIMESTAMP(UTC_TIMESTAMP(3)) * 1000 AS UNSIGNED);

CREATE TABLE IF NOT EXISTS external_admin_accounts (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '外管账号 ID',
  app_code VARCHAR(32) NOT NULL COMMENT '固定 App 租户',
  linked_app_user_id BIGINT NOT NULL COMMENT '绑定的 App 内部用户 ID',
  username VARCHAR(64) NOT NULL COMMENT '外管登录账号，服务端统一转小写',
  password_hash VARCHAR(255) NOT NULL COMMENT 'bcrypt 密码摘要',
  permissions_json JSON NOT NULL COMMENT '允许进入外管白名单路由的权限快照',
  status VARCHAR(24) NOT NULL DEFAULT 'active' COMMENT 'active/disabled',
  password_change_required TINYINT(1) NOT NULL DEFAULT 1 COMMENT '管理员创建或重置后要求首次改密',
  failed_login_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '连续登录失败次数',
  locked_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '失败锁定截止时间，UTC epoch ms',
  last_login_at_ms BIGINT NULL COMMENT '最近成功登录时间，UTC epoch ms',
  created_by_admin_id BIGINT UNSIGNED NOT 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 (id),
  UNIQUE KEY uk_external_admin_accounts_app_username (app_code, username),
  UNIQUE KEY uk_external_admin_accounts_app_linked_user (app_code, linked_app_user_id),
  KEY idx_external_admin_accounts_app_status_updated (app_code, status, updated_at_ms),
  KEY idx_external_admin_accounts_creator (created_by_admin_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='独立外管账号';

CREATE TABLE IF NOT EXISTS external_admin_sessions (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '会话 ID',
  account_id BIGINT UNSIGNED NOT NULL COMMENT '外管账号 ID',
  app_code VARCHAR(32) NOT NULL COMMENT '创建时固化的 App 租户',
  token_hash CHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL COMMENT 'opaque session token SHA-256',
  csrf_token_hash CHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL COMMENT 'CSRF token SHA-256',
  ip VARCHAR(64) NOT NULL DEFAULT '' COMMENT '登录 IP',
  user_agent VARCHAR(512) NOT NULL DEFAULT '' COMMENT '登录客户端',
  expires_at_ms BIGINT NOT NULL COMMENT '过期时间，UTC epoch ms',
  last_seen_at_ms BIGINT NOT NULL COMMENT '最近访问时间，UTC epoch ms',
  revoked_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '撤销时间，0 表示有效',
  revoke_reason VARCHAR(64) NOT NULL DEFAULT '' COMMENT '撤销原因',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  PRIMARY KEY (id),
  UNIQUE KEY uk_external_admin_sessions_token_hash (token_hash),
  KEY idx_external_admin_sessions_account_active (account_id, revoked_at_ms, expires_at_ms),
  KEY idx_external_admin_sessions_app_expiry (app_code, expires_at_ms),
  CONSTRAINT fk_external_admin_sessions_account FOREIGN KEY (account_id) REFERENCES external_admin_accounts(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='外管服务端 opaque 会话';

CREATE TABLE IF NOT EXISTS external_admin_login_logs (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '登录日志 ID',
  account_id BIGINT UNSIGNED NULL COMMENT '命中账号时记录；未知账号保持 NULL 防止伪造关联',
  app_code VARCHAR(32) NOT NULL COMMENT '请求 App',
  username VARCHAR(64) NOT NULL COMMENT '请求账号',
  ip VARCHAR(64) NOT NULL DEFAULT '' COMMENT '请求 IP',
  user_agent VARCHAR(512) NOT NULL DEFAULT '' COMMENT '请求客户端',
  status VARCHAR(24) NOT NULL COMMENT 'success/failed',
  message VARCHAR(128) NOT NULL DEFAULT '' COMMENT '内部审计原因，不回传客户端',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  PRIMARY KEY (id),
  KEY idx_external_admin_login_logs_app_username_time (app_code, username, created_at_ms),
  KEY idx_external_admin_login_logs_account_time (account_id, created_at_ms),
  KEY idx_external_admin_login_logs_status_time (status, created_at_ms)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='外管登录日志';

CREATE TABLE IF NOT EXISTS external_admin_operation_logs (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '操作日志 ID',
  account_id BIGINT UNSIGNED NOT NULL COMMENT '外管账号 ID',
  app_code VARCHAR(32) NOT NULL COMMENT '会话固定 App',
  username VARCHAR(64) NOT NULL COMMENT '外管账号',
  request_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '请求链路 ID',
  action VARCHAR(160) NOT NULL COMMENT 'HTTP 方法与路由模板',
  resource VARCHAR(120) NOT NULL DEFAULT '' COMMENT '资源类型',
  resource_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '目标资源 ID',
  method VARCHAR(12) NOT NULL COMMENT 'HTTP 方法',
  path VARCHAR(255) NOT NULL COMMENT '实际请求路径',
  ip VARCHAR(64) NOT NULL DEFAULT '' COMMENT '请求 IP',
  user_agent VARCHAR(512) NOT NULL DEFAULT '' COMMENT '请求客户端',
  status VARCHAR(24) NOT NULL COMMENT 'success/failed',
  http_status INT NOT NULL COMMENT 'HTTP 状态码',
  latency_ms BIGINT NOT NULL DEFAULT 0 COMMENT '处理耗时',
  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
  PRIMARY KEY (id),
  KEY idx_external_admin_operation_logs_account_time (account_id, created_at_ms),
  KEY idx_external_admin_operation_logs_app_time (app_code, created_at_ms),
  KEY idx_external_admin_operation_logs_request (request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='外管业务操作审计';

INSERT INTO admin_permissions (name, code, kind, description, created_at_ms, updated_at_ms) VALUES
  ('外管用户查看', 'external-admin-user:view', 'menu', '查看当前 App 的外管用户', @now_ms, @now_ms),
  ('外管用户创建', 'external-admin-user:create', 'button', '按当前 App 用户短 ID 创建外管账号', @now_ms, @now_ms),
  ('外管用户状态', 'external-admin-user:status', 'button', '启用或停用当前 App 的外管账号', @now_ms, @now_ms),
  ('外管用户重置密码', 'external-admin-user:reset-password', 'button', '重置当前 App 的外管账号密码并撤销全部会话', @now_ms, @now_ms)
ON DUPLICATE KEY UPDATE
  name = VALUES(name),
  kind = VALUES(kind),
  description = VALUES(description),
  updated_at_ms = @now_ms;

INSERT INTO admin_menus (parent_id, title, code, path, icon, permission_code, sort, visible, created_at_ms, updated_at_ms)
SELECT parent.id, '外管用户', 'operation-external-admin-users', '/operations/external-admin-users', 'shield', 'external-admin-user:view', 72, TRUE, @now_ms, @now_ms
FROM admin_menus parent
WHERE parent.code = 'operations'
ON DUPLICATE KEY UPDATE
  parent_id = VALUES(parent_id),
  title = VALUES(title),
  path = VALUES(path),
  icon = VALUES(icon),
  permission_code = VALUES(permission_code),
  sort = VALUES(sort),
  visible = VALUES(visible),
  updated_at_ms = @now_ms;

-- 创建或重置密码等同于签发/接管固定的全能力外管账号，只允许平台管理员执行，
-- 避免运营角色通过委派获得自身权限矩阵中不存在的 VIP 发放等高风险能力。
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
SELECT role.id, permission.id
FROM admin_roles role
JOIN admin_permissions permission
WHERE role.code = 'platform-admin'
  AND permission.code IN (
    'external-admin-user:view',
    'external-admin-user:create',
    'external-admin-user:status',
    'external-admin-user:reset-password'
  );

-- 运营负责人和运营专员负责日常查看、启用和停用，不持有账号凭据签发能力。
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
SELECT role.id, permission.id
FROM admin_roles role
JOIN admin_permissions permission
WHERE role.code IN ('ops-admin', 'operations-specialist')
  AND permission.code IN (
    'external-admin-user:view',
    'external-admin-user:status'
  );
