import { VerificationTokenRepository } from "@/internal/verification-tokens/verification-tokens.repository";
import {
  CONSOLE_SSO_TTL_MINUTES,
  VerificationTokenServices,
} from "@/internal/verification-tokens/verification-tokens.services";
import { env } from "./env";
import { logError } from "./logger";
import type { Context } from "hono";

/**
 * One-time sign-in links for console emails.
 *
 * A notification is only useful if acting on it is one click away, and an emailed
 * "open the console" link that dumps the reader on a sign-in form is not. These
 * links carry a single-use token that the console redeems for a normal session, so
 * the recipient lands on the page the mail is about, already signed in.
 *
 * Two properties matter and are enforced elsewhere:
 *
 *  - single-use, because the token is deleted by the read that claims it
 *    (`ConsumeToken`), not by a later delete
 *  - per-recipient, because the token names one console user. A notice sent to
 *    several super admins must therefore be sent as one mail each — a shared link
 *    would sign every reader in as whoever it was minted for.
 */

export { CONSOLE_SSO_TTL_MINUTES };

function consoleBaseUrl(): string {
  return env.GetString("ADMIN_CONSOLE_URL").replace(/\/+$/, "");
}

/**
 * Builds the link to put behind a button in an email.
 *
 * `userId` may be null — someone can be emailed who has no console account, or a
 * row may predate the id being recorded. In that case, and on any failure to mint,
 * the caller still gets a working deep link; the reader just signs in first. A
 * broken button would be worse than an ordinary one, so this never throws.
 */
export async function consoleSsoLink(
  c: Context,
  opts: { userId: string | null | undefined; next: string },
): Promise<string> {
  const base = consoleBaseUrl();
  const target = `${base}${opts.next}`;
  if (!opts.userId) return target;

  try {
    const services = VerificationTokenServices({
      VerificationTokenRepository: VerificationTokenRepository(c as any),
    });
    const { token } = await services.CreateConsoleSsoToken(opts.userId);
    return `${base}/sso?token=${encodeURIComponent(token)}&next=${encodeURIComponent(
      opts.next,
    )}`;
  } catch (err) {
    logError("Failed minting a console sign-in link; falling back to a plain link:", err);
    return target;
  }
}
