import assert from 'node:assert/strict';
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { test } from 'node:test';

test('Dovecot preparation creates a private secret and Maildir root', () => {
  const projectDir = mkdtempSync(path.join(tmpdir(), 'mailhub-dovecot-prepare-'));
  const result = spawnSync('bash', ['scripts/prepare-dovecot.sh'], {
    cwd: path.resolve(import.meta.dirname, '..'),
    env: {
      ...process.env,
      MAILHUB_PROJECT_DIR: projectDir
    },
    encoding: 'utf8'
  });
  const unsupportedLinuxUid = process.platform === 'linux'
    && typeof process.getuid === 'function'
    && ![0, 1000].includes(process.getuid());
  if (unsupportedLinuxUid) {
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /must run as root or host uid 1000/);
    return;
  }
  assert.equal(result.status, 0, result.stderr || result.stdout);

  const secretPath = path.join(projectDir, 'data', 'secrets', 'dovecot_auth_secret');
  const webmailSecretPath = path.join(projectDir, 'data', 'secrets', 'webmail_sso_secret');
  const maildirRoot = path.join(projectDir, 'data', 'maildir');
  const secretStat = statSync(secretPath);
  assert.equal(secretStat.isFile(), true);
  assert.equal(secretStat.mode & 0o777, 0o400);
  assert.equal(statSync(webmailSecretPath).isFile(), true);
  assert.equal(statSync(webmailSecretPath).mode & 0o777, 0o440);
  assert.equal(statSync(path.dirname(webmailSecretPath)).mode & 0o777, 0o750);
  assert.notEqual(readFileSync(secretPath, 'utf8'), readFileSync(webmailSecretPath, 'utf8'));
  assert.equal(statSync(maildirRoot).isDirectory(), true);
  assert.equal(statSync(maildirRoot).mode & 0o777, 0o700);
});

test('Dovecot preparation grants only the configured Roundcube worker gid', () => {
  const projectDir = mkdtempSync(path.join(tmpdir(), 'mailhub-webmail-reader-'));
  const binDir = path.join(projectDir, 'bin');
  const chownLog = path.join(projectDir, 'chown.log');
  mkdirSync(binDir);
  writeExecutable(path.join(binDir, 'id'), '#!/usr/bin/env bash\necho 0\n');
  writeExecutable(path.join(binDir, 'uname'), '#!/usr/bin/env bash\necho Darwin\n');
  writeExecutable(path.join(binDir, 'chown'), `#!/usr/bin/env bash
printf '%s\\n' "$*" >> "\${CHOWN_LOG}"
`);
  writeFileSync(path.join(projectDir, '.env'), 'WEBMAIL_SSO_READER_GID=33\n');

  const result = spawnSync('bash', ['scripts/prepare-dovecot.sh'], {
    cwd: path.resolve(import.meta.dirname, '..'),
    env: {
      ...process.env,
      PATH: `${binDir}:${process.env.PATH}`,
      CHOWN_LOG: chownLog,
      MAILHUB_PROJECT_DIR: projectDir
    },
    encoding: 'utf8'
  });
  assert.equal(result.status, 0, result.stderr || result.stdout);

  const chownCalls = readFileSync(chownLog, 'utf8').trim().split('\n');
  const privateCall = chownCalls.find((line) => line.startsWith('1000:1000 '));
  const webmailCall = chownCalls.find((line) => line.startsWith('1000:33 '));
  assert.ok(privateCall?.includes('dovecot_auth_secret'));
  assert.ok(privateCall?.includes('/maildir'));
  assert.ok(!privateCall?.includes('webmail_sso_secret'));
  assert.ok(webmailCall?.includes('/secrets'));
  assert.ok(webmailCall?.includes('webmail_sso_secret'));
});

test('Dovecot preparation rejects ambiguous or invalid Roundcube reader gids', () => {
  for (const envContents of [
    'WEBMAIL_SSO_READER_GID=33\nWEBMAIL_SSO_READER_GID=34\n',
    'WEBMAIL_SSO_READER_GID=www-data\n',
    'WEBMAIL_SSO_READER_GID=0\n'
  ]) {
    const projectDir = mkdtempSync(path.join(tmpdir(), 'mailhub-webmail-reader-invalid-'));
    writeFileSync(path.join(projectDir, '.env'), envContents);
    const result = spawnSync('bash', ['scripts/prepare-dovecot.sh'], {
      cwd: path.resolve(import.meta.dirname, '..'),
      env: {
        ...process.env,
        MAILHUB_PROJECT_DIR: projectDir
      },
      encoding: 'utf8'
    });
    assert.notEqual(result.status, 0);
    assert.match(result.stderr, /reader gid|must appear at most once/i);
  }
});

function writeExecutable(filePath, contents) {
  writeFileSync(filePath, contents);
  chmodSync(filePath, 0o755);
}
