import type { Handler } from 'hono';
import { getUsers, getUserById, updateUser, deleteUser, USER_ROLES, OWNER_USER_ID } from 'officerdb';
import type { UserRole } from 'officerdb';
import * as errors from '@@/custom-errors';

// Owner-only management of the other accounts. Everything here is gated by ownerGate in
// users-router.ts; these handlers assume the caller is the Super Admin.
//
// The password hash never leaves this file — listing users is not a reason to hand them out.

type PublicUser = {
  id: number;
  email: string;
  name: string | null;
  username: string | null;
  avatar: string | null;
  status: string;
  role: string;
  createdAt: Date;
  /** True for the bootstrap account. The UI uses it to lock the row; the database enforces it. */
  isOwner: boolean;
};

const toPublicUser = (u: Awaited<ReturnType<typeof getUsers>>[number]): PublicUser => ({
  id: u.id,
  email: u.email,
  name: u.name,
  username: u.username,
  avatar: u.avatar,
  status: u.status,
  role: u.role,
  createdAt: u.createdAt,
  isOwner: u.id === OWNER_USER_ID,
});

export const listUsersHandler: Handler = async function (ctx) {
  const users = await getUsers();
  return ctx.json({
    users: users.sort((a, b) => a.id - b.id).map(toPublicUser),
    roles: USER_ROLES,
    ownerId: OWNER_USER_ID,
  });
};

export const updateUserRoleHandler: Handler = async function (ctx) {
  const id = Number(ctx.req.param('id'));
  if (!Number.isInteger(id) || id < 1) throw errors.BAD_REQUEST('Invalid user id');

  const { role } = ctx.get('body') as { role?: unknown };
  if (typeof role !== 'string' || !(USER_ROLES as readonly string[]).includes(role)) {
    throw errors.BAD_REQUEST(`Role must be one of: ${USER_ROLES.join(', ')}`);
  }

  // ck_users_owner_is_super_admin would reject this anyway — the point of catching it here is the
  // message. A raw CHECK violation surfaces as a 500 with Postgres wording, which tells the person
  // clicking the dropdown nothing about why.
  if (id === OWNER_USER_ID && role !== 'Super Admin') {
    throw errors.FORBIDDEN('The server owner cannot be demoted.');
  }

  const existing = await getUserById(id);
  if (!existing) throw errors.NOT_FOUND('User not found');

  const updated = await updateUser(id, { role: role as UserRole });
  if (!updated) throw errors.NOT_FOUND('User not found');
  return ctx.json({ user: toPublicUser(updated) });
};

export const deleteUserHandler: Handler = async function (ctx) {
  const id = Number(ctx.req.param('id'));
  if (!Number.isInteger(id) || id < 1) throw errors.BAD_REQUEST('Invalid user id');

  // deleteUser() throws for the owner regardless; this turns it into a 403 with a sentence rather than
  // an unhandled error.
  if (id === OWNER_USER_ID) throw errors.FORBIDDEN('The server owner cannot be removed.');

  const existing = await getUserById(id);
  if (!existing) throw errors.NOT_FOUND('User not found');

  // Deleting a user cascades: passkeys, dashboards, screens, email accounts, playlists, everything keyed
  // to them. There is no undo, which is why the UI asks first.
  await deleteUser(id);
  return ctx.json({ ok: true });
};
