import { randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import { Metadata } from '@grpc/grpc-js';
import { trace } from '@opentelemetry/api';
import { AI_CONFIG } from './tokens';
import type { AiServiceConfig } from '../../config/check-ai-service-config';

/**
 * Builds the gRPC `Metadata` object every outbound call to
 * `apf-ai-service` must carry, per ADR-0024 §"Metadata contract".
 *
 * Two entries:
 *
 *   - `x-client-id` — deployment slug from `AI_SERVICE_CLIENT_ID`.
 *     Lets the AI service tag its audit rows and metrics with the
 *     calling deployment without inspecting the proto body.
 *   - `x-correlation-id` — W3C `trace-id` of the active OpenTelemetry
 *     span when one exists (per [ADR-0012](../../docs/decisions/0012-observability-pino-opentelemetry.md)),
 *     falling back to an explicit value if passed, falling back to a
 *     freshly-minted UUID v4 as a last resort. The AI service echoes
 *     this id in its audit log and propagates it to Jaeger, so the
 *     portal and AI traces can be joined cross-service.
 *
 * The builder is a class (not a static helper) so it composes with
 * NestJS DI and so it can be swapped for a fake in tests that need
 * to assert the metadata shape without standing up the OTel SDK.
 */
@Injectable()
export class GrpcMetadataBuilder {
  constructor(@Inject(AI_CONFIG) private readonly config: AiServiceConfig) {}

  /**
   * Construct the metadata for one outbound call.
   *
   * @param options.correlationId Override the auto-resolved value.
   *        Use when the caller already holds a stable id (for example
   *        the SSE bridge controller's request-scoped trace-id) and
   *        wants to bind multiple calls to the same correlation key.
   *        `undefined` is accepted (and equivalent to omitting the
   *        key) so callers can forward an optional value without
   *        a conditional spread.
   */
  build(options: { correlationId?: string | undefined } = {}): Metadata {
    const meta = new Metadata();
    meta.set('x-client-id', this.config.clientId);

    const correlationId =
      options.correlationId ?? trace.getActiveSpan()?.spanContext().traceId ?? randomUUID();
    meta.set('x-correlation-id', correlationId);

    return meta;
  }
}
