import { DatabaseError } from "@/lib/error";
import { logError } from "@/lib/logger";
import type { RepositoryContext } from "../datastore/repository";
import { BaseRepository } from "../datastore/repository";
import { nanoid } from "nanoid";

export const VerificationTokenRepository = (ctx: RepositoryContext) => {
  const { datastore } = new BaseRepository(ctx);

  const CreateToken = async (entry: {
    userId: string;
    kind: string;
    expiresInDays?: number;
    /** Takes precedence over `expiresInDays`, for short-lived tokens. */
    expiresInMinutes?: number;
  }) => {
    try {
      const token = nanoid(32);
      const expires = new Date();
      if (entry.expiresInMinutes) {
        expires.setMinutes(expires.getMinutes() + entry.expiresInMinutes);
      } else {
        expires.setDate(expires.getDate() + (entry.expiresInDays ?? 7));
      }

      const newEntry = await datastore
        .insertInto("verification_tokens")
        .values({
          token,
          user_id: entry.userId as any, // ID is uuid in console_users but verification_tokens.user_id might be expected differently?
          kind: entry.kind,
          expires: expires.toISOString(),
        })
        .returningAll()
        .executeTakeFirstOrThrow();

      return newEntry;
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to create verification token",
      });
    }
  };

  const FindToken = async (token: string, kind: string) => {
    try {
      const record = await datastore
        .selectFrom("verification_tokens")
        .selectAll()
        .where("token", "=", token)
        .where("kind", "=", kind)
        .executeTakeFirst();
      return record;
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to fetch verification token",
      });
    }
  };

  /**
   * Fetches and deletes a token in a single statement.
   *
   * `FindToken` then `DeleteToken` leaves a window where two requests both read
   * the row and both succeed. For a token that logs someone in, single-use has to
   * be real, so the delete itself is the claim: only one caller gets a row back.
   * Expiry is judged by the caller on the returned row.
   */
  const ConsumeToken = async (token: string, kind: string) => {
    try {
      const record = await datastore
        .deleteFrom("verification_tokens")
        .where("token", "=", token)
        .where("kind", "=", kind)
        .returningAll()
        .executeTakeFirst();
      return record;
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to consume verification token",
      });
    }
  };

  const DeleteToken = async (id: string) => {
    try {
      await datastore
        .deleteFrom("verification_tokens")
        .where("id", "=", id as any)
        .execute();
    } catch (err) {
      logError(err);
      throw new DatabaseError({
        error: err,
        message: "Failed to delete verification token",
      });
    }
  };

  return {
    CreateToken,
    FindToken,
    ConsumeToken,
    DeleteToken,
  };
};

export type TVerificationTokenRepository = ReturnType<typeof VerificationTokenRepository>;
