import { createId } from "@paralleldrive/cuid2";
import { dirAsync } from "fs-jetpack";
import lmdb, { RootDatabase, open } from "lmdb";
import { dirname, join } from "path";
import { dir } from "./dir";

const cuid = createId;
type SessionEntry<T> = { id: string; expired: number; data: T };

export type SRVCache<T> = ReturnType<typeof createCache<T>>;

export const createCache = <T>() => ({
  lmdb: null as unknown as RootDatabase<SessionEntry<T>>,
  cookieKey: "",
  async init(arg: { cookieKey: string; dbname?: string }) {
    const dbpath = dir.data(join(arg.dbname || "session") + ".lmdb");
    await dirAsync(dirname(dbpath));
    self(this).lmdb = open({
      path: dbpath,
      compression: true,
    });
    self(this).cookieKey = arg.cookieKey;
  },
  async new(data: any, expired?: Date): Promise<SessionEntry<T>> {
    const s = {
      id: cuid(),
      expired: expired ? expired.getTime() / 1000 : 0,
      data,
    };

    await self(this).lmdb.put(s.id, s);

    return s;
  },
  get<T>(req: string | Request): null | SessionEntry<T> {
    let id = "";
    if (typeof req === "string") {
      id = req;
    } else {
      const cookie = req.headers.get("cookie");
      if (cookie) {
        id = parseCookies(cookie)[self(this).cookieKey];
      }
    }

    if (!id) {
      return null;
    }
    const s = self(this).lmdb.get(id) as unknown as SessionEntry<T>;

    if (s) {
      if (s.expired !== 0 && Date.now() / 1000 > s.expired) {
        return null;
      }

      return s;
    }
    return null;
  },
  async set(id: string, data: any): Promise<SessionEntry<T>> {
    await self(this).lmdb.put(id, data);
    return data;
  },
  del(id: string) {
    return self(this).lmdb.remove(id);
  },
  keys() {
    return new Promise<lmdb.Key[]>((resolve) => {
      const keys: lmdb.Key[] = [];
      self(this)
        .lmdb.getKeys()
        .forEach((e) => {
          keys.push(e);
        });
      resolve(keys);
    });
  },
  clear() {
    self(this).lmdb.clearSync();
  },
  count() {
    return self(this).lmdb.getCount();
  },
});

export const session = createCache();
if (session.lmdb === null) {
  session.init({ cookieKey: "prasi-sid" });
}

const self = (me: Session) => me;
type Session = typeof session;

export function parseCookies(cookieHeader: string) {
  const list: Record<string, string> = {};
  if (!cookieHeader) return list;

  cookieHeader.split(`;`).forEach(function (cookie) {
    let [name, ...rest] = cookie.split(`=`);
    name = name?.trim();
    if (!name) return;
    const value = rest.join(`=`).trim();
    if (!value) return;
    list[name] = decodeURIComponent(value);
  });

  return list;
}
