import 'reflect-metadata';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { Global, Module } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
import { HermesRuntimeProvider } from '@mosaicstack/agent';
import { AgentModule } from './agent.module.js';
import { AUTH } from '../auth/auth.tokens.js';
import { AuthGuard } from '../auth/auth.guard.js';
import { BRAIN } from '../brain/brain.tokens.js';
import { DB } from '../database/database.module.js';
import { CoordModule } from '../coord/coord.module.js';
import { McpClientModule } from '../mcp-client/mcp-client.module.js';
import { SkillsModule } from '../skills/skills.module.js';
import { GCModule } from '../gc/gc.module.js';
import { LogModule } from '../log/log.module.js';
import { CommandsModule } from '../commands/commands.module.js';
import {
  AGENT_RUNTIME_PROVIDER_REGISTRY,
  RUNTIME_APPROVAL_VERIFIER,
  RUNTIME_PROVIDER_AUDIT_SINK,
  RuntimeProviderAuditService,
} from './runtime-provider-registry.service.js';
import { DurableSessionService } from './durable-session.service.js';
import { DurableSessionRepository } from './durable-session.repository.js';
import { AgentService } from './agent.service.js';
import { ProviderService } from './provider.service.js';
import { ProviderCredentialsService } from './provider-credentials.service.js';
import { RoutingService } from './routing.service.js';
import { RoutingEngineService } from './routing/routing-engine.service.js';
import { SkillLoaderService } from './skill-loader.service.js';

const authenticatedUser = { id: 'operator-1', tenantId: 'tenant-1' };

@Module({})
class EmptyAgentDependencyModule {}

@Global()
@Module({
  providers: [
    {
      provide: AUTH,
      useValue: {
        api: {
          getSession: vi.fn(async ({ headers }: { headers: Headers }) =>
            headers.get('cookie') === 'session=trusted'
              ? { user: authenticatedUser, session: { id: 'session-1' } }
              : null,
          ),
        },
      },
    },
    AuthGuard,
    { provide: BRAIN, useValue: {} },
    { provide: DB, useValue: {} },
  ],
  exports: [AUTH, AuthGuard, BRAIN, DB],
})
class AuthenticatedRequestModule {}

/**
 * This is deliberately an HTTP test rather than a controller unit test: it
 * exercises AgentModule's actual provider factory, Nest DI, and AuthGuard.
 */
describe('Hermes runtime provider reachability', (): void => {
  let app: NestFastifyApplication | undefined;

  beforeAll(async (): Promise<void> => {
    process.env['MOSAIC_AGENT_NAME'] = 'Nova';
    const moduleRef = await Test.createTestingModule({
      imports: [AuthenticatedRequestModule, AgentModule],
    })
      .overrideModule(CoordModule)
      .useModule(EmptyAgentDependencyModule)
      .overrideModule(McpClientModule)
      .useModule(EmptyAgentDependencyModule)
      .overrideModule(SkillsModule)
      .useModule(EmptyAgentDependencyModule)
      .overrideModule(GCModule)
      .useModule(EmptyAgentDependencyModule)
      .overrideModule(LogModule)
      .useModule(EmptyAgentDependencyModule)
      .overrideModule(CommandsModule)
      .useModule(EmptyAgentDependencyModule)
      .overrideProvider(RuntimeProviderAuditService)
      .useValue({ record: vi.fn().mockResolvedValue(undefined) })
      .overrideProvider(RUNTIME_PROVIDER_AUDIT_SINK)
      .useValue({ record: vi.fn().mockResolvedValue(undefined) })
      .overrideProvider(RUNTIME_APPROVAL_VERIFIER)
      .useValue({ consume: vi.fn().mockResolvedValue(false) })
      .overrideProvider(DurableSessionService)
      .useValue({})
      .overrideProvider(DurableSessionRepository)
      .useValue({})
      .overrideProvider(AgentService)
      .useValue({})
      .overrideProvider(ProviderService)
      .useValue({})
      .overrideProvider(ProviderCredentialsService)
      .useValue({})
      .overrideProvider(RoutingService)
      .useValue({})
      .overrideProvider(RoutingEngineService)
      .useValue({})
      .overrideProvider(SkillLoaderService)
      .useValue({})
      .compile();

    app = moduleRef.createNestApplication<NestFastifyApplication>(new FastifyAdapter());
    await app.init();
    await app.getHttpAdapter().getInstance().ready();
  });

  afterAll(async (): Promise<void> => {
    await app?.close();
  });

  it('returns gateway denial responses from the actual guarded interaction routes', async (): Promise<void> => {
    if (!app) throw new Error('Nest application did not initialize');

    const attachDenied = await app.inject({
      method: 'POST',
      url: '/api/interaction/Nova/sessions/session-1/attach',
      headers: { 'x-correlation-id': 'correlation-1' },
      payload: { mode: 'read' },
    });
    expect(attachDenied.statusCode).toBe(401);

    const sendDenied = await app.inject({
      method: 'POST',
      url: '/api/interaction/Nova/sessions/session-1/send',
      headers: { cookie: 'session=trusted', 'x-correlation-id': 'correlation-1' },
      payload: {},
    });
    expect(sendDenied.statusCode).toBe(403);
    expect(sendDenied.json()).toMatchObject({
      message: 'Content and idempotency key are required',
    });

    const stopDenied = await app.inject({
      method: 'POST',
      url: '/api/interaction/Nova/sessions/session-1/stop',
      headers: { cookie: 'session=trusted', 'x-correlation-id': 'correlation-1' },
      payload: {},
    });
    expect(stopDenied.statusCode).toBe(403);
    expect(stopDenied.json()).toMatchObject({ message: 'Exact-action approval is required' });
  });

  it('requires authentication and reaches the Hermes provider registered by AgentModule', async (): Promise<void> => {
    if (!app) throw new Error('Nest application did not initialize');
    const registry = app.get(AGENT_RUNTIME_PROVIDER_REGISTRY);
    expect(registry.get('runtime.hermes')).toBeInstanceOf(HermesRuntimeProvider);

    const denied = await app.inject({
      method: 'GET',
      url: '/api/interaction/Nova/transitional-capabilities?provider=runtime.hermes',
      headers: { 'x-correlation-id': 'correlation-1' },
    });
    expect(denied.statusCode).toBe(401);

    const response = await app.inject({
      method: 'GET',
      url: '/api/interaction/Nova/transitional-capabilities?provider=runtime.hermes',
      headers: { cookie: 'session=trusted', 'x-correlation-id': 'correlation-1' },
    });
    expect(response.statusCode).toBe(200);
    expect(response.json()).toEqual([
      { capability: 'kanban', status: 'unsupported' },
      { capability: 'skills', status: 'unsupported' },
      { capability: 'memory', status: 'unsupported' },
      { capability: 'tools', status: 'unsupported' },
      { capability: 'cron', status: 'unsupported' },
    ]);
  });
});
