import Redis from "ioredis";

type RedisLike = {
  set(...args: unknown[]): Promise<string | null | undefined>;
};

const globalForRedis = global as unknown as { redis?: RedisLike };
const configuredRedisUrl = process.env.REDIS_URL?.trim();

class InMemoryRedisDedupe implements RedisLike {
  private entries = new Map<string, number>();

  private cleanup(now: number) {
    for (const [key, expiryAt] of this.entries) {
      if (expiryAt <= now) {
        this.entries.delete(key);
      }
    }
  }

  async set(...args: unknown[]): Promise<string | null | undefined> {
    const [keyArg, , modeArg, ttlSecondsArg, conditionArg] = args;
    const key = String(keyArg);
    const mode = typeof modeArg === "string" ? modeArg : undefined;
    const ttlSeconds = typeof ttlSecondsArg === "number" ? ttlSecondsArg : undefined;
    const condition = typeof conditionArg === "string" ? conditionArg : undefined;

    const now = Date.now();
    this.cleanup(now);

    if (condition === "NX" && this.entries.has(key)) {
    return null;
    }

    if (mode && mode !== "EX") {
      return null;
    }

    const ttl = ttlSeconds && ttlSeconds > 0 ? ttlSeconds * 1000 : 10 * 60 * 1000;
    this.entries.set(key, now + ttl);
    return "OK";
  }
}

export const redis: RedisLike =
  globalForRedis.redis ||
  (() => {
    if (!configuredRedisUrl) {
      if (process.env.NODE_ENV !== "test") {
        console.info("[traffic-monitor] REDIS_URL is not set, fallback to in-memory dedupe." );
      }
      return new InMemoryRedisDedupe();
    }

    const redisClient = new Redis(configuredRedisUrl, {
      maxRetriesPerRequest: 2,
      enableOfflineQueue: true,
      retryStrategy: (times) => {
        if (times > 8) {
          return 2000;
        }
        return Math.min(times * 200, 1000);
      },
      reconnectOnError: (error) => {
        const message = error.message || "";
        if (message.includes("READONLY")) return true;
        if (message.includes("ECONNREFUSED")) return true;
        if (message.includes("ENETUNREACH")) return true;
        return false;
      },
    });

    return redisClient;
  })();

if (!globalForRedis.redis && configuredRedisUrl) {
  let lastWarnAt = 0;
  (redis as Redis).on("error", (error: Error) => {
    const now = Date.now();
    if (now - lastWarnAt < 60_000) return;
    lastWarnAt = now;
    console.warn("[Redis error]", error.message || String(error));
  });
}

if (process.env.NODE_ENV !== "production") {
  globalForRedis.redis = redis;
}
