// OpenTelemetry 手工注入 - 不依赖 K8s autoinstrumentation
//
// 通过 NodeSDK 手动配置 Resource、Exporter 和 Instrumentations，
// 支持通过环境变量或 .env 文件配置，不再依赖 K8s Operator 注入探针。

import { trace } from '@opentelemetry/api';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core';
import { PgInstrumentation } from '@opentelemetry/instrumentation-pg';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { NodeSDK } from '@opentelemetry/sdk-node';
import {
  BatchSpanProcessor,
  ReadableSpan,
  SpanProcessor,
} from '@opentelemetry/sdk-trace-base';
import {
  ATTR_SERVICE_NAME,
  ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
import { PrismaInstrumentation } from '@prisma/instrumentation';

// 从 DATABASE_URL 解析数据库连接信息，用于补充 Prisma span 的 peer 属性
function parseDbUrl(url?: string) {
  if (!url) return null;
  try {
    const parsed = new URL(url);
    return {
      host: parsed.hostname,
      port: parsed.port || '5432',
      name: parsed.pathname.replace('/', ''),
    };
  } catch {
    return null;
  }
}

// 自定义 SpanProcessor：为 Prisma 产生的 db span 补充连接信息
class PrismaDbSpanProcessor implements SpanProcessor {
  private inner: SpanProcessor;

  constructor(inner: SpanProcessor) {
    this.inner = inner;
  }

  onStart(span: ReadableSpan, parentContext: any) {
    this.inner.onStart(span as any, parentContext);
  }

  onEnd(span: ReadableSpan) {
    const dbInfo = parseDbUrl(process.env.DATABASE_URL);
    if (dbInfo && span.attributes['db.system'] === 'postgresql') {
      const mutableSpan = span as any;
      mutableSpan.attributes['net.peer.name'] = dbInfo.host;
      mutableSpan.attributes['net.peer.port'] = Number(dbInfo.port);
      mutableSpan.attributes['db.name'] = dbInfo.name;
      mutableSpan.attributes['db.instance'] = dbInfo.name;
      mutableSpan.attributes['db.ip'] = dbInfo.host;
      mutableSpan.attributes[
        'peer.service'
      ] = `${dbInfo.name}@${dbInfo.host}:${dbInfo.port}`;
      mutableSpan.attributes[
        'peer.filled'
      ] = `${dbInfo.name}@${dbInfo.host}:${dbInfo.port}`;
    }
    this.inner.onEnd(span);
  }

  shutdown() {
    return this.inner.shutdown();
  }

  forceFlush() {
    return this.inner.forceFlush();
  }
}

export function initializeTracing(): void {
  const otelEndpoint = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
  const serviceName =
    process.env.OTEL_SERVICE_NAME || `oit-erp-backend-${process.env.NODE_ENV}`;
  const otelToken = process.env.OTEL_RESOURCE_ATTRIBUTES_TOKEN;

  console.log('[Tracing] ========== OpenTelemetry 手工注入模式 ==========');
  console.log('[Tracing] NODE_ENV:', process.env.NODE_ENV);
  console.log('[Tracing] OTEL_SERVICE_NAME:', serviceName);
  console.log('[Tracing] OTEL_EXPORTER_OTLP_ENDPOINT:', otelEndpoint);
  console.log(
    '[Tracing] OTEL_RESOURCE_ATTRIBUTES_TOKEN:',
    otelToken ? '******(已配置)' : '未配置',
  );

  let sdk: NodeSDK | null = null;

  if (!otelEndpoint) {
    console.log(
      '[Tracing] OTEL_EXPORTER_OTLP_ENDPOINT 未配置，跳过 tracing 初始化',
    );
    return;
  }

  console.log('[Tracing] 正在初始化 OpenTelemetry SDK...');

  try {
    const resource = resourceFromAttributes({
      token: otelToken,
      [ATTR_SERVICE_NAME]: serviceName,
      [ATTR_SERVICE_VERSION]: process.env.npm_package_version || '0.0.1',
      ['deployment.environment']: process.env.NODE_ENV || 'development',
      'host.name': process.env.HOSTNAME,
    } as Record<string, string>);

    const traceExporter = new OTLPTraceExporter({
      url: otelEndpoint,
    });

    const spanProcessor = new PrismaDbSpanProcessor(
      new BatchSpanProcessor(traceExporter),
    );

    sdk = new NodeSDK({
      resource,
      spanProcessor,
      instrumentations: [
        new HttpInstrumentation({
          ignoreIncomingRequestHook: (req) => req.method === 'HEAD',
        }),
        new ExpressInstrumentation(),
        new NestInstrumentation(),
        new PgInstrumentation(),
        new PrismaInstrumentation(),
      ],
    });

    sdk.start();

    // 验证 TracerProvider 是否正常
    const tracer = trace.getTracer(serviceName);
    const testSpan = tracer.startSpan('tracing-init-test');
    const spanContext = testSpan.spanContext();

    console.log('[Tracing] SDK 启动成功');
    console.log(
      '[Tracing]   TracerProvider:',
      trace.getTracerProvider()?.constructor?.name,
    );
    console.log('[Tracing]   Test traceId:', spanContext.traceId);
    console.log('[Tracing]   Sampled:', spanContext.traceFlags === 1);

    testSpan.end();
  } catch (error) {
    console.error('[Tracing] 初始化失败:', error);
  }

  process.on('SIGTERM', async () => {
    if (sdk) {
      console.log('[Tracing] 正在关闭 OpenTelemetry SDK...');
      await sdk.shutdown();
      console.log('[Tracing] SDK 已关闭');
    }

    console.log(
      '[Tracing] ====================================================',
    );
  });
}

if (process.env.NODE_ENV !== 'development') {
  console.log('[Bootstrap] Initializing tracing...');
  initializeTracing();
  console.log('[Bootstrap] Tracing ready, starting NestJS application...');
}
