import { createClient } from "redis";

export type CacheLogFn = (msg: string) => void;

export type CacheStore = {
  getJson<T>(key: string): Promise<T | undefined>;
  setJson(key: string, value: unknown, ttlSeconds?: number): Promise<void>;
  flush(): Promise<void>;
  close(): Promise<void>;
};

type RedisCacheOptions = {
  url: string;
  defaultTtlSeconds: number;
  namespace?: string;
  log?: CacheLogFn;
};

function nsKey(namespace: string | undefined, key: string) {
  return namespace ? `${namespace}:${key}` : key;
}

export async function createRedisCache(opts: RedisCacheOptions): Promise<CacheStore> {
  const log = opts.log;
  const client = createClient({ url: opts.url });

  client.on("error", (err) => {
    log?.(`cache: redis error (${String(err)})`);
  });

  await client.connect();

  return {
    async getJson<T>(key: string) {
      const k = nsKey(opts.namespace, key);
      const raw = await client.get(k);
      if (raw == null) {
        log?.(`cache: miss ${k}`);
        return undefined;
      }
      log?.(`cache: hit ${k}`);
      try {
        return JSON.parse(raw) as T;
      } catch {
        // Bad cache entry: treat as miss.
        return undefined;
      }
    },

    async setJson(key: string, value: unknown, ttlSeconds?: number) {
      const k = nsKey(opts.namespace, key);
      const ttl = Math.max(1, Math.floor(ttlSeconds ?? opts.defaultTtlSeconds));
      const raw = JSON.stringify(value);
      await client.set(k, raw, { EX: ttl });
    },

    async flush() {
      await client.flushDb();
    },

    async close() {
      try {
        await client.quit();
      } catch {
        // ignore
      }
    },
  };
}

export function resolveRedisUrlFromEnv(env: NodeJS.ProcessEnv): string | undefined {
  const url = env.CACHE_REDIS_URL;
  if (url) return url;

  const host = env.CACHE_REDIS_HOST;
  const port = env.CACHE_REDIS_PORT;
  const db = env.CACHE_REDIS_DB;

  if (!host) return undefined;
  const p = port ? Number(port) : 6379;
  const d = db ? Number(db) : 0;
  if (!Number.isFinite(p) || !Number.isFinite(d)) return undefined;
  return `redis://${host}:${p}/${d}`;
}

export function resolveDefaultTtlSecondsFromEnv(env: NodeJS.ProcessEnv): number {
  const raw = env.CACHE_DEFAULT_TTL_SECONDS;
  const n = raw ? Number(raw) : NaN;
  if (Number.isFinite(n) && n > 0) return Math.floor(n);
  return 3600;
}

