/**
 * OTP 验证码存储抽象层。
 *
 * 路由策略:
 *  - Redis 可用 → 走 Redis (生产, 多实例, 跨重启)
 *  - Redis 缺失 → 降级到进程内 Map (dev 联调; 单实例; 重启丢失但 5min TTL 无所谓)
 *
 * 不能"Redis 缺失就直接通过校验" —— 那会复活之前的安全漏洞。
 * 必须真实生成 / 真实存储 / 真实核对。
 */
import { getRedis } from "./redis";

const TTL_SECONDS = 300;
const redisKey = (phone: string) => `sms:otp:${phone}`;

// 通过 globalThis 防止 Next dev HMR 重置模块导致 Map 丢失
declare global {
  // eslint-disable-next-line no-var
  var __otpMemStore: Map<string, { code: string; expiresAt: number }> | undefined;
}
const mem: Map<string, { code: string; expiresAt: number }> =
  globalThis.__otpMemStore ?? (globalThis.__otpMemStore = new Map());

/** 清理 mem 里过期的条目, 防止长跑后内存膨胀 */
function sweep() {
  const now = Date.now();
  for (const [phone, entry] of mem) {
    if (entry.expiresAt <= now) mem.delete(phone);
  }
}

/** 存验证码 (覆盖该手机号的旧码) */
export async function storeOtp(phone: string, code: string): Promise<void> {
  const redis = getRedis();
  if (redis) {
    await redis.set(redisKey(phone), code, "EX", TTL_SECONDS);
    return;
  }
  sweep();
  mem.set(phone, { code, expiresAt: Date.now() + TTL_SECONDS * 1000 });
}

/**
 * 核对验证码;
 * 通过则立即销毁该码 (防止重放), 返回 true;
 * 失败 (码不存在 / 已过期 / 不匹配) 返回 false。
 */
export async function consumeOtp(phone: string, code: string): Promise<boolean> {
  const redis = getRedis();
  if (redis) {
    const stored = await redis.get(redisKey(phone));
    if (!stored || stored !== code) return false;
    await redis.del(redisKey(phone));
    return true;
  }
  const entry = mem.get(phone);
  if (!entry) return false;
  if (Date.now() > entry.expiresAt) {
    mem.delete(phone);
    return false;
  }
  if (entry.code !== code) return false;
  mem.delete(phone);
  return true;
}
