import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import type { TojuPluginManifest } from '../../../../shared-kernel';
import { PLUGIN_CAPABILITIES } from '../../../../shared-kernel';
import {
  PLUGIN_CLIENT_API_CAPABILITY_REQUIREMENTS,
  PLUGIN_CLIENT_API_SURFACE,
  assertPluginApiSurfaceImplemented,
  collectPluginApiMethodPaths,
  collectRequiredPluginApiCapabilities
} from './plugin-client-api-surface.rules';

const E2E_ALL_API_MANIFEST_PATH = resolve(
  process.cwd(),
  'public/plugins/e2e-all-api/toju.plugin.json'
);

describe('plugin client API surface rules', () => {
  it('lists every documented namespace and method', () => {
    expect(collectPluginApiMethodPaths()).toEqual([
      'attachments.import',
      'channels.addAudioChannel',
      'channels.addTextChannel',
      'channels.addVideoChannel',
      'channels.list',
      'channels.remove',
      'channels.rename',
      'channels.select',
      'clientData.read',
      'clientData.remove',
      'clientData.write',
      'commands.list',
      'commands.register',
      'context.getCurrent',
      'events.publishP2p',
      'events.publishServer',
      'events.subscribeP2p',
      'events.subscribeServer',
      'logger.debug',
      'logger.error',
      'logger.info',
      'logger.warn',
      'media.addCustomAudioStream',
      'media.addCustomVideoStream',
      'media.playAudioClip',
      'media.setInputVolume',
      'media.setOutputVolume',
      'messageBus.publish',
      'messageBus.sendLatestMessages',
      'messageBus.subscribe',
      'messages.delete',
      'messages.edit',
      'messages.import',
      'messages.moderateDelete',
      'messages.readCurrent',
      'messages.send',
      'messages.sendAsPluginUser',
      'messages.setTyping',
      'messages.subscribeTyping',
      'messages.sync',
      'p2p.broadcastData',
      'p2p.connectedPeers',
      'p2p.sendData',
      'profile.getCurrent',
      'profile.update',
      'profile.updateAvatar',
      'roles.list',
      'roles.setAssignments',
      'server.getCurrent',
      'server.registerPluginUser',
      'server.updateIcon',
      'server.updatePermissions',
      'server.updateSettings',
      'serverData.read',
      'serverData.remove',
      'serverData.write',
      'storage.get',
      'storage.remove',
      'storage.set',
      'ui.mountElement',
      'ui.registerAppPage',
      'ui.registerChannelSection',
      'ui.registerComposerAction',
      'ui.registerEmbedRenderer',
      'ui.registerProfileAction',
      'ui.registerSettingsPage',
      'ui.registerSidePanel',
      'ui.registerToolbarAction',
      'users.ban',
      'users.getCurrent',
      'users.kick',
      'users.list',
      'users.readMembers',
      'users.setRole'
    ]);
  });

  it('maps privileged methods to known plugin capabilities', () => {
    for (const capability of Object.values(PLUGIN_CLIENT_API_CAPABILITY_REQUIREMENTS)) {
      expect(PLUGIN_CAPABILITIES).toContain(capability);
    }
  });

  it('requires a capability for every privileged namespace method', () => {
    const privilegedNamespaces = Object.entries(PLUGIN_CLIENT_API_SURFACE)
      .filter(([namespace]) => !['context', 'logger'].includes(namespace));

    for (const [namespace, methods] of privilegedNamespaces) {
      for (const method of methods) {
        const path = `${namespace}.${method}`;

        expect(PLUGIN_CLIENT_API_CAPABILITY_REQUIREMENTS).toHaveProperty(path);
      }
    }
  });

  it('detects missing API methods', () => {
    expect(() => assertPluginApiSurfaceImplemented({
      commands: { list: () => [] }
    })).toThrow(/Plugin API surface is incomplete/);
  });

  it('accepts a fully implemented API object', () => {
    const api = Object.fromEntries(
      Object.entries(PLUGIN_CLIENT_API_SURFACE).map(([namespace, methods]) => {
        const namespaceApi = Object.fromEntries(methods.map((method) => [method, () => undefined]));

        return [namespace, namespaceApi];
      })
    );

    expect(() => assertPluginApiSurfaceImplemented(api)).not.toThrow();
  });

  it('keeps the E2E all-api fixture manifest granted for full API coverage', () => {
    const manifest = JSON.parse(readFileSync(E2E_ALL_API_MANIFEST_PATH, 'utf8')) as TojuPluginManifest;

    expect(manifest.capabilities ?? []).toEqual(expect.arrayContaining(collectRequiredPluginApiCapabilities()));
  });

  it('collects the full capability set needed for API coverage', () => {
    expect(collectRequiredPluginApiCapabilities()).toEqual([
      'audio.volume',
      'channels.manage',
      'channels.read',
      'events.p2p.publish',
      'events.p2p.subscribe',
      'events.server.publish',
      'events.server.subscribe',
      'media.addAudioStream',
      'media.addVideoStream',
      'media.playAudio',
      'messages.deleteOwn',
      'messages.editOwn',
      'messages.moderate',
      'messages.read',
      'messages.send',
      'messages.sync',
      'p2p.data',
      'profile.read',
      'profile.write',
      'roles.manage',
      'roles.read',
      'server.manage',
      'server.read',
      'storage.local',
      'storage.serverData.read',
      'storage.serverData.write',
      'ui.channelsSection',
      'ui.commands',
      'ui.dom',
      'ui.embeds',
      'ui.pages',
      'ui.settings',
      'ui.sidePanel',
      'users.manage',
      'users.read'
    ]);
  });
});
