import { timingSafeEqual } from "node:crypto";
import { HttpError } from "@batch-cooking/error-tools";
import { ErrorCode } from "@batch-cooking/shared";
import type { NextFunction, Request, Response } from "express";
import { env } from "../config/env.js";

/** Header `services/tech-step-llm-worker` sends its shared secret on. Not `Authorization`/a bearer scheme — this isn't a user session, just one internal caller authenticating to another, same "one flat shared secret" shape as e.g. a webhook signing header. */
const INTERNAL_WORKER_SECRET_HEADER = "x-internal-worker-secret";

/**
 * Express middleware guarding `/internal/tech-steps/*` — the surface
 * `services/tech-step-llm-worker` (a process outside this monorepo, no
 * Prisma access of its own, see that service's own README) reads
 * low-confidence NLP clauses and pending `StepTechStepCorrection`s from,
 * and posts `TechStepTrainingSuggestion`s back to. Never reachable by an
 * end user's session cookie — deliberately a *different* auth mechanism
 * than {@link requireAuth} (`require-auth.ts`), not layered on top of it,
 * since the worker has no `UserProfile`/session of its own to authenticate
 * as.
 *
 * Fails closed: an unset `INTERNAL_WORKER_SECRET` (the default in any
 * environment that doesn't run the worker, see `config/env.ts`) rejects
 * every request rather than leaving the surface open, same posture as a
 * misconfigured `JWT_SECRET` would if it had a working fallback.
 *
 * @throws {HttpError} `401 NOT_AUTHENTICATED` if the header is missing,
 *   wrong, or the server has no secret configured at all — never
 *   distinguishes the reason, same posture as {@link requireAuth}.
 */
export function requireInternalWorker(req: Request, _res: Response, next: NextFunction): void {
  const provided = req.header(INTERNAL_WORKER_SECRET_HEADER);
  if (env.INTERNAL_WORKER_SECRET === undefined || provided === undefined) {
    next(new HttpError(401, ErrorCode.NOT_AUTHENTICATED, "Not authenticated"));
    return;
  }

  // `timingSafeEqual` throws on mismatched buffer lengths rather than
  // returning `false` — checked separately first. A length mismatch alone
  // already means "not equal", so this loses no timing-attack protection
  // (an attacker learns nothing beyond what a differing length itself
  // already reveals, no different from `!==` on the common case where the
  // secret's real length isn't a secret worth protecting).
  const expected = Buffer.from(env.INTERNAL_WORKER_SECRET);
  const actual = Buffer.from(provided);
  if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) {
    next(new HttpError(401, ErrorCode.NOT_AUTHENTICATED, "Not authenticated"));
    return;
  }
  next();
}
