import 'reflect-metadata';
import { RequestMethod } from '@nestjs/common';
import { describe, expect, it } from 'vitest';
import type { FastifyRequest } from 'fastify';
import { FederationCapabilitiesResponseSchema, FEDERATION_VERBS } from '@mosaicstack/types';
import { FederationScopeError } from '../../../scope-schema.js';
import { FederationAuthGuard } from '../../federation-auth.guard.js';
import { CapabilitiesController } from '../capabilities.controller.js';

const VALID_SCOPE = {
  resources: ['tasks', 'notes'],
  excluded_resources: ['credentials'],
  max_rows_per_query: 250,
} as const;

const DEFAULTED_SCOPE = {
  resources: ['memory'],
  max_rows_per_query: 10,
} as const;

function makeRequest(scope: Record<string, unknown>): FastifyRequest {
  return {
    federationContext: {
      grantId: 'grant-1',
      peerId: 'peer-1',
      subjectUserId: 'user-1',
      scope,
    },
  } as FastifyRequest;
}

describe('CapabilitiesController', () => {
  it('declares GET /api/federation/v1/capabilities', () => {
    expect(Reflect.getMetadata('path', CapabilitiesController)).toBe(
      'api/federation/v1/capabilities',
    );
    expect(Reflect.getMetadata('path', CapabilitiesController.prototype.getCapabilities)).toBe('/');
    expect(Reflect.getMetadata('method', CapabilitiesController.prototype.getCapabilities)).toBe(
      RequestMethod.GET,
    );
  });

  it('is protected only by FederationAuthGuard', () => {
    const guards = Reflect.getMetadata('__guards__', CapabilitiesController) as unknown[];

    expect(guards).toEqual([FederationAuthGuard]);
  });

  it('returns resources, excluded resources, max rows, and M3 supported verbs from the active grant scope', () => {
    const controller = new CapabilitiesController();

    const response = controller.getCapabilities(makeRequest(VALID_SCOPE));

    expect(response).toEqual({
      resources: ['tasks', 'notes'],
      excluded_resources: ['credentials'],
      max_rows_per_query: 250,
      supported_verbs: [...FEDERATION_VERBS],
    });
    expect(FederationCapabilitiesResponseSchema.safeParse(response).success).toBe(true);
  });

  it('applies scope defaults without RBAC or resource filtering', () => {
    const controller = new CapabilitiesController();

    const response = controller.getCapabilities(makeRequest(DEFAULTED_SCOPE));

    expect(response).toEqual({
      resources: ['memory'],
      excluded_resources: [],
      max_rows_per_query: 10,
      supported_verbs: ['list', 'get', 'capabilities'],
    });
  });

  it('rejects invalid scope state instead of returning an invalid capabilities contract', () => {
    const controller = new CapabilitiesController();

    expect(() =>
      controller.getCapabilities(
        makeRequest({
          resources: [],
          max_rows_per_query: 0,
        }),
      ),
    ).toThrow(FederationScopeError);
  });
});
