import http from 'node:http';

const PROTECTED_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);

function increment(counter, method) {
  counter[method] = (counter[method] || 0) + 1;
}

function basicAuthorization(username, password) {
  const token = Buffer.from(`${username}:${password}`, 'utf8').toString('base64');
  return `Basic ${token}`;
}

function unauthorized(response) {
  response.writeHead(401, {
    'Content-Type': 'application/json; charset=utf-8',
    'WWW-Authenticate': 'Basic realm="Wegwichtel local test"'
  });
  response.end(JSON.stringify({
    error: 'Unauthorized',
    message: 'Für schreibende Operationen sind Zugangsdaten erforderlich.'
  }));
}

function badGateway(response, error) {
  if (response.headersSent) {
    response.destroy(error);
    return;
  }
  response.writeHead(502, { 'Content-Type': 'application/json; charset=utf-8' });
  response.end(JSON.stringify({
    error: 'BadGateway',
    message: `Lokaler Testserver nicht erreichbar: ${error.message}`
  }));
}

export async function startLocalWriteAuthProxy({
  listenHost = '127.0.0.1',
  listenPort = 47145,
  targetHost = '127.0.0.1',
  targetPort,
  username,
  password
}) {
  if (!targetPort) throw new TypeError('targetPort ist erforderlich.');
  if (!username || !password) throw new TypeError('Test-Credentials sind erforderlich.');

  const expectedAuthorization = basicAuthorization(username, password);
  const stats = {
    unauthorizedWrites: {},
    authorizedWrites: {},
    forwardedReads: 0,
    authenticatedReads: 0
  };

  const server = http.createServer((request, response) => {
    const method = (request.method || 'GET').toUpperCase();
    const isProtected = PROTECTED_METHODS.has(method);
    const suppliedAuthorization = request.headers.authorization;

    if (isProtected && suppliedAuthorization !== expectedAuthorization) {
      increment(stats.unauthorizedWrites, method);
      request.resume();
      unauthorized(response);
      return;
    }

    if (isProtected) {
      increment(stats.authorizedWrites, method);
    } else {
      stats.forwardedReads += 1;
      if (suppliedAuthorization) stats.authenticatedReads += 1;
    }

    const headers = { ...request.headers, host: `${targetHost}:${targetPort}` };
    delete headers.authorization;

    const upstream = http.request({
      host: targetHost,
      port: targetPort,
      method,
      path: request.url,
      headers
    }, upstreamResponse => {
      response.writeHead(upstreamResponse.statusCode || 502, upstreamResponse.headers);
      upstreamResponse.pipe(response);
    });

    upstream.on('error', error => badGateway(response, error));
    request.pipe(upstream);
  });

  await new Promise((resolve, reject) => {
    server.once('error', reject);
    server.listen(listenPort, listenHost, () => {
      server.off('error', reject);
      resolve();
    });
  });

  return {
    stats,
    async close() {
      if (!server.listening) return;
      await new Promise((resolve, reject) => {
        server.close(error => error ? reject(error) : resolve());
      });
    }
  };
}
