import { createRouter } from '../../create-router';
import { createApiKey, listApiKeys, revokeApiKey } from 'officerdb';
import { mintApiKey } from '../../auth-token';
import * as errors from '../../custom-errors';

// A user's own API keys: mint, list, revoke. Every route acts on `ctx.get('user').id` and nothing accepts
// a userId from the caller, so there is no shape of request here that reaches another account's keys.
//
// The plaintext secret exists for exactly one response. There is deliberately no "show it to me again"
// endpoint — if a key is lost, revoke it and mint another. Same reasoning as the DAV app passwords beside
// this, and the same reason the column holds a hash.
//
// A key currently carries its holder's FULL authority: it resolves to the user, and authorization then
// asks the same questions of that user it would have asked of a browser session. That is not an
// escalation — it can do exactly what the password could already do — but it does mean a leaked key is a
// leaked account, which is why revocation is one call and last-used is recorded. Narrowing a key to a
// subset of its holder's capabilities is the next step and wants a `scopes` column, not a change here.

export const apiKeysRouter = createRouter();

const MAX_NAME_LENGTH = 100;

apiKeysRouter.get('/', async (ctx) => {
  const user = ctx.get('user');
  return ctx.json({ keys: await listApiKeys(user.id) });
});

apiKeysRouter.post('/', async (ctx) => {
  const user = ctx.get('user');
  const body = ctx.get('body') as { name?: string; expiresInDays?: number } | undefined;

  const name = body?.name?.trim();
  if (!name) throw errors.BAD_REQUEST('name is required');
  if (name.length > MAX_NAME_LENGTH) throw errors.BAD_REQUEST(`name must be ${MAX_NAME_LENGTH} characters or fewer`);

  // Absent means "does not expire" — the common case is a key living in a phone the owner keeps. An
  // expiry, when asked for, is enforced in SQL during lookup rather than by anything that has to run.
  let expiresAt: Date | null = null;
  const days = body?.expiresInDays;
  if (days !== undefined && days !== null) {
    if (!Number.isInteger(days) || days < 1) throw errors.BAD_REQUEST('expiresInDays must be a positive integer');
    expiresAt = new Date(Date.now() + days * 24 * 60 * 60 * 1000);
  }

  const { secret, keyHash, prefix } = mintApiKey();
  const entry = await createApiKey({ userId: user.id, name, keyHash, prefix, expiresAt });

  const { keyHash: _hash, ...safe } = entry;
  // `key` is returned here and nowhere else. Nothing in the system can produce it again.
  return ctx.json({ entry: safe, key: secret });
});

apiKeysRouter.delete('/:id', async (ctx) => {
  const user = ctx.get('user');
  const id = Number(ctx.req.param('id'));
  if (!Number.isInteger(id)) throw errors.BAD_REQUEST('invalid id');

  // Scoped by user inside the statement: naming someone else's key id revokes nothing and is answered
  // with the same 404 as an id that never existed.
  if (!(await revokeApiKey(id, user.id))) throw errors.NOT_FOUND('no such api key');
  return ctx.json({ ok: true });
});
