import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import path from 'node:path';
import { spawn, spawnSync } from 'node:child_process';
import http from 'node:http';
import test from 'node:test';

const root = path.resolve(import.meta.dirname, '..');
const read = relative => fs.readFile(path.join(root, relative), 'utf8');

test('upload MIME types are detected from content and not trusted request metadata', async () => {
  const upload = await read('src/middleware/upload.js');
  const media = await read('src/services/media-service.js');
  const pkg = JSON.parse(await read('package.json'));

  assert.equal(pkg.dependencies['file-type'], '22.0.1');
  assert.equal(pkg.dependencies['@file-type/av'], '0.2.0');
  assert.match(upload, /fileTypeFromFile/);
  assert.match(upload, /customDetectors:\s*\[detectAv\]/);
  assert.match(upload, /detectGpx/);
  assert.doesNotMatch(upload, /allowed\.has\(file\.mimetype\)/);
  assert.doesNotMatch(upload, /contentType ist erforderlich/);
  assert.match(media, /file\.detectedExtension/);
});

test('Python API tools are syntax-valid and contain reusable 401 authentication handling', async () => {
  const toolsDir = path.join(root, 'tools/python');
  const coverageRunnerPath = path.join(root, 'test-support/python_coverage_runner.py');
  const names = (await fs.readdir(toolsDir)).filter(name => name.endsWith('.py'));
  const expected = [
    'append_route.py', 'common.py', 'create_audio.py', 'create_picture.py', 'create_poi.py',
    'create_route.py', 'delete_audio.py', 'delete_picture.py', 'delete_poi.py', 'delete_route.py',
    'restore_route.py', 'update_audio.py', 'update_picture.py', 'update_poi.py', 'update_route.py'
  ];
  assert.deepEqual(
    names.toSorted((left, right) => left.localeCompare(right)),
    expected.toSorted((left, right) => left.localeCompare(right))
  );

  const compile = spawnSync('python3', [
    '-m', 'py_compile',
    ...names.map(name => path.join(toolsDir, name)),
    coverageRunnerPath
  ], {
    cwd: root,
    encoding: 'utf8'
  });
  assert.equal(compile.status, 0, compile.stderr);
  await fs.rm(path.join(toolsDir, '__pycache__'), { recursive: true, force: true });
  await fs.rm(path.join(root, 'test-support/__pycache__'), { recursive: true, force: true });

  const common = await read('tools/python/common.py');
  const sonar = await read('sonar-project.properties');
  const coverageRunner = await read('test-support/python_coverage_runner.py');
  const pkg = JSON.parse(await read('package.json'));
  assert.match(common, /class ApiClient/);
  assert.match(common, /error\.code != 401/);
  assert.match(common, /MAX_AUTH_ATTEMPTS/);
  assert.match(common, /self\.prompt_credentials\(\)/);
  assert.match(common, /getpass\.getpass/);
  assert.match(common, /os\.getenv\(API_USER_ENV/);
  assert.match(common, /os\.getenv\(API_PASSWORD_ENV/);
  assert.match(common, /def encode_multipart/);
  assert.match(common, /traceback\.print_exception/);
  assert.doesNotMatch(common, /Content-Type:.*application\/octet-stream/);
  assert.match(pkg.scripts['test:coverage'], /test:coverage:python/);
  assert.match(pkg.scripts['test:coverage:python'], /python_coverage_runner\.py/);
  assert.match(coverageRunner, /coverage\/python\/coverage\.xml/);
  assert.match(coverageRunner, /import trace/);
  assert.match(coverageRunner, /xml\.etree\.ElementTree/);
  assert.doesNotMatch(coverageRunner, /-m[', ]+pip|requirements-dev|python-test-deps/);
  assert.match(coverageRunner, /traceback\.print_exc/);
  assert.match(sonar, /sonar\.python\.coverage\.reportPaths=coverage\/python\/coverage\.xml/);
});


test('Python client retries an intentionally simulated reverse-proxy 401 with trimmed environment credentials', async t => {
  const apiUser = 'local-test-user';
  const apiPassword = 'local-test-password';
  const expectedAuthorization = `Basic ${Buffer.from(`${apiUser}:${apiPassword}`).toString('base64')}`;
  const receivedAuthorization = [];

  const server = http.createServer((req, res) => {
    receivedAuthorization.push(req.headers.authorization || null);
    if (req.headers.authorization !== expectedAuthorization) {
      res.writeHead(401, { 'Content-Type': 'application/json', 'WWW-Authenticate': 'Basic realm="test"' });
      res.end(JSON.stringify({ message: 'Zugangsdaten erforderlich.' }));
      return;
    }
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ ok: true }));
  });
  await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
  t.after(() => server.close());
  const { port } = server.address();

  const script = `
from common import ApiClient
result = ApiClient('http://127.0.0.1:${port}').request('GET', '/protected')
assert result == {'ok': True}
`;
  const child = spawn('python3', ['-c', script], {
    env: {
      ...process.env,
      API_USER: `  ${apiUser}  `,
      API_PASSWORD: `  ${apiPassword}  `,
      PYTHONPATH: path.join(root, 'tools/python')
    },
    stdio: ['ignore', 'pipe', 'pipe']
  });
  const stderr = [];
  child.stderr.on('data', chunk => stderr.push(chunk.toString()));
  const status = await new Promise(resolve => child.once('exit', resolve));
  assert.equal(status, 0, stderr.join(''));
  assert.deepEqual(receivedAuthorization, [null, expectedAuthorization]);
});
