import { NodeSDK, tracing } from "@opentelemetry/sdk-node";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
import { PgInstrumentation } from "@opentelemetry/instrumentation-pg";
import { buildResource } from "./resource";
import { createSentryOtelBridge } from "./sentry-bridge";
import { PiiScrubSpanProcessor, PiiScrubLogRecordProcessor } from "./pii-scrub-processor";

const { BatchSpanProcessor } = tracing;

export type InitOtelServerNodeOpts = {
  /** Sentry DSN. When empty, OTel SDK boots without the Sentry exporter. */
  dsn: string;
  serviceName: string;
  serviceVersion?: string;
  environment: string;
  release?: string;
  namespace?: string;
};

/**
 * Initializes the OpenTelemetry NodeSDK for a server-side app.
 * - Configures Resource attributes per OTel semantic conventions.
 * - Registers PII scrub processors FIRST so all downstream exporters see clean data.
 * - Registers Sentry span processor (via createSentryOtelBridge) when DSN is set.
 * - Registers Sentry log record processor when DSN is set.
 * - Registers an in-process MeterProvider (no exporter — Sentry metrics not yet
 *   wired; a future phase or vendor-specific exporter can add a MetricReader).
 *
 * Caller is responsible for `sdk.shutdown()` on process exit.
 */
export function initOtelServerNode(opts: InitOtelServerNodeOpts): NodeSDK {
  const resource = buildResource({
    serviceName: opts.serviceName,
    serviceVersion: opts.serviceVersion,
    environment: opts.environment,
    namespace: opts.namespace,
  });

  const bridge = createSentryOtelBridge({ dsn: opts.dsn });

  // PiiScrubSpanProcessor runs FIRST so the Sentry exporter never sees raw PII.
  const spanProcessors = bridge.spanProcessor
    ? [
        new PiiScrubSpanProcessor(),
        // `as never` works around a TypeScript version conflict: `core-shared`'s direct
        // dep on `@opentelemetry/sdk-trace-base@1.30.1` has subtly incompatible types
        // vs the 1.28.0 bundled by `sdk-node@0.55.0`. The runtime objects are compatible;
        // the structural mismatch is type-only. Phase 1 implementer chose this rather than
        // constraining sdk-trace-base to 1.28.x to avoid losing future bug fixes.
        new BatchSpanProcessor(bridge.spanProcessor as never),
      ]
    : [new PiiScrubSpanProcessor()];

  // PiiScrubLogRecordProcessor runs FIRST for the same reason.
  // SentryLogRecordForwarder forwards synchronously (no batching wrapper needed).
  const logRecordProcessors = bridge.logRecordProcessor
    ? [new PiiScrubLogRecordProcessor(), bridge.logRecordProcessor]
    : [new PiiScrubLogRecordProcessor()];

  // In-process MeterProvider with no reader/exporter. Sentry metrics ingestion
  // is experimental in @sentry/opentelemetry 10.x and not wired here; metrics
  // emit through the API but are not exported anywhere. A future phase can add
  // a PeriodicExportingMetricReader when a vendor exporter is available.
  const metricReader = undefined;

  const sdk = new NodeSDK({
    resource,
    spanProcessors,
    logRecordProcessors,
    metricReader,
  });

  sdk.start();

  registerInstrumentations({
    instrumentations: [
      new HttpInstrumentation({
        requestHook: (span, request) => {
          const url = (request as { url?: string }).url ?? "";
          span.setAttribute("http.url.path", url.split("?")[0] ?? "");
        },
        ignoreIncomingRequestHook: (req) => {
          const url = (req as { url?: string }).url ?? "";
          return url === "/_health" || url === "/_otel-export";
        },
      }),
      new UndiciInstrumentation(),
      new PgInstrumentation({ enhancedDatabaseReporting: false }),
    ],
  });

  return sdk;
}
