import { Hono } from "hono";
import { hashPassword, issueToken, userFromAuthHeader, verifyPassword } from "../auth.js";
import type { Store } from "../db/store.js";

/** Accounts and sessions — GDD §9. */

const MIN_PASSWORD_LENGTH = 8;
const USERNAME_PATTERN = /^[a-zA-Z0-9_-]{3,24}$/;

export function authRoutes(store: Store) {
  const app = new Hono();

  app.post("/register", async (context) => {
    const body = (await context.req.json().catch(() => null)) as {
      username?: unknown;
      email?: unknown;
      password?: unknown;
      displayName?: unknown;
    } | null;

    const { username, email, password, displayName } = body ?? {};

    if (typeof username !== "string" || !USERNAME_PATTERN.test(username)) {
      return context.json({ error: "username must be 3-24 chars, alphanumeric/_/-" }, 400);
    }
    if (typeof email !== "string" || !email.includes("@")) {
      return context.json({ error: "a valid email is required" }, 400);
    }
    if (typeof password !== "string" || password.length < MIN_PASSWORD_LENGTH) {
      return context.json({ error: `password must be at least ${MIN_PASSWORD_LENGTH} chars` }, 400);
    }

    if (await store.findUserByUsername(username)) {
      return context.json({ error: "username already taken" }, 409);
    }

    const user = await store.createUser({
      username,
      email,
      displayName: typeof displayName === "string" ? displayName : username,
      passwordHash: await hashPassword(password),
    });

    return context.json(
      { token: issueToken(user.id), user: publicUser(user) },
      201,
    );
  });

  app.post("/login", async (context) => {
    const body = (await context.req.json().catch(() => null)) as {
      username?: unknown;
      password?: unknown;
    } | null;

    const { username, password } = body ?? {};
    if (typeof username !== "string" || typeof password !== "string") {
      return context.json({ error: "username and password are required" }, 400);
    }

    const user = await store.findUserByUsername(username);
    // Same response whether the user is missing or the password is wrong, so
    // this endpoint cannot be used to enumerate accounts.
    if (!user || !(await verifyPassword(password, user.passwordHash))) {
      return context.json({ error: "invalid credentials" }, 401);
    }

    await store.touchLogin(user.id);
    return context.json({ token: issueToken(user.id), user: publicUser(user) });
  });

  app.get("/me", async (context) => {
    const user = await userFromAuthHeader(store, context.req.header("authorization"));
    if (!user) return context.json({ error: "unauthorized" }, 401);
    return context.json({ user: publicUser(user) });
  });

  return app;
}

/** Never let `passwordHash` reach a response body. */
function publicUser(user: { id: string; username: string; displayName: string | null }) {
  return { id: user.id, username: user.username, displayName: user.displayName };
}
