import { createClient, type RedisClientType } from "redis";
import { env } from "./env.js";
import { logger } from "../shared/observability/logger.js";

let client: RedisClientType | null = null;

/**
 * 获取 Redis 客户端（单例）。
 * REDIS_URL 未配置时返回 null，调用方需做 null 检查。
 */
export function getRedisClient(): RedisClientType | null {
  if (!env.REDIS_URL) return null;
  if (!client) {
    client = createClient({ url: env.REDIS_URL });
    client.on("error", (err) => {
      logger.error({ err: err.message }, "Redis client error");
    });
  }
  return client;
}

/**
 * 连接 Redis（非阻塞：连接失败不影响服务启动）。
 */
export async function connectRedis(): Promise<void> {
  const c = getRedisClient();
  if (!c) {
    logger.warn("REDIS_URL not set, Redis disabled");
    return;
  }
  if (c.isOpen) return;
  try {
    await c.connect();
    logger.info("Redis connected");
  } catch (err) {
    logger.warn(
      { err: err instanceof Error ? err.message : String(err) },
      "Redis connect failed, running without Redis",
    );
  }
}

/**
 * 关闭 Redis 连接。
 */
export async function disconnectRedis(): Promise<void> {
  if (client && client.isOpen) {
    await client.quit();
    logger.info("Redis disconnected");
  }
}

/**
 * Redis 健康检查（用于 /readyz）。
 */
export async function isRedisHealthy(): Promise<boolean> {
  if (!client || !client.isOpen) return false;
  try {
    const pong = await client.ping();
    return pong === "PONG";
  } catch {
    return false;
  }
}

/**
 * 分布式锁（用于作业提交幂等性保护）。
 *
 * 使用 SET NX EX 实现：
 *   - 成功获取锁返回 true
 *   - 锁已被持有返回 false
 *
 * 锁自动过期（TTL），避免死锁。
 */
export async function acquireLock(
  key: string,
  ttlSeconds: number,
): Promise<boolean> {
  const c = getRedisClient();
  if (!c || !c.isOpen) return false;
  try {
    const result = await c.set(key, "1", { NX: true, EX: ttlSeconds });
    return result === "OK";
  } catch {
    return false;
  }
}

export async function releaseLock(key: string): Promise<void> {
  const c = getRedisClient();
  if (!c || !c.isOpen) return;
  try {
    await c.del(key);
  } catch {
    // 释放失败不影响业务（锁会自动过期）
  }
}
