import { describe, expect, it } from 'bun:test';
import { manifestProblems, mountPrefix, type PluginManifest } from './manifest';

const valid = (over: Partial<PluginManifest> = {}): PluginManifest => ({
  publisher: 'officerdev',
  version: '1.0.0',
  platform: '>=1.0.0 <2.0.0',
  label: 'Offscale',
  summary: 'Your tailnet',
  icon: 'Network',
  color: '#818cf8',
  permissions: [{ key: 'offscale', label: 'Offscale', description: 'The tailnet' }],
  ...over,
});

describe('mountPrefix', () => {
  it('puts first-party plugins at the root', () => {
    expect(mountPrefix({ appName: 'offscale', manifest: { publisher: 'officerdev' } })).toBe('/offscale');
  });

  it('puts everyone else under /p/<publisher>/', () => {
    expect(mountPrefix({ appName: 'notes', manifest: { publisher: 'alice' } })).toBe('/p/alice/notes');
  });

  // The property the segment exists for: a third party cannot reach a core route's namespace, whatever
  // they call their plugin. Without it, publishing `notes` at /api/notes would mean the platform could
  // never add /api/notes itself.
  it('cannot shadow a core route, whatever the plugin is called', () => {
    for (const core of ['chat', 'users', 'terminal', 'auth', 'files']) {
      expect(mountPrefix({ appName: core, manifest: { publisher: 'alice' } })).toBe(`/p/alice/${core}`);
    }
  });
});

describe('manifestProblems', () => {
  it('accepts a good manifest', () => {
    expect(manifestProblems('offscale', valid())).toEqual([]);
  });

  it('rejects a missing manifest with a reason rather than a crash', () => {
    expect(manifestProblems('offscale', null)).toHaveLength(1);
  });

  it('reports every problem at once, not the first', () => {
    // An install fixed one field per attempt is an install nobody finishes.
    const problems = manifestProblems('offscale', { publisher: 'officerdev' } as Partial<PluginManifest>);
    expect(problems.length).toBeGreaterThan(3);
  });

  // The app name is a URL segment, a SQL identifier prefix and a directory name simultaneously. Anything
  // that is not safe in all three has to be refused at the door.
  it.each([
    ['Offscale', 'uppercase'],
    ['1offscale', 'leading digit'],
    ['off scale', 'space'],
    ['off_scale', 'underscore'],
    ['off/scale', 'slash'],
    ['../escape', 'traversal'],
    ['', 'empty'],
  ])('refuses the directory name %p (%s)', (appName) => {
    expect(manifestProblems(appName, valid()).length).toBeGreaterThan(0);
  });

  it('refuses a publisher that is not a safe path segment', () => {
    expect(manifestProblems('notes', valid({ publisher: '../evil' })).length).toBeGreaterThan(0);
  });

  it('requires permissions to be an array, so [] is how a plugin says it gates nothing', () => {
    expect(manifestProblems('notes', valid({ permissions: [] }))).toEqual([]);
    expect(manifestProblems('notes', valid({ permissions: undefined })).length).toBeGreaterThan(0);
  });

  it('names the permission that is malformed, by index', () => {
    const problems = manifestProblems('notes', valid({ permissions: [{ label: 'x' }] as never }));
    expect(problems.some((p) => p.includes('permissions[0].key'))).toBe(true);
  });
});
