/**
 * Structured request logging middleware. Emits one record per request with a
 * request id, method, path, status, and duration — as pretty text (dev) or JSON
 * (production/log aggregation). The request id is stored on `ctx.locals` so
 * downstream handlers can correlate their own logs.
 */

import type { Context, Middleware } from "./context.ts";

export interface RequestRecord {
  time: string;
  id: string;
  method: string;
  path: string;
  status: number;
  durationMs: number;
}

export interface RequestLoggerOptions {
  /** "pretty" (default) for humans, "json" for machines. */
  format?: "pretty" | "json";
  /** Where each finished record goes. Default console.log. */
  sink?: (line: string, record: RequestRecord) => void;
  /** ctx.locals key for the request id. Default "requestId". */
  requestIdKey?: string;
  /** Clock injection for tests. Default Date.now. */
  now?: () => number;
}

export function requestLogger(options: RequestLoggerOptions = {}): Middleware {
  const format = options.format ?? "pretty";
  const sink = options.sink ?? ((line) => console.log(line));
  const idKey = options.requestIdKey ?? "requestId";
  const now = options.now ?? Date.now;

  return async (ctx: Context, next) => {
    const start = now();
    const id = (ctx.locals[idKey] as string | undefined) ?? crypto.randomUUID();
    ctx.locals[idKey] = id;

    let status = 500;
    try {
      const res = await next();
      status = res.status;
      return res;
    } finally {
      const record: RequestRecord = {
        time: new Date(start).toISOString(),
        id,
        method: ctx.req.method,
        path: ctx.url.pathname,
        status,
        durationMs: now() - start,
      };
      sink(format === "json" ? JSON.stringify(record) : formatPretty(record), record);
    }
  };
}

function formatPretty(r: RequestRecord): string {
  return `${r.method} ${r.path} → ${r.status} ${r.durationMs}ms [${r.id.slice(0, 8)}]`;
}
