import { env } from "../config/env.js";

/**
 * Server-side operational logging — the one place in this codebase allowed
 * to call `console.*` directly (see `biome.json`'s `noConsole`, which bans
 * bare `console.log` everywhere else: every log line here goes through a
 * named level instead, `logger.info(...)`/`logger.error(...)`, never an
 * unlabeled dump of text). Everything else (the request logger, `server.ts`'s
 * startup line, the error middleware) goes through this instead of touching
 * `console` itself, so there's exactly one place that decides the log
 * *shape* (structured JSON lines — one object per line, trivially
 * grep/parse-able by `docker logs`/Portainer or any log aggregator, unlike
 * free-form `console.log` text).
 *
 * A class, not a plain object literal — same convention as
 * `ErrorHandlerService` (`packages/error-tools`): `public` methods are the
 * actual API, `private` ones are internals a caller never touches directly.
 * `export const logger = new LoggerService()` below is the single shared
 * instance every caller imports — there's exactly one log stream for the
 * whole process, nothing to parameterize per call site, so nobody
 * constructs their own.
 */

/** Ascending severity — mirrors the standard `debug < info < warn < error` convention. */
export type LogLevel = "debug" | "info" | "warn" | "error";

/** Arbitrary structured context attached to a log line (a request id, a duration, an error's own fields, …) — merged into the emitted JSON object, never interpolated into the message string itself. */
export type LogMeta = Record<string, unknown>;

const LEVEL_SEVERITY: Record<LogLevel, number> = {
  debug: 0,
  info: 1,
  warn: 2,
  error: 3,
};

/**
 * Lines below this level are dropped rather than emitted — keeps `debug`
 * out of production (verbose, meant for local troubleshooting only) and
 * out of the test suite's own output (Mocha's reporter is noisy enough
 * already), while `pnpm dev:api` sees everything. A free function, not a
 * method — exported (unlike the rest of this module's internals) purely so
 * `logger.service.test.ts` can exercise the three `NODE_ENV` cases
 * directly, without needing a `LoggerService` instance at all.
 */
export function minLevelFor(nodeEnv: typeof env.NODE_ENV): LogLevel {
  if (nodeEnv === "production") return "info";
  if (nodeEnv === "test") return "warn";
  return "debug";
}

/** Console method each level writes through — `error`/`warn` go to stderr (their own native behavior), `debug`/`info` to stdout, the usual split log aggregators expect. */
const CONSOLE_METHOD: Record<LogLevel, "debug" | "info" | "warn" | "error"> = {
  debug: "debug",
  info: "info",
  warn: "warn",
  error: "error",
};

/** Server-side operational logger — see the module doc comment for why this exists and what it's for. */
export class LoggerService {
  /** Computed once at construction from `env.NODE_ENV` — see {@link minLevelFor}. */
  private readonly _minSeverity: number;

  public constructor(nodeEnv: typeof env.NODE_ENV = env.NODE_ENV) {
    this._minSeverity = LEVEL_SEVERITY[minLevelFor(nodeEnv)];
  }

  public debug(message: string, meta?: LogMeta): void {
    this._emit("debug", message, meta);
  }

  public info(message: string, meta?: LogMeta): void {
    this._emit("info", message, meta);
  }

  public warn(message: string, meta?: LogMeta): void {
    this._emit("warn", message, meta);
  }

  public error(message: string, meta?: LogMeta): void {
    this._emit("error", message, meta);
  }

  private _emit(level: LogLevel, message: string, meta?: LogMeta): void {
    if (LEVEL_SEVERITY[level] < this._minSeverity) return;

    // `meta` spread first so a caller accidentally passing e.g. `{ message:
    // ... }` in it can never shadow the line's own core fields.
    const line = {
      ...meta,
      timestamp: new Date().toISOString(),
      level,
      message,
    };
    // biome-ignore lint/suspicious/noConsole: this is the one place allowed to — see the class doc comment above.
    console[CONSOLE_METHOD[level]](JSON.stringify(line));
  }
}

/** Single shared instance — this service has no per-call-site state to isolate, same reasoning as `errorHandlerService`. */
export const logger = new LoggerService();
