import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';

import {
  parsePropertiesFile,
  serializePropertiesFile,
} from '../dist/servers/properties/server-properties.util.js';
import { assertSafeServerPath } from '../dist/servers/shared/server-path.util.js';

describe('phase 3 server utilities', () => {
  it('parses server.properties with types', () => {
    const raw = [
      '# comment',
      'max-players=20',
      'online-mode=false',
      'motd=Hello World',
      '',
    ].join('\n');

    const parsed = parsePropertiesFile(raw);
    assert.equal(parsed['max-players'], 20);
    assert.equal(parsed['online-mode'], false);
    assert.equal(parsed['motd'], 'Hello World');
  });

  it('serializes server.properties while preserving comments', () => {
    const raw = '# header\nmax-players=10\nonline-mode=true\n';
    const updated = parsePropertiesFile(raw);
    updated['max-players'] = 25;
    updated['online-mode'] = false;

    const serialized = serializePropertiesFile(updated, raw);
    assert.match(serialized, /^# header/);
    assert.match(serialized, /max-players=25/);
    assert.match(serialized, /online-mode=false/);
  });

  it('rejects path traversal', () => {
    assert.throws(
      () => assertSafeServerPath('../etc/passwd'),
      (error) => error.message.includes('traversal'),
    );
    assert.throws(() => assertSafeServerPath('foo//bar'));
  });

  it('normalizes safe paths', () => {
    assert.equal(assertSafeServerPath(''), '.');
    assert.equal(assertSafeServerPath('logs/latest.log'), 'logs/latest.log');
    assert.equal(assertSafeServerPath('\\world\\level.dat'), 'world/level.dat');
  });
});

describe('phase 3 api contract', () => {
  it('documents console, files, properties and players routes', () => {
    const files = [
      'servers/console/console.controller.ts',
      'servers/files/files.controller.ts',
      'servers/properties/properties.controller.ts',
      'servers/players/players.controller.ts',
      'servers/console/console.gateway.ts',
    ];

    for (const file of files) {
      const source = readFileSync(join(process.cwd(), 'src', file), 'utf8');
      assert.ok(source.length > 0, `${file} should exist`);
    }

    const consoleSource = readFileSync(
      join(process.cwd(), 'src', 'servers/console/console.controller.ts'),
      'utf8',
    );
    assert.ok(consoleSource.includes("@Get('token')"));

    const gatewaySource = readFileSync(
      join(process.cwd(), 'src', 'servers/console/console.gateway.ts'),
      'utf8',
    );
    assert.ok(gatewaySource.includes('console/ws'));

    const filesSource = readFileSync(
      join(process.cwd(), 'src', 'servers/files/files.controller.ts'),
      'utf8',
    );
    assert.ok(filesSource.includes("@Get('content')"));

    const propertiesSource = readFileSync(
      join(process.cwd(), 'src', 'servers/properties/properties.controller.ts'),
      'utf8',
    );
    assert.ok(propertiesSource.includes("@Patch()"));

    const playersSource = readFileSync(
      join(process.cwd(), 'src', 'servers/players/players.controller.ts'),
      'utf8',
    );
    assert.ok(playersSource.includes('whitelist'));

    const combined = [
      consoleSource,
      gatewaySource,
      filesSource,
      propertiesSource,
      playersSource,
    ].join('\n');
    for (const snippet of ['files', 'properties', 'players']) {
      assert.ok(combined.includes(snippet), `missing route hint ${snippet}`);
    }
  });
});
