import { randomBytes } from 'node:crypto';
import { describe, it, expect, beforeAll, afterAll, beforeEach } from '@jest/globals';
import { Test } from '@nestjs/testing';
import { AiClientModule } from './ai-client.module';
import { ChatClient } from './chat.client';
import { GrpcMetadataBuilder } from './grpc-metadata.builder';
import { IngestionClient } from './ingestion.client';
import { ModelsClient } from './models.client';
import { PrincipalMapper } from './principal.mapper';
import { RagClient } from './rag.client';
import {
  AI_CHAT_GRPC_CLIENT,
  AI_CONFIG,
  AI_CREDENTIALS,
  AI_INGESTION_GRPC_CLIENT,
  AI_MODELS_GRPC_CLIENT,
  AI_RAG_GRPC_CLIENT,
} from './tokens';

/**
 * Smoke-tests `AiClientModule.compile()` — every provider resolves,
 * the four exported wrapper clients are constructable, and the gRPC
 * stubs share the same credentials instance (i.e. the underlying
 * HTTP/2 channel can be multiplexed by grpc-js).
 *
 * Does NOT exercise the wire — the wire is covered by the per-client
 * fake-server specs.
 */

const STRONG_SALT = randomBytes(32).toString('base64url');
const ORIGINAL = {
  salt: process.env['LOG_USER_ID_SALT'],
  endpoint: process.env['AI_SERVICE_GRPC_ENDPOINT'],
  clientId: process.env['AI_SERVICE_CLIENT_ID'],
  tls: process.env['AI_SERVICE_GRPC_TLS'],
};

beforeAll(() => {
  process.env['LOG_USER_ID_SALT'] = STRONG_SALT;
  process.env['AI_SERVICE_GRPC_ENDPOINT'] = 'apf-ai-service:8080';
  process.env['AI_SERVICE_CLIENT_ID'] = 'apf-portal-test';
  process.env['AI_SERVICE_GRPC_TLS'] = 'false';
});

afterAll(() => {
  if (ORIGINAL.salt === undefined) delete process.env['LOG_USER_ID_SALT'];
  else process.env['LOG_USER_ID_SALT'] = ORIGINAL.salt;
  if (ORIGINAL.endpoint === undefined) delete process.env['AI_SERVICE_GRPC_ENDPOINT'];
  else process.env['AI_SERVICE_GRPC_ENDPOINT'] = ORIGINAL.endpoint;
  if (ORIGINAL.clientId === undefined) delete process.env['AI_SERVICE_CLIENT_ID'];
  else process.env['AI_SERVICE_CLIENT_ID'] = ORIGINAL.clientId;
  if (ORIGINAL.tls === undefined) delete process.env['AI_SERVICE_GRPC_TLS'];
  else process.env['AI_SERVICE_GRPC_TLS'] = ORIGINAL.tls;
});

describe('AiClientModule', () => {
  let moduleRef: Awaited<ReturnType<ReturnType<typeof Test.createTestingModule>['compile']>>;

  beforeEach(async () => {
    // AiClientModule provides HashUserIdService locally — see the
    // module docstring for the rationale — so the test does not
    // need to bring in AuditModule (and AuditWriter's Prisma + CLS
    // dependencies that would imply).
    moduleRef = await Test.createTestingModule({
      imports: [AiClientModule],
    }).compile();
  });

  it('resolves all four wrapper clients', () => {
    expect(moduleRef.get(ChatClient)).toBeInstanceOf(ChatClient);
    expect(moduleRef.get(RagClient)).toBeInstanceOf(RagClient);
    expect(moduleRef.get(IngestionClient)).toBeInstanceOf(IngestionClient);
    expect(moduleRef.get(ModelsClient)).toBeInstanceOf(ModelsClient);
  });

  it('resolves the Principal mapper and the metadata builder', () => {
    expect(moduleRef.get(PrincipalMapper)).toBeInstanceOf(PrincipalMapper);
    expect(moduleRef.get(GrpcMetadataBuilder)).toBeInstanceOf(GrpcMetadataBuilder);
  });

  it('parses the env vars into AI_CONFIG', () => {
    const config = moduleRef.get(AI_CONFIG) as {
      endpoint: string;
      clientId: string;
      useTls: boolean;
    };
    expect(config).toEqual({
      endpoint: 'apf-ai-service:8080',
      clientId: 'apf-portal-test',
      useTls: false,
    });
  });

  it('shares one credentials instance across every generated stub', () => {
    const credentials = moduleRef.get(AI_CREDENTIALS);
    expect(credentials).toBeDefined();
    // Each stub provider injects AI_CREDENTIALS — same reference
    // means the underlying HTTP/2 connection can be multiplexed by
    // grpc-js when they target the same address.
    const chat = moduleRef.get(AI_CHAT_GRPC_CLIENT) as unknown;
    const rag = moduleRef.get(AI_RAG_GRPC_CLIENT) as unknown;
    const ingestion = moduleRef.get(AI_INGESTION_GRPC_CLIENT) as unknown;
    const models = moduleRef.get(AI_MODELS_GRPC_CLIENT) as unknown;
    expect(chat).toBeDefined();
    expect(rag).toBeDefined();
    expect(ingestion).toBeDefined();
    expect(models).toBeDefined();
  });
});
