type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";

const LEVEL_VALUES: Record<LogLevel, number> = {
  trace: 10,
  debug: 20,
  info: 30,
  warn: 40,
  error: 50,
  fatal: 60,
};

function createLogger(bindings: Record<string, unknown> = {}, initialLevel: LogLevel = "info") {
  let currentLevel = initialLevel;

  function shouldLog(level: LogLevel): boolean {
    return LEVEL_VALUES[level] >= LEVEL_VALUES[currentLevel];
  }

  function formatMessage(level: LogLevel, arg: unknown, msg?: string): string {
    const timestamp = new Date().toISOString();

    let message: string;
    const meta: Record<string, unknown> = { ...bindings };

    if (typeof arg === "string") {
      message = arg;
    } else if (arg !== null && typeof arg === "object") {
      // Pino-style: log(obj, msg?) — strip internal pino keys
      const {
        msg: m,
        level: _l,
        time: _t,
        pid: _p,
        hostname: _h,
        req: _req,
        res: _res,
        reqId,
        ...rest
      } = arg as Record<string, unknown>;
      message = msg || (typeof m === "string" ? m : "");
      if (reqId) meta.reqId = reqId;
      Object.assign(meta, rest);
    } else {
      message = String(arg ?? "");
    }

    const parts = [`[${timestamp}]`, `[${level.toUpperCase()}]`, message];
    let result = parts.join(" ");

    const metaEntries = Object.entries(meta).filter(([k]) => k !== "reqId");
    const reqId = meta.reqId;
    if (reqId) result = `[${timestamp}] [${level.toUpperCase()}] [${reqId}] ${message}`;

    if (metaEntries.length > 0) {
      result += " " + JSON.stringify(Object.fromEntries(metaEntries));
    }

    return result;
  }

  function write(level: LogLevel, arg: unknown, msg?: string) {
    if (!shouldLog(level)) return;
    const formatted = formatMessage(level, arg, msg);
    switch (level) {
      case "trace":
      case "debug":
        console.debug(formatted);
        break;
      case "info":
        console.info(formatted);
        break;
      case "warn":
        console.warn(formatted);
        break;
      case "error":
      case "fatal":
        console.error(formatted);
        break;
    }
  }

  return {
    get level() {
      return currentLevel;
    },
    set level(l: string) {
      currentLevel = l as LogLevel;
    },
    trace: (arg: unknown, msg?: string) => write("trace", arg, msg),
    debug: (arg: unknown, msg?: string) => write("debug", arg, msg),
    info: (arg: unknown, msg?: string) => write("info", arg, msg),
    warn: (arg: unknown, msg?: string) => write("warn", arg, msg),
    error: (arg: unknown, msg?: string) => write("error", arg, msg),
    fatal: (arg: unknown, msg?: string) => write("fatal", arg, msg),
    silent: () => {},
    child: (newBindings: Record<string, unknown>) =>
      createLogger({ ...bindings, ...newBindings }, currentLevel),
  };
}

export const logger = createLogger({}, (process.env.LOG_LEVEL as LogLevel) || "info");
