/**
 * Authentication primitives.
 *
 * Passwords are hashed with argon2id via `Bun.password`. Sessions ride on the
 * existing cookie-backed `SessionStore`: logging a user in stores a serializable
 * user object under the "user" key, and `sessionAuth` hydrates `ctx.user` from
 * it on every request. `requireAuth` is a guard middleware for protected routes.
 */

import type { Context, Middleware } from "./context.ts";

/** Session key under which the authenticated user is stored. */
export const SESSION_USER_KEY = "user";

/** Hash a plaintext password (argon2id). Store the returned string. */
export function hashPassword(password: string): Promise<string> {
  return Bun.password.hash(password);
}

/** Verify a plaintext password against a stored hash. Safe against bad hashes. */
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
  if (!hash) return false;
  try {
    return await Bun.password.verify(password, hash);
  } catch {
    return false;
  }
}

/** Persist the authenticated user in the session and on the context. */
export function logIn<U = unknown>(ctx: Context, user: U): void {
  // Regenerate the session id first so a pre-login (possibly attacker-planted)
  // id can't be reused post-login — defends against session fixation.
  ctx.session.regenerate();
  ctx.session.set(SESSION_USER_KEY, user);
  ctx.user = user;
}

/** Clear the session and forget the current user. */
export function logOut(ctx: Context): void {
  ctx.session.clear();
  ctx.user = null;
}

/**
 * The currently-authenticated user, or null. Reads `ctx.user` first (set by
 * `sessionAuth`/`logIn`), falling back to the session store.
 */
export function getUser<U = unknown>(ctx: Context): U | null {
  if (ctx.user != null) return ctx.user as U;
  const fromSession = ctx.session.get<U>(SESSION_USER_KEY);
  return fromSession ?? null;
}

/**
 * Hydrate `ctx.user` from the session for every request. Register this early in
 * the middleware chain so downstream pages and API routes can read `ctx.user`.
 */
export function sessionAuth(): Middleware {
  return (ctx, next) => {
    ctx.user = ctx.session.get(SESSION_USER_KEY) ?? null;
    return next();
  };
}

export interface RequireAuthOptions {
  /** Where to redirect unauthenticated page requests. Default "/login". */
  loginPath?: string;
}

/**
 * Guard that requires an authenticated user. Unauthenticated requests that look
 * like an API/fetch call get a 401 JSON response; page navigations get a 302
 * redirect to the login page with the original target preserved as `?next=`.
 */
export function requireAuth(options: RequireAuthOptions = {}): Middleware {
  const loginPath = options.loginPath ?? "/login";
  return (ctx, next) => {
    if (getUser(ctx) != null) return next();
    if (wantsJson(ctx)) {
      return Response.json({ ok: false, error: "Unauthorized" }, { status: 401 });
    }
    const target = encodeURIComponent(ctx.url.pathname + ctx.url.search);
    return new Response(null, {
      status: 302,
      headers: { Location: `${loginPath}?next=${target}` },
    });
  };
}

function wantsJson(ctx: Context): boolean {
  if (ctx.url.pathname.startsWith("/api/")) return true;
  const accept = ctx.req.headers.get("accept") ?? "";
  return accept.includes("application/json") && !accept.includes("text/html");
}
