/**
 * Frontend error telemetry service.
 *
 * Provides centralized, structured error logging with correlation IDs
 * for distributed tracing across frontend and backend systems.
 *
 * Privacy & Security:
 * - NEVER log passwords, tokens, session IDs, or sensitive user data
 * - Use `redact` utility to sanitize URLs and objects before logging
 * - PII should only be logged with explicit developer intent
 * - Telemetry is logged to console (development) or backend (production-ready)
 */

/**
 * Severity levels for telemetry events, matching backend structlog levels.
 */
export type TelemetrySeverity = "debug" | "info" | "warning" | "error" | "critical";

/**
 * Structured telemetry event.
 *
 * All telemetry is captured in a structured format that mirrors backend
 * structlog patterns, enabling consistent analysis across frontend and backend.
 */
export interface TelemetryEvent {
  /** Event name in snake_case (e.g., "api_error", "component_render_error"). */
  event: string;
  /** Severity level matching structlog conventions. */
  severity: TelemetrySeverity;
  /** Correlation ID for tracing across systems. */
  correlation_id?: string;
  /** Human-readable message. */
  message?: string;
  /** Optional error instance for stack traces and error info. */
  error?: Error;
  /** Additional structured context (must not contain PII). */
  context?: Record<string, unknown>;
  /** Timestamp when the event occurred. */
  timestamp: string;
}

/**
 * Telemetry event handler callback.
 * Called when a telemetry event is recorded.
 */
type TelemetryHandler = (event: TelemetryEvent) => void;

/** Registered telemetry handlers (initially console logger). */
let handlers: TelemetryHandler[] = [logToConsole];

/**
 * Log telemetry event to browser console.
 * In development, this provides immediate visibility to errors.
 * @internal
 */
function logToConsole(event: TelemetryEvent): void {
  const prefix = `[${event.severity.toUpperCase()}] ${event.event}`;
  const correlation = event.correlation_id ? ` [${event.correlation_id}]` : "";

  const args = [
    `${prefix}${correlation}`,
    event.message || "",
    event.context || {},
    event.error ? event.error : "",
  ].filter((arg) => arg !== "");

  switch (event.severity) {
    case "debug":
      console.debug(...args);
      break;
    case "info":
      console.info(...args);
      break;
    case "warning":
      console.warn(...args);
      break;
    case "error":
    case "critical":
      console.error(...args);
      break;
  }
}

/**
 * Register a custom telemetry handler.
 * Handlers are called when telemetry events are recorded.
 * @param handler - Callback to invoke on telemetry events.
 */
export function registerTelemetryHandler(handler: TelemetryHandler): void {
  handlers.push(handler);
}

/**
 * Clear all telemetry handlers and reinstall the console logger.
 * Useful for testing or resetting telemetry in single-page app contexts.
 */
export function resetTelemetryHandlers(): void {
  handlers = [logToConsole];
}

/**
 * Dispatch a telemetry event to all registered handlers.
 * @internal
 */
function dispatch(event: TelemetryEvent): void {
  for (const handler of handlers) {
    try {
      handler(event);
    } catch (e) {
      // Prevent telemetry errors from crashing the app
      console.error("Telemetry handler error:", e);
    }
  }
}

/**
 * Record a debug-level telemetry event.
 */
export function recordDebug(
  event: string,
  message?: string,
  context?: Record<string, unknown>,
  correlationId?: string,
): void {
  dispatch({
    event,
    severity: "debug",
    message,
    context,
    correlation_id: correlationId,
    timestamp: new Date().toISOString(),
  });
}

/**
 * Record an info-level telemetry event.
 */
export function recordInfo(
  event: string,
  message?: string,
  context?: Record<string, unknown>,
  correlationId?: string,
): void {
  dispatch({
    event,
    severity: "info",
    message,
    context,
    correlation_id: correlationId,
    timestamp: new Date().toISOString(),
  });
}

/**
 * Record a warning-level telemetry event.
 */
export function recordWarning(
  event: string,
  message?: string,
  context?: Record<string, unknown>,
  correlationId?: string,
): void {
  dispatch({
    event,
    severity: "warning",
    message,
    context,
    correlation_id: correlationId,
    timestamp: new Date().toISOString(),
  });
}

/**
 * Record an error-level telemetry event.
 * @param event - Event name in snake_case.
 * @param error - Error instance (will extract message and stack trace).
 * @param context - Optional structured context.
 * @param correlationId - Optional correlation ID for distributed tracing.
 */
export function recordError(
  event: string,
  error: Error,
  context?: Record<string, unknown>,
  correlationId?: string,
): void {
  dispatch({
    event,
    severity: "error",
    message: error.message,
    error,
    context,
    correlation_id: correlationId,
    timestamp: new Date().toISOString(),
  });
}

/**
 * Record a critical-level telemetry event.
 * Use for unrecoverable errors that require immediate attention.
 */
export function recordCritical(
  event: string,
  error: Error,
  context?: Record<string, unknown>,
  correlationId?: string,
): void {
  dispatch({
    event,
    severity: "critical",
    message: error.message,
    error,
    context,
    correlation_id: correlationId,
    timestamp: new Date().toISOString(),
  });
}

/**
 * Redact sensitive data from URLs and objects for safe logging.
 * Replaces passwords, tokens, and sensitive query parameters.
 * @param url - URL or string to redact.
 * @returns Safely redacted string.
 */
export function redact(url: string): string {
  try {
    const urlObj = new URL(url);

    // Redact sensitive query parameters
    const sensitiveParams = ["password", "token", "api_key", "secret", "key"];
    for (const param of sensitiveParams) {
      if (urlObj.searchParams.has(param)) {
        urlObj.searchParams.set(param, "[REDACTED]");
      }
    }

    return urlObj.toString();
  } catch {
    // If URL parsing fails, use regex-based approach for relative URLs
    return url.replace(
      /[?&](password|token|api_key|secret|key)=[^&]*/gi,
      (_match, param: string) => `?${param}=[REDACTED]`,
    );
  }
}

/**
 * Redact sensitive fields from an object for safe logging.
 * @param obj - Object to redact.
 * @returns New object with sensitive fields replaced with [REDACTED].
 */
export function redactObject(obj: Record<string, unknown>): Record<string, unknown> {
  const sensitiveFields = [
    "password",
    "token",
    "api_key",
    "secret",
    "key",
    "Authorization",
    "X-API-Key",
    "bangui_session",
  ];

  const redacted: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(obj)) {
    if (sensitiveFields.includes(key)) {
      redacted[key] = "[REDACTED]";
    } else if (typeof value === "string" && value.includes("://")) {
      redacted[key] = redact(value);
    } else {
      redacted[key] = value;
    }
  }
  return redacted;
}
