/**
 * Configure OpenAuth to use Redis DB as a
 * storage adapter.
 *
 * ```ts
 * import { RedisStorage } from "./src/storage.js"
 * import { Redis } from "@nestri/core/redis/client"
 *
 * const storage = RedisStorage({
 *   redis: Redis.Client.create()
 * })
 *
 *
 * export default issuer({
 *   storage,
 *   // ...
 * })
 * ```
 *
 * @packageDocumentation
 */
import { redis as RedisClient } from "bun";
import { joinKey, StorageAdapter } from "@openauthjs/openauth/storage/storage";

const separator = String.fromCharCode(0x1f);

/**
 * Configure the Redis connection that's created.
 */
export interface RedisStorageOptions {
  keyPrefix?: string;
  defaultTTL?: number;
  client: typeof RedisClient;
}

/**
 * Creates a Redis DB store.
 * @param options - The config for the adapter.
 */
export function RedisStorage(options: RedisStorageOptions): StorageAdapter {
  const { client } = options;
  const prefix = "auth";

  function createKey(key: string[]): string {
    return `${prefix}:${joinKey(key)}`;
  }

  function parseKey(redisKey: string): string[] {
    const withoutPrefix = redisKey.slice(prefix.length + 1);
    return withoutPrefix.split(separator);
  }

  return {
    async get(key: string[]) {
      const redisKey = createKey(key);
      const value = await client.get(redisKey);

      if (!value) return;

      return JSON.parse(value) as Record<string, any>;
    },

    async set(key: string[], value: any, expiry?: Date) {
      const redisKey = createKey(key);

      const ttlSeconds = expiry
        ? Math.max(Math.floor((expiry.getTime() - Date.now()) / 1000), 1)
        : undefined;

      if (ttlSeconds) {
        // SET key value EX ttlSeconds
        await client.set(redisKey, JSON.stringify(value), "EX", ttlSeconds);
      } else {
        // No expiry → simple SET
        await client.set(redisKey, JSON.stringify(value));
      }
    },

    async remove(key: string[]) {
      const redisKey = createKey(key);
      await client.del(redisKey);
    },

    async *scan(prefix: string[]) {
      const scanPattern = `${createKey(prefix)}${prefix.length ? separator : ""}*`;

      let cursor = "0";

      do {
        // SCAN cursor MATCH pattern COUNT 100
        const [nextCursor, keys] = await client.scan(
          cursor,
          "MATCH",
          scanPattern,
          "COUNT",
          100,
        );
        cursor = nextCursor;

        if (keys.length === 0) {
          continue;
        }
        const values = await client.mget(...keys);
        for (let i = 0; i < keys.length; i++) {
          const key = keys[i];
          if (!key) {
            continue;
          }
          const value = values[i];
          if (value !== null) {
            const parsedValue =
              typeof value === "string" ? JSON.parse(value) : value;
            yield [parseKey(key), parsedValue];
          }
        }
      } while (cursor !== "0");
    },
  };
}
