import { Hono } from 'hono';
import { getUserById } from 'officerdb';
import { CustomError } from '../../custom-errors';
import { bodyParser } from '../../_middlewares/body-parser';
import { emailRouter } from './routes';

// The email sidecar's own listener. `/api/email/*` on the platform is a proxy onto this — the platform
// authenticates the owner, injects X-Officer-User, and forwards without reading the body.
//
// The routes moved here verbatim, Hono and all, so this file's only real job is to reconstruct the three
// things the platform's middleware used to provide: the authenticated user on the context, the parsed
// body on the context, and an error handler that turns thrown custom-errors into status codes.

type HonoVariables = { user: { id: number }; body: Record<string, unknown>; origin: string };

const app = new Hono<{ Variables: HonoVariables }>();

// The platform's userMiddleware set the whole user row on the context from the JWT. The proxy injects only
// the id (X-Officer-User), so the rest is loaded here — and it has to be the WHOLE row, not just the id:
// the mail store is keyed by the owner's email (DATA_PATH/<owner>/email_accounts/<account>/emails.db), so
// a user object without `email` silently resolves to the wrong path and the mailbox reads as empty.
//
// Cached because this runs on every request and the owner does not change; single-user is a hard invariant.
let cachedUser: { id: number; email: string } | null = null;

app.use('*', async (ctx, next) => {
  const id = Number(ctx.req.header('X-Officer-User'));
  if (!Number.isFinite(id) || id <= 0) return ctx.json({ error: 'missing X-Officer-User' }, 401);

  if (!cachedUser || cachedUser.id !== id) {
    const row = await getUserById(id);
    if (!row?.email) return ctx.json({ error: `unknown user ${id}` }, 401);
    cachedUser = row as { id: number; email: string };
  }
  ctx.set('user', cachedUser as never);
  await next();
});

// Handlers read the body off the context (`ctx.get('body')`), which only the platform's middleware used to
// provide. Without this every write route here destructured undefined — `POST /accounts` threw on
// `body.provider` and surfaced as a bare "failed to add account" toast. `isProxiedPath` is inert in this
// process (the prefix set is populated by `createSidecarProxy`, which runs in the platform), so nothing is
// skipped; multipart on `/send` is parsed once and served from Hono's cache when the route asks again.
app.use('*', bodyParser());

app.route('/', emailRouter as never);

// Mirrors the platform's own handler (`hono.ts` onError) rather than inventing a second error shape. Two
// bugs lived in the version that didn't: custom-errors carry `statusCode`, NOT `status`, so every
// deliberate 4xx fell through to the 500 branch with its message replaced by "internal error"; and the
// body was JSON where the whole API answers errors as plain text, which `useClient` reads with
// `res.text()` — so even a correct message reached the client wrapped in `{"error":…}`.
app.onError((err, ctx) => {
  if (err instanceof CustomError) {
    if (err.returnValue) {
      return typeof err.returnValue === 'string'
        ? ctx.text(err.returnValue, err.statusCode)
        : ctx.json(err.returnValue, err.statusCode);
    }
    return ctx.text(err.message, err.statusCode);
  }
  console.error('[email] unhandled error', err);
  return ctx.text('Internal Server Error', 500);
});

export function startEmailServer(): number {
  const server = Bun.serve({
    port: 0,
    hostname: '127.0.0.1',
    // A from-scratch mailbox sync answers slowly; the platform proxy extends its own side to match.
    idleTimeout: 255,
    fetch: app.fetch,
  });
  const port = server.port;
  if (port == null) throw new Error('[email] failed to acquire a port');
  console.log(`[email] http server listening on http://127.0.0.1:${port}`);
  return port;
}
