import { afterEach, describe, expect, it } from 'vitest';
import { homedir } from 'node:os';
import { join } from 'node:path';
import {
  detectPlatform,
  parseInstallArgs,
  buildServiceArgs,
  resolveBin,
  resolveScriptPath,
  renderSystemdUnit,
  renderLaunchAgentPlist,
  xmlEscape,
  systemdUnitPath,
  launchAgentPlistPath,
} from '../src/cli/install.js';

describe('cli install — detectPlatform', () => {
  it('accepte linux et darwin', () => {
    expect(detectPlatform('linux')).toBe('linux');
    expect(detectPlatform('darwin')).toBe('darwin');
  });

  it('rejette les autres plateformes avec un message clair', () => {
    expect(() => detectPlatform('win32')).toThrow(/Linux \(systemd\) and macOS \(launchd\)/);
    expect(() => detectPlatform('freebsd')).toThrow(/freebsd/);
  });
});

describe('cli install — buildServiceArgs', () => {
  it('aucun flag → aucun argument (le service garde les défauts loopback)', () => {
    expect(buildServiceArgs(parseInstallArgs([]))).toEqual([]);
  });

  it('propage --port et --allow-origin (répétable) dans un ordre stable', () => {
    const flags = parseInstallArgs(['--port', '8080', '--allow-origin', 'a', '--allow-origin', 'b']);
    expect(buildServiceArgs(flags)).toEqual(['--port', '8080', '--allow-origin', 'a', '--allow-origin', 'b']);
  });

  it("n'injecte JAMAIS --i-know-this-exposes-a-terminal (modèle de sécurité)", () => {
    const flags = parseInstallArgs(['--bind', '0.0.0.0', '--port', '7317']);
    expect(buildServiceArgs(flags)).not.toContain('--i-know-this-exposes-a-terminal');
  });

  it("ne propage pas les flags propres à l'install (bin-path, label, dry-run, no-enable)", () => {
    const flags = parseInstallArgs(['--bin-path', '/usr/local/bin/arboretum', '--label', 'x', '--dry-run', '--no-enable']);
    expect(buildServiceArgs(flags)).toEqual([]);
  });
});

describe('cli install — resolveBin', () => {
  it('par défaut : node (process.execPath) + dist/index.js', () => {
    const { exec, args } = resolveBin({});
    expect(exec).toBe(process.execPath);
    expect(args).toHaveLength(1);
    expect(args[0]).toBe(resolveScriptPath());
    expect(args[0]).toMatch(/index\.(js|ts)$/);
  });

  it('--bin-path force le wrapper, sans argument de script', () => {
    expect(resolveBin({ binPath: '/usr/local/bin/arboretum' })).toEqual({
      exec: '/usr/local/bin/arboretum',
      args: [],
    });
  });
});

describe('cli install — renderSystemdUnit', () => {
  it('contient le ExecStart calculé et les directives clés', () => {
    const unit = renderSystemdUnit({ exec: '/usr/bin/node', scriptArgs: ['/opt/arboretum/index.js', '--port', '7317'] });
    expect(unit).toContain('ExecStart=/usr/bin/node /opt/arboretum/index.js --port 7317');
    expect(unit).toContain('Restart=on-failure');
    expect(unit).toContain('KillSignal=SIGTERM');
    expect(unit).toContain('TimeoutStopSec=10');
    expect(unit).toContain('WantedBy=default.target');
  });

  it('quote les tokens contenant un espace', () => {
    const unit = renderSystemdUnit({ exec: '/path with space/node', scriptArgs: ['/s.js'] });
    expect(unit).toContain('ExecStart="/path with space/node" /s.js');
  });

  it('snapshot du unit pour un jeu de flags fixe', () => {
    const unit = renderSystemdUnit({
      exec: '/usr/bin/node',
      scriptArgs: ['/opt/arboretum/index.js', '--port', '7317', '--allow-origin', 'https://m.ts.net'],
    });
    expect(unit).toMatchInlineSnapshot(`
      "[Unit]
      Description=Arboretum — git worktree & Claude Code dashboard
      After=network-online.target
      Wants=network-online.target

      [Service]
      ExecStart=/usr/bin/node /opt/arboretum/index.js --port 7317 --allow-origin https://m.ts.net
      Restart=on-failure
      RestartSec=5
      KillSignal=SIGTERM
      TimeoutStopSec=10
      Environment=NODE_ENV=production

      [Install]
      WantedBy=default.target
      "
    `);
  });
});

describe('cli install — renderLaunchAgentPlist', () => {
  const base = {
    label: 'fr.lidge.arboretum',
    programArguments: ['/usr/bin/node', '/opt/index.js', '--port', '7317'],
    stdoutPath: '/Users/me/Library/Logs/arboretum/out.log',
    stderrPath: '/Users/me/Library/Logs/arboretum/err.log',
  };

  it('contient le label, les ProgramArguments ordonnés et les clés launchd', () => {
    const plist = renderLaunchAgentPlist(base);
    expect(plist).toContain('<string>fr.lidge.arboretum</string>');
    const idxNode = plist.indexOf('<string>/usr/bin/node</string>');
    const idxScript = plist.indexOf('<string>/opt/index.js</string>');
    expect(idxNode).toBeGreaterThan(0);
    expect(idxScript).toBeGreaterThan(idxNode);
    expect(plist).toContain('<key>RunAtLoad</key>');
    expect(plist).toContain('<key>KeepAlive</key>');
    expect(plist).toContain('<key>StandardOutPath</key>');
  });

  it('échappe les caractères XML dans les arguments (URL avec &)', () => {
    const plist = renderLaunchAgentPlist({
      ...base,
      programArguments: ['/usr/bin/node', '/opt/index.js', '--allow-origin', 'https://a?b&c=d'],
    });
    expect(plist).toContain('https://a?b&amp;c=d');
    expect(plist).not.toContain('b&c=d');
  });
});

describe('cli install — xmlEscape', () => {
  it('échappe &, < et >', () => {
    expect(xmlEscape('a & b < c > d')).toBe('a &amp; b &lt; c &gt; d');
  });
});

describe('cli install — chemins', () => {
  const savedXdg = process.env.XDG_CONFIG_HOME;
  afterEach(() => {
    if (savedXdg === undefined) delete process.env.XDG_CONFIG_HOME;
    else process.env.XDG_CONFIG_HOME = savedXdg;
  });

  it('systemdUnitPath respecte XDG_CONFIG_HOME', () => {
    process.env.XDG_CONFIG_HOME = '/tmp/xdg';
    expect(systemdUnitPath()).toBe('/tmp/xdg/systemd/user/arboretum.service');
  });

  it('systemdUnitPath retombe sur ~/.config sans XDG_CONFIG_HOME', () => {
    delete process.env.XDG_CONFIG_HOME;
    expect(systemdUnitPath()).toBe(join(homedir(), '.config', 'systemd', 'user', 'arboretum.service'));
  });

  it('launchAgentPlistPath place le plist dans ~/Library/LaunchAgents', () => {
    expect(launchAgentPlistPath('fr.lidge.arboretum')).toBe(
      join(homedir(), 'Library', 'LaunchAgents', 'fr.lidge.arboretum.plist'),
    );
  });
});
