import type { Handler } from 'hono';
import { getUserById, OWNER_USER_ID } from 'officerdb';
import * as errors from '@@/custom-errors';
import { validatePublicKey } from '@@/os-user-ssh';
import { provisionOsAccount } from './provision-os';

// POST /api/users/:id/provision-linux — give an existing account its Linux side, or repair it.
//
// One route for what are the same operation from the owner's point of view:
//
//   backfill  an account created before per-user Linux accounts existed, or while the host was not set up
//             for them, gets one now.
//   retry     the first attempt failed for a reason the owner has since fixed — the traversable-ancestor
//             chmod being the one everybody hits once.
//   re-key    a new inbound public key replaces the old `authorized_keys`.
//
// Before this, the answer to all three was "delete the account and create it again", which throws away the
// password, the dashboards and everything else keyed to the row to redo a retryable side effect.

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

  // The owner runs as the service user itself and its home is HOME_DIR — there is nothing to provision, and
  // creating a second Linux account for it would be actively confusing.
  if (id === OWNER_USER_ID) throw errors.BAD_REQUEST('The server owner already runs as the service account.');

  const user = await getUserById(id);
  if (!user) throw errors.NOT_FOUND('User not found');
  if (!user.username) throw errors.BAD_REQUEST('This account has no username to name a Linux user after.');

  // Optional. Absent means "leave authorized_keys as it is" rather than "remove inbound access": clearing a
  // key should be a deliberate act, not the consequence of submitting a form with an empty field.
  const body = (ctx.get('body') ?? {}) as Record<string, unknown>;
  const raw = typeof body.sshPublicKey === 'string' ? body.sshPublicKey.trim() : '';
  let inboundKey: string | null = null;
  if (raw) {
    const checked = validatePublicKey(raw);
    if (!checked.ok) throw errors.BAD_REQUEST(`SSH public key: ${checked.error}`);
    inboundKey = checked.key;
  }

  // Their CURRENT role, read from the row rather than taken from the request — this route is also how a
  // member promoted to Developer gets the database role they did not qualify for when the account was made.
  const result = await provisionOsAccount({
    userId: user.id,
    email: user.email,
    username: user.username,
    role: user.role,
    inboundKey,
  });

  // 200 with the error in the body rather than a 4xx: the interesting cases are partial. "The Linux account
  // exists and is confined but the keys failed" is not an error the caller should treat as nothing having
  // happened, and the UI has to be able to show both halves.
  return ctx.json(result);
};
