import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { spawnSync } from 'node:child_process';

const servicePath = new URL('../deploy/timmy-staging.service', import.meta.url);
const envPath = new URL('../deploy/timmy-staging.env.example', import.meta.url);
const caddyPath = new URL('../deploy/Caddyfile.staging.example', import.meta.url);
const runbookPath = new URL('../docs/STAGING-RUNBOOK.md', import.meta.url);

const literalSecret = /(password|token|secret|api[_-]?key)\s*[=:]\s*(?!\$\{|<|CHANGE_ME|false|$)["']?[A-Za-z0-9/+_.-]{8,}/i;

test('systemd template runs a dedicated loopback-only agent-disabled service', async () => {
  const service = await readFile(servicePath, 'utf8');
  for (const directive of [
    'User=timmy-staging', 'Group=timmy-staging',
    'WorkingDirectory=/opt/timmy-staging/current',
    'EnvironmentFile=/etc/timmy-staging.env',
    'Environment=HOST=127.0.0.1', 'Environment=PORT=4174',
    'Environment=TIMMY_BASE_PATH=/timmy-staging',
    'Environment=TIMMY_AGENT_ENABLED=false',
  ]) assert.match(service, new RegExp(`^${directive.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'), directive);
  assert.match(service, /^ExecStart=\/usr\/bin\/env TIMMY_AGENT_ENABLED=false TIMMY_VISION_ENABLED=false \/usr\/local\/lib\/timmy-staging\/node server\.mjs$/m);
  assert.doesNotMatch(service, /ExecStart=\/usr\/(?:local\/)?bin\/node|0\.0\.0\.0|TIMMY_AGENT_ENABLED=true/);
  assert.doesNotMatch(service, literalSecret);
});

test('systemd command-level environment keeps agent and vision disabled after EnvironmentFile overrides', async () => {
  const service = await readFile(servicePath, 'utf8');
  const environmentFileIndex = service.indexOf('EnvironmentFile=');
  const execLine = service.match(/^ExecStart=(.+)$/m)?.[1];
  assert.ok(execLine, 'ExecStart must exist');
  assert.ok(environmentFileIndex < service.indexOf(`ExecStart=${execLine}`), 'EnvironmentFile must be loaded before command overrides');
  const argv = execLine.trim().split(/\s+/);
  assert.equal(argv.shift(), '/usr/bin/env');
  const assignments = argv.filter(value => /^TIMMY_(?:AGENT|VISION)_ENABLED=/.test(value));
  assert.deepEqual(assignments, ['TIMMY_AGENT_ENABLED=false', 'TIMMY_VISION_ENABLED=false']);
  const probe = spawnSync('/usr/bin/env', [
    ...assignments, process.execPath, '-e',
    'process.stdout.write(`${process.env.TIMMY_AGENT_ENABLED},${process.env.TIMMY_VISION_ENABLED}`)',
  ], { env: { ...process.env, TIMMY_AGENT_ENABLED: 'true', TIMMY_VISION_ENABLED: 'true' }, encoding: 'utf8' });
  assert.equal(probe.status, 0, probe.stderr);
  assert.equal(probe.stdout, 'false,false');
});

test('systemd template applies filesystem privilege process and resource confinement', async () => {
  const service = await readFile(servicePath, 'utf8');
  for (const directive of [
    'UMask=0077', 'NoNewPrivileges=true', 'PrivateTmp=true', 'PrivateDevices=true',
    'ProtectSystem=strict', 'ProtectHome=true', 'ReadWritePaths=/var/lib/timmy-staging',
    'RestrictSUIDSGID=true', 'LockPersonality=true', 'RestrictNamespaces=true',
    'ProtectKernelTunables=true', 'ProtectKernelModules=true', 'ProtectKernelLogs=true',
    'ProtectControlGroups=true', 'ProtectClock=true', 'ProtectHostname=true',
    'CapabilityBoundingSet=', 'AmbientCapabilities=', 'RestrictRealtime=true',
    'TasksMax=64', 'MemoryMax=512M', 'LimitNOFILE=1024',
  ]) assert.match(service, new RegExp(`^${directive.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'), directive);
  assert.equal((service.match(/^ReadWritePaths=/gm) || []).length, 1);
});

test('environment example is least privilege base-path staging configuration without credentials', async () => {
  const env = await readFile(envPath, 'utf8');
  for (const setting of [
    'HOST=127.0.0.1', 'PORT=4174', 'TIMMY_BASE_PATH=/timmy-staging',
    'TIMMY_STAGING_LABEL=true', 'TIMMY_AGENT_ENABLED=false', 'TIMMY_VISION_ENABLED=false',
  ]) assert.match(env, new RegExp(`^${setting.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'));
  assert.match(env, /install -m 600/);
  assert.doesNotMatch(env, /TIMMY_AGENT_ACCESS_TOKEN|GITEA_TOKEN|OPENAI_API_KEY|COOKIE/);
  assert.doesNotMatch(env, literalSecret);
});

test('Caddy snippet isolates git and authenticates staging before a catchall', async () => {
  const caddy = await readFile(caddyPath, 'utf8');
  const git = caddy.indexOf('handle @git');
  const staging = caddy.indexOf('handle_path /timmy-staging/*');
  const catchall = caddy.indexOf('handle {');
  assert.ok(git >= 0 && staging > git && catchall > staging, 'route order must be git, staging, catchall');
  assert.match(caddy, /basic_auth/);
  assert.match(caddy, /\{\$TIMMY_STAGING_PASSWORD_HASH\}/);
  assert.match(caddy, /reverse_proxy 127\.0\.0\.1:4174/);
  assert.match(caddy, /max_size 8MB/);
  assert.match(caddy, /X-Content-Type-Options "nosniff"/);
  assert.match(caddy, /X-Frame-Options "DENY"/);
  assert.match(caddy, /Referrer-Policy "no-referrer"/);
  assert.match(caddy, /Content-Security-Policy/);
  assert.match(caddy, /rewrite \* \/timmy-staging\{uri\}/, 'handle_path strips once, then upstream base path is explicitly reconstructed');
  assert.doesNotMatch(caddy, /\$2[aby]\$[A-Za-z0-9./]{20,}|password\s+[^<{\s]/i);
});

test('runbook covers safe installation operation verification rollback backup and removal', async () => {
  const runbook = await readFile(runbookPath, 'utf8');
  for (const heading of [
    'Prerequisites', 'DNS and URL', 'Install', 'Promote', 'Smoke test', 'Status and logs',
    'Rollback', 'Backup', 'Remove staging', 'Template validation',
  ]) assert.match(runbook, new RegExp(`^## .*${heading}`, 'mi'), heading);
  assert.match(runbook, /sha256/i);
  assert.match(runbook, /install -D -o root -g root -m 755[^\n]+\/usr\/local\/lib\/timmy-staging\/node/);
  assert.match(runbook, /\/usr\/local\/lib\/timmy-staging\/node --version/);
  assert.match(runbook, /chmod 600|install -m 600/);
  assert.match(runbook, /systemctl restart timmy-staging\.service/);
  assert.match(runbook, /no prior release[^.]*stops[^.]*service/i);
  assert.match(runbook, /failed release[^.]*inert[^.]*evidence/i);
  assert.match(runbook, /journalctl -u timmy-staging\.service/);
  assert.match(runbook, /do not.*live|approval/i);
  assert.doesNotMatch(runbook, literalSecret);
});
