import type { OfficerContext } from './routes';
import { badRequest, notFound, methodNotAllowed, readJson } from './routes';
import { activeClient } from './active';
import { toUser, toNode, arrayField, type OfficerUser } from './normalize';

// User routes — /_officer/users/*. A Headscale "user" is a namespace that owns nodes and pre-auth keys.
//
// The list is enriched with a node count, which the admin API does not provide: deleting a user takes its
// nodes with it, so "3 nodes" next to the delete button is the difference between an informed action and a
// surprise. That is one extra upstream call for the whole list, not one per user.

export type UserWithCounts = OfficerUser & { nodeCount: number; onlineCount: number };

async function listUsers(ctx: OfficerContext): Promise<Response> {
  const client = await activeClient(ctx.userId);
  if (client instanceof Response) return client;

  const [userBody, nodeBody] = await Promise.all([client.call('/api/v1/user'), client.call('/api/v1/node')]);

  const nodes = arrayField(nodeBody, 'nodes').map(toNode);
  const counts = new Map<string, { total: number; online: number }>();
  for (const node of nodes) {
    const id = node.user?.id;
    if (!id) continue;
    const entry = counts.get(id) ?? { total: 0, online: 0 };
    entry.total += 1;
    if (node.online) entry.online += 1;
    counts.set(id, entry);
  }

  const users: UserWithCounts[] = arrayField(userBody, 'users')
    .map(toUser)
    .filter((u): u is OfficerUser => !!u)
    .map((u) => ({ ...u, nodeCount: counts.get(u.id)?.total ?? 0, onlineCount: counts.get(u.id)?.online ?? 0 }))
    .sort((a, b) => a.name.localeCompare(b.name));

  return Response.json({ users });
}

async function createUser(ctx: OfficerContext): Promise<Response> {
  const client = await activeClient(ctx.userId);
  if (client instanceof Response) return client;

  const body = await readJson(ctx.req);
  if (!body) return badRequest('expected a JSON body');
  const name = typeof body.name === 'string' ? body.name.trim() : '';
  if (!name) return badRequest('name is required');

  const created = await client.call<{ user?: Record<string, unknown> }>('/api/v1/user', {
    method: 'POST',
    body: {
      name,
      displayName: typeof body.displayName === 'string' ? body.displayName.trim() : undefined,
      email: typeof body.email === 'string' ? body.email.trim() : undefined,
    },
  });
  return Response.json({ user: toUser(created.user) }, { status: 201 });
}

type UserActionParams = { ctx: OfficerContext; id: string; action: string | undefined };

async function handleUserAction({ ctx, id, action }: UserActionParams): Promise<Response> {
  const client = await activeClient(ctx.userId);
  if (client instanceof Response) return client;

  if (action === 'rename') {
    if (ctx.req.method !== 'POST') return methodNotAllowed();
    const body = await readJson(ctx.req);
    if (!body) return badRequest('expected a JSON body');
    const name = typeof body.name === 'string' ? body.name.trim() : '';
    if (!name) return badRequest('name is required');
    // Rename takes both the id and the new name in the path — encode or a '/' becomes a routing accident.
    const renamed = await client.call<{ user?: Record<string, unknown> }>(
      `/api/v1/user/${encodeURIComponent(id)}/rename/${encodeURIComponent(name)}`,
      { method: 'POST' },
    );
    return Response.json({ user: toUser(renamed.user) });
  }

  if (action !== undefined) return notFound();

  if (ctx.req.method === 'DELETE') {
    // Headscale refuses to delete a user that still owns nodes, with a message the UI relays verbatim.
    await client.call(`/api/v1/user/${encodeURIComponent(id)}`, { method: 'DELETE' });
    return new Response(null, { status: 204 });
  }

  return methodNotAllowed();
}

/** Dispatch `/_officer/users/...`. `rest` is the path after `users`. */
export async function handleUsersRoute(ctx: OfficerContext, rest: string[]): Promise<Response> {
  if (rest.length === 0) {
    if (ctx.req.method === 'GET') return listUsers(ctx);
    if (ctx.req.method === 'POST') return createUser(ctx);
    return methodNotAllowed();
  }
  if (rest.length > 2) return notFound();

  const id = rest[0];
  if (!id || !/^\d+$/.test(id)) return badRequest('user id must be numeric');

  return handleUserAction({ ctx, id, action: rest[1] });
}
