import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { trace } from '@opentelemetry/api';
import { GrpcMetadataBuilder } from './grpc-metadata.builder';
import type { AiServiceConfig } from '../../config/check-ai-service-config';

/**
 * Locks the metadata-injection contract per ADR-0024 §"Metadata contract":
 *
 *   - x-client-id      ← AI_SERVICE_CLIENT_ID env (via AI_CONFIG).
 *   - x-correlation-id ← active OTel span's trace-id when present;
 *                        explicit `correlationId` override wins over
 *                        the span; otherwise a fresh UUID is minted
 *                        so the AI service can still join its audit
 *                        row to a unique id even when the portal
 *                        request is outside a trace.
 */

const CONFIG: AiServiceConfig = {
  endpoint: 'apf-ai-service:8080',
  clientId: 'apf-portal-test',
  useTls: false,
};

let activeSpan: ReturnType<typeof trace.getActiveSpan> = undefined;
let getActiveSpanSpy: ReturnType<typeof jest.spyOn>;

beforeEach(() => {
  activeSpan = undefined;
  getActiveSpanSpy = jest.spyOn(trace, 'getActiveSpan').mockImplementation(() => activeSpan);
});

afterEach(() => {
  getActiveSpanSpy.mockRestore();
});

describe('GrpcMetadataBuilder', () => {
  it('always emits x-client-id from the config', () => {
    const builder = new GrpcMetadataBuilder(CONFIG);
    const meta = builder.build();
    expect(meta.get('x-client-id')).toEqual(['apf-portal-test']);
  });

  it('uses the active OTel trace-id as x-correlation-id when a span is active', () => {
    activeSpan = {
      spanContext: () => ({
        traceId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
        spanId: 'bbbbbbbbbbbbbbbb',
        traceFlags: 1,
      }),
    } as ReturnType<typeof trace.getActiveSpan>;

    const builder = new GrpcMetadataBuilder(CONFIG);
    const meta = builder.build();
    expect(meta.get('x-correlation-id')).toEqual(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa']);
  });

  it('prefers the explicit options.correlationId over the active span', () => {
    activeSpan = {
      spanContext: () => ({
        traceId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
        spanId: 'bbbbbbbbbbbbbbbb',
        traceFlags: 1,
      }),
    } as ReturnType<typeof trace.getActiveSpan>;

    const builder = new GrpcMetadataBuilder(CONFIG);
    const meta = builder.build({ correlationId: 'caller-supplied-id' });
    expect(meta.get('x-correlation-id')).toEqual(['caller-supplied-id']);
  });

  it('falls back to a UUID v4 when no span is active and no override is passed', () => {
    const builder = new GrpcMetadataBuilder(CONFIG);
    const meta = builder.build();
    const correlationIds = meta.get('x-correlation-id') as string[];
    expect(correlationIds).toHaveLength(1);
    expect(correlationIds[0]).toMatch(
      /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
    );
  });

  it('returns a fresh Metadata instance on every call (no shared mutation)', () => {
    const builder = new GrpcMetadataBuilder(CONFIG);
    const a = builder.build();
    const b = builder.build();
    expect(a).not.toBe(b);
  });
});
