import { createRouter } from '../../create-router';
import { listDavAppPasswords, createDavAppPassword, revokeDavAppPassword, deleteDavAppPassword } from 'officerdb';
import * as errors from '../../custom-errors';
import { stashIosProfile } from './ios-profile';

// Management of DAV app passwords, for Officer's own UI. Behind userMiddleware like everything else
// under /api — this is the owner administering their devices from a logged-in browser, which is a
// completely different act from a phone syncing (that is /dav, see sync-router.ts).
//
// The plaintext credential exists for exactly one response and is never stored, so POST is the only
// place it appears. There is deliberately no "show me it again" endpoint: if it is lost, revoke the
// row and mint another. That is cheaper than any design where the secret can be read back.

export const davRouter = createRouter();

davRouter.get('/passwords', async (ctx) => {
  const user = ctx.get('user');
  return ctx.json({ passwords: await listDavAppPasswords(user.id) });
});

davRouter.post('/passwords', async (ctx) => {
  const user = ctx.get('user');
  const body = ctx.get('body') as { label?: string } | undefined;
  const label = body?.label?.trim();
  if (!label) throw errors.BAD_REQUEST('label is required');

  const { entry, password } = await createDavAppPassword(user.id, label);
  // `password` is returned once, here. Nothing else in the system can produce it again.
  return ctx.json({ entry, password, username: user.email });
});

// One-tap iOS setup: mint a credential, wrap it in a configuration profile, hand back a URL for the app
// to open in Safari. See docs/mobile-dav-provisioning.md §3, and ios-profile.ts for why the profile is
// held in memory behind a one-shot token rather than stored.
//
// This is the only endpoint that returns a route to a live password instead of the password itself. It
// exists because the alternative is the owner typing a 25-character secret into a phone keyboard twice.
davRouter.post('/provision/ios', async (ctx) => {
  const user = ctx.get('user');
  const body = ctx.get('body') as { deviceLabel?: string } | undefined;
  const deviceLabel = body?.deviceLabel?.trim();
  if (!deviceLabel) throw errors.BAD_REQUEST('deviceLabel is required');

  // The profile has to name a host, and PUBLIC_URL is the only place that knows the one a phone can
  // actually reach — the request's own Host header is whatever the reverse proxy forwarded.
  const publicUrl = process.env.PUBLIC_URL;
  if (!publicUrl) throw errors.INTERNAL_SERVER_ERROR('PUBLIC_URL is not set; cannot build a profile');

  let url: URL;
  try {
    url = new URL(publicUrl);
  } catch {
    throw errors.INTERNAL_SERVER_ERROR('PUBLIC_URL is not a valid URL');
  }
  if (url.protocol !== 'https:') {
    // CalDAVUseSSL is hardcoded true in the payload, so an http PUBLIC_URL would produce a profile that
    // cannot work. Better to say so than to ship a phone an account that silently never syncs.
    throw errors.INTERNAL_SERVER_ERROR('PUBLIC_URL must be https to provision a device');
  }

  const { entry, password } = await createDavAppPassword(user.id, deviceLabel);

  const stored = await stashIosProfile(
    {
      host: url.hostname,
      port: Number(url.port) || 443,
      username: user.email,
      password,
      // The principal, not a collection: iOS discovers every calendar and address book under it, so a
      // collection the owner adds later appears without re-provisioning.
      principalUrl: `/dav/${user.id}/`,
      accountName: 'Officer',
    },
    url.origin,
  );

  console.log(`[dav] provisioned ios profile for "${deviceLabel}" (password #${entry.id}, signed=${stored.signed})`);
  return ctx.json(stored);
});

davRouter.post('/passwords/:id/revoke', async (ctx) => {
  const user = ctx.get('user');
  const id = Number(ctx.req.param('id'));
  if (!Number.isInteger(id)) throw errors.BAD_REQUEST('invalid id');
  if (!(await revokeDavAppPassword(user.id, id))) throw errors.NOT_FOUND('no such app password');
  return ctx.json({ ok: true });
});

davRouter.delete('/passwords/: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');
  if (!(await deleteDavAppPassword(user.id, id))) throw errors.NOT_FOUND('no such app password');
  return ctx.json({ ok: true });
});
