/**
 * Thin Redis cache wrapper for analytics read paths.
 *
 * All helpers are fire-and-forget on failure: they log and return null
 * rather than throwing. The analytics pipeline must never block because
 * Redis is unavailable.
 *
 * TTL default is 300 seconds (5 minutes). Users tolerate this much
 * staleness on historical analytics views; raise only with explicit approval.
 */

import { createClient } from "redis";
import type { RedisClientType } from "redis";

let client: RedisClientType | null = null;
let connectPromise: Promise<RedisClientType | null> | null = null;

async function getClient(): Promise<RedisClientType | null> {
  if (client?.isReady) return client;
  if (connectPromise) return connectPromise;

  const url = process.env.REDIS_URL;
  if (!url) {
    // Warn once at cold start; skip further noise in dev without Redis.
    if (process.env.NODE_ENV !== "test") {
      console.warn("[redis-cache] REDIS_URL not set, cache disabled");
    }
    return null;
  }

  connectPromise = (async () => {
    try {
      const c = createClient({ url }) as RedisClientType;
      c.on("error", (err: Error) => console.error("[redis-cache] client error", err.message));
      await c.connect();
      client = c;
      connectPromise = null;
      return c;
    } catch (e) {
      console.error("[redis-cache] connect failed", e instanceof Error ? e.message : e);
      connectPromise = null;
      return null;
    }
  })();

  return connectPromise;
}

export async function getCached<T>(key: string): Promise<T | null> {
  try {
    const c = await getClient();
    if (!c) return null;
    const raw = await c.get(key);
    return raw ? (JSON.parse(raw) as T) : null;
  } catch (e) {
    console.error("[redis-cache] get failed", key, e instanceof Error ? e.message : e);
    return null;
  }
}

export async function setCached<T>(
  key: string,
  value: T,
  ttlSeconds = 300,
): Promise<void> {
  try {
    const c = await getClient();
    if (!c) return;
    await c.set(key, JSON.stringify(value), { EX: ttlSeconds });
  } catch (e) {
    console.error("[redis-cache] set failed", key, e instanceof Error ? e.message : e);
  }
}

export async function deleteCached(key: string): Promise<void> {
  try {
    const c = await getClient();
    if (!c) return;
    await c.del(key);
  } catch (e) {
    console.error("[redis-cache] delete failed", key, e instanceof Error ? e.message : e);
  }
}

/**
 * Delete all keys with a given prefix. Safe at current scale (max ~10 keys
 * per brand). If cache key volume grows significantly, replace KEYS with a
 * SCAN-based iteration to avoid blocking the Redis event loop.
 */
export async function deleteCachedPrefix(prefix: string): Promise<void> {
  try {
    const c = await getClient();
    if (!c) return;
    const keys = await c.keys(`${prefix}*`);
    if (keys.length > 0) await c.del(keys);
  } catch (e) {
    console.error("[redis-cache] deletePrefix failed", prefix, e instanceof Error ? e.message : e);
  }
}
