import 'reflect-metadata';
import {
  type CanActivate,
  type ExecutionContext,
  type INestApplication,
  ValidationPipe,
} from '@nestjs/common';
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { AuthGuard } from '../auth/auth.guard.js';
import { HarnessRegistry } from './harness.registry.js';
import { HARNESS_REGISTRY } from './harness.tokens.js';
import { FakeHarnessAdapter } from './testing/fake-harness.adapter.js';
// The real module under test — importing it (not a hand-listed controllers/mocks
// list) is what makes an unresolved provider fail loudly at app.init() (#1145 guard).
import { HarnessModule } from './harness.module.js';

// Fields that must NEVER surface on a browser-facing catalog/list response.
const FORBIDDEN_KEYS = [
  'executable',
  'executablePath',
  'home',
  'homeDir',
  'cwd',
  'workingDir',
  'workingDirectory',
  'nativeSessionPath',
  'sessionPath',
  'env',
  'secret',
  'secrets',
  'token',
  'apiKey',
];

function assertNoForbiddenLeak(payload: unknown): void {
  const serialized = JSON.stringify(payload).toLowerCase();
  for (const key of FORBIDDEN_KEYS) {
    expect(serialized).not.toContain(key.toLowerCase());
  }
}

const authGuard: CanActivate = {
  canActivate(context: ExecutionContext): boolean {
    const requestContext = context.switchToHttp().getRequest<{ user?: { id: string } }>();
    requestContext.user = { id: 'user-1' };
    return true;
  },
};

function registryWithFake(): HarnessRegistry {
  const registry = new HarnessRegistry();
  registry.register(new FakeHarnessAdapter({ id: 'fake' }));
  return registry;
}

describe('Harness catalog HTTP surface', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [HarnessModule],
    })
      .overrideGuard(AuthGuard)
      .useValue(authGuard)
      .overrideProvider(HARNESS_REGISTRY)
      .useValue(registryWithFake())
      .compile();

    app = moduleRef.createNestApplication<NestFastifyApplication>(new FastifyAdapter());
    app.useGlobalPipes(
      new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }),
    );
    await app.init();
    await app.getHttpAdapter().getInstance().ready();
  });

  afterAll(async () => {
    await app.close();
  });

  it('boots the real HarnessModule so all providers resolve at app.init()', () => {
    // If HarnessModule failed to resolve a provider, beforeAll's app.init() would
    // have thrown and this suite would never reach here.
    expect(app).toBeDefined();
  });

  it('GET /api/harnesses returns 200 with safe fields only', async () => {
    const response = await request(app.getHttpServer()).get('/api/harnesses');

    expect(response.status).toBe(200);
    expect(Array.isArray(response.body)).toBe(true);
    expect(response.body.length).toBeGreaterThan(0);
    const summary = response.body[0];
    expect(Object.keys(summary).sort()).toEqual(['capabilities', 'displayName', 'id']);
    expect(summary.id).toBe('fake');
    expect(typeof summary.displayName).toBe('string');
    expect(Array.isArray(summary.capabilities)).toBe(true);
    assertNoForbiddenLeak(response.body);
  });

  it('GET /api/harnesses/:harnessId/catalog returns 200 with safe catalog fields only', async () => {
    const response = await request(app.getHttpServer()).get('/api/harnesses/fake/catalog');

    expect(response.status).toBe(200);
    expect(response.body.harnessId).toBe('fake');
    expect(typeof response.body.version).toBe('string');
    expect(typeof response.body.fingerprint).toBe('string');
    expect(Array.isArray(response.body.models)).toBe(true);
    expect(response.body.models.length).toBeGreaterThan(0);
    const entry = response.body.models[0];
    // Whitelisted catalog-entry fields only (no executables/paths/secrets).
    expect(Object.keys(entry).sort()).toEqual(
      [
        'authState',
        'availability',
        'displayName',
        'harnessId',
        'inputTypes',
        'modelId',
        'providerId',
        'reasoningCapability',
      ].sort(),
    );
    assertNoForbiddenLeak(response.body);
  });

  it('GET catalog for an unknown harnessId returns a typed adapter_unavailable error, never a fallback catalog', async () => {
    const response = await request(app.getHttpServer()).get('/api/harnesses/ghost-harness/catalog');

    expect(response.status).toBe(404);
    expect(response.body.code).toBe('adapter_unavailable');
    // A fallback catalog would carry a models array; a typed error must not.
    expect(response.body.models).toBeUndefined();
  });
});
