import { Redis } from "ioredis";
import { logger } from "./logger.js";

const REDIS_URL = process.env["REDIS_URL"] ?? "redis://localhost:6380";
const KEY_PREFIX = "dashboard:";
const DEFAULT_TTL_SECONDS = 60;

let redis: Redis | null = null;

function getRedis(): Redis {
  if (!redis) {
    redis = new Redis(REDIS_URL, {
      lazyConnect: false,
      enableReadyCheck: false,
      // Don't let cache operations block the app if Redis is slow
      commandTimeout: 2000,
    });
    redis.on("error", (e: unknown) => {
      logger.warn({ err: e, redisUrl: REDIS_URL }, "Redis cache connection emitted an error");
    });
  }
  return redis;
}

/**
 * Retrieve a cached value by key.
 * Returns null on cache miss or if Redis is unavailable.
 */
export async function cacheGet<T>(key: string): Promise<T | null> {
  try {
    const raw = await getRedis().get(`${KEY_PREFIX}${key}`);
    if (raw === null) return null;
    return JSON.parse(raw) as T;
  } catch {
    // Redis down or parse error — fall through to DB
    return null;
  }
}

/**
 * Store a value in the cache with a TTL.
 * Silently ignores errors when Redis is unavailable.
 */
export async function cacheSet(
  key: string,
  value: unknown,
  ttlSeconds: number = DEFAULT_TTL_SECONDS,
): Promise<void> {
  try {
    await getRedis().set(
      `${KEY_PREFIX}${key}`,
      JSON.stringify(value),
      "EX",
      ttlSeconds,
    );
  } catch {
    // Redis down — silently ignore, data will be served from DB next time
  }
}

/**
 * Delete all keys matching a glob pattern (e.g. "dashboard:*").
 * The pattern is automatically prefixed with the KEY_PREFIX unless it already starts with it.
 */
export async function cacheInvalidate(pattern: string): Promise<void> {
  try {
    const fullPattern = pattern.startsWith(KEY_PREFIX)
      ? pattern
      : `${KEY_PREFIX}${pattern}`;
    const r = getRedis();
    let cursor = "0";
    do {
      const [nextCursor, keys] = await r.scan(
        cursor,
        "MATCH",
        fullPattern,
        "COUNT",
        100,
      );
      cursor = nextCursor;
      if (keys.length > 0) {
        await r.del(...keys);
      }
    } while (cursor !== "0");
  } catch {
    // Redis down — nothing to invalidate
  }
}

/**
 * Invalidate all dashboard cache entries.
 * Convenience wrapper used from mutation hooks.
 */
export async function invalidateDashboardCache(): Promise<void> {
  await cacheInvalidate("*");
}
