import { describe, it, expect, beforeAll, afterAll, beforeEach } from '@jest/globals';
import {
  ChannelCredentials,
  Server,
  ServerCredentials,
  status as GrpcStatus,
  type Metadata,
  type sendUnaryData,
  type ServerUnaryCall,
} from '@grpc/grpc-js';
import {
  RagServiceClient,
  RagServiceService,
  type RagSearchRequest,
  type RagSearchResponse,
} from '../gen/apf-ai/rag';
import { Struct } from '../gen/apf-ai/google/protobuf/struct';
import { GrpcMetadataBuilder } from './grpc-metadata.builder';
import { RagClient } from './rag.client';
import type { AiServiceConfig } from '../../config/check-ai-service-config';

/**
 * Locks the unary RAG contract: the wrapper promisifies the
 * callback-style stub, forwards metadata to the server, and
 * surfaces `ServiceError` rejections with the original gRPC status
 * code intact.
 */

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

interface ServerObservations {
  request: RagSearchRequest | null;
  metadata: Metadata | null;
}

let server: Server;
let port: number;
let serverObservations: ServerObservations;
let searchHandler: (
  call: ServerUnaryCall<RagSearchRequest, RagSearchResponse>,
  callback: sendUnaryData<RagSearchResponse>,
) => void;

beforeAll(async () => {
  server = new Server();
  server.addService(RagServiceService, {
    search: (call, callback) => {
      serverObservations.request = call.request;
      serverObservations.metadata = call.metadata;
      searchHandler(call, callback);
    },
  });
  port = await new Promise<number>((resolve, reject) => {
    server.bindAsync('127.0.0.1:0', ServerCredentials.createInsecure(), (err, bound) => {
      if (err) {
        reject(err);
        return;
      }
      resolve(bound);
    });
  });
});

afterAll(async () => {
  await new Promise<void>((resolve, reject) => {
    server.tryShutdown((err) => {
      if (err) {
        reject(err);
        return;
      }
      resolve();
    });
  });
});

beforeEach(() => {
  serverObservations = { request: null, metadata: null };
});

function buildClient(): RagClient {
  const config = { ...CONFIG, endpoint: `127.0.0.1:${port}` };
  const grpc = new RagServiceClient(config.endpoint, ChannelCredentials.createInsecure());
  return new RagClient(grpc, new GrpcMetadataBuilder(config));
}

describe('RagClient (in-process fake server)', () => {
  it('resolves with the server response on a happy unary call', async () => {
    searchHandler = (_call, callback) => {
      callback(null, {
        chunks: [
          {
            id: 'chunk-1',
            documentId: 'doc-1',
            content: 'Lorem ipsum',
            source: 'rag-test',
            score: 0.99,
            metadata: Struct.fromPartial({}),
          },
        ],
        correlationId: 'echo-corr',
      });
    };

    const client = buildClient();
    const response = await client.search(
      {
        query: 'hello',
        topK: 3,
        principal: { subject: 'subj-1', roles: [], attributes: {} },
        filters: { source: '', documentId: '' },
      },
      { correlationId: 'caller-corr' },
    );

    expect(response.chunks).toHaveLength(1);
    expect(response.chunks[0]?.id).toBe('chunk-1');
    expect(serverObservations.metadata?.get('x-correlation-id')).toEqual(['caller-corr']);
  });

  it('rejects with the original gRPC ServiceError on server error', async () => {
    searchHandler = (_call, callback) => {
      callback({
        code: GrpcStatus.PERMISSION_DENIED,
        details: 'forbidden',
        metadata: undefined as unknown as Metadata,
        name: 'Error',
        message: 'forbidden',
      });
    };

    const client = buildClient();
    await expect(
      client.search({
        query: 'q',
        topK: 1,
        principal: { subject: 'subj-2', roles: [], attributes: {} },
        filters: { source: '', documentId: '' },
      }),
    ).rejects.toMatchObject({ code: GrpcStatus.PERMISSION_DENIED });
  });
});
