/**
 * Logging hook utilities for @beignet/core/server
 */

import type { HttpContractConfig } from "../../contracts/index.js";
import {
  resolveTrustedRequest,
  type TrustedRequestInfo,
} from "../trusted-proxy.js";
import type { HttpRequestLike, ServerHook } from "../types.js";
import { getRequestIdFromContext } from "./utils.js";

/**
 * Minimal logger shape accepted by `createLoggingHooks(...)`.
 */
export interface Logger {
  /**
   * Log an informational event.
   */
  info: (...args: unknown[]) => void;
  /**
   * Log an error event.
   */
  error: (...args: unknown[]) => void;
  /**
   * Log a warning event.
   */
  warn?: (...args: unknown[]) => void;
  /**
   * Log a debug event.
   */
  debug?: (...args: unknown[]) => void;
}

/**
 * Logging hook configuration.
 */
export interface LoggingConfig<Ctx> {
  /**
   * Logger used when custom lifecycle callbacks are not provided.
   */
  logger?: Logger;
  /**
   * Response header name used to expose `ctx.requestId` when present.
   */
  requestIdHeader?: string;
  /**
   * Custom request-start observer.
   */
  onRequestStart?: (args: {
    ctx?: Ctx;
    req: HttpRequestLike;
    requestInfo: TrustedRequestInfo;
    contract?: HttpContractConfig;
  }) => void;
  /**
   * Custom request-end observer.
   */
  onRequestEnd?: (args: {
    ctx?: Ctx;
    req: HttpRequestLike;
    requestInfo: TrustedRequestInfo;
    res: { status: number; headers: Record<string, string> };
    durationMs: number;
    contract?: HttpContractConfig;
    error?: unknown;
  }) => void;
}

function loggingRequestInfo(
  req: HttpRequestLike,
  requestInfo: TrustedRequestInfo | undefined,
): TrustedRequestInfo {
  return requestInfo ?? resolveTrustedRequest(req);
}

/**
 * Create request logging hooks.
 *
 * Logging observer errors are intentionally ignored so logging cannot break
 * request handling.
 */
export function createLoggingHooks<Ctx>(
  config: LoggingConfig<Ctx>,
): ServerHook<Ctx> {
  const requestIdHeader = config.requestIdHeader;

  return {
    name: "logging",
    onRequest: ({ req, requestInfo, contract }) => {
      if (config.onRequestStart) {
        try {
          config.onRequestStart({
            req,
            requestInfo: loggingRequestInfo(req, requestInfo),
            contract,
            ctx: undefined,
          });
        } catch {
          // Ignore logging errors
        }
        return undefined;
      }

      if (!config.logger) return undefined;
      try {
        config.logger.info(
          {
            method: req.method,
            url: loggingRequestInfo(req, requestInfo).url.pathname,
          },
          "Request start",
        );
      } catch {
        // Ignore logging errors
      }
      return undefined;
    },
    ...(requestIdHeader
      ? {
          beforeSend: ({ ctx, response }) => {
            const requestId = getRequestIdFromContext(ctx);
            if (!requestId) {
              return response;
            }

            return {
              ...response,
              headers: {
                ...(response.headers ?? {}),
                [requestIdHeader]: String(requestId),
              },
            };
          },
        }
      : {}),
    afterSend: ({
      ctx,
      req,
      requestInfo,
      response,
      durationMs,
      contract,
      error,
    }) => {
      const headers = response.headers ?? {};
      if (config.onRequestEnd) {
        try {
          config.onRequestEnd({
            ctx,
            req,
            requestInfo: loggingRequestInfo(req, requestInfo),
            res: { status: response.status, headers },
            durationMs,
            contract,
            error,
          });
        } catch {
          // Ignore logging errors
        }
        return;
      }

      if (!config.logger) return;
      try {
        const requestId = getRequestIdFromContext(ctx);
        const payload = {
          method: req.method,
          url: loggingRequestInfo(req, requestInfo).url.pathname,
          status: response.status,
          durationMs: Math.round(durationMs),
          ...(requestId !== undefined ? { requestId } : {}),
          ...(error !== undefined ? { error } : {}),
        };
        if (error !== undefined) {
          config.logger.error(payload, "Request complete with error");
          return;
        }
        config.logger.info(payload, "Request complete");
      } catch {
        // Ignore logging errors
      }
    },
  };
}
