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

const root = path.resolve(import.meta.dirname, '..');
const helper = path.join(root, 'tools/caddy_route_remove.sh');
const routesPath = '/config/apps/http/servers/srv1/routes';

function hostRoute(...hosts) {
  return { match: [{ host: hosts }], handle: [{ handler: 'static_response' }] };
}

function runHelper(env) {
  return new Promise((resolve, reject) => {
    const child = spawn(helper, [], {
      env: { ...process.env, ...env },
      stdio: ['ignore', 'pipe', 'pipe'],
    });
    let stdout = '';
    let stderr = '';
    child.stdout.setEncoding('utf8');
    child.stderr.setEncoding('utf8');
    child.stdout.on('data', (chunk) => { stdout += chunk; });
    child.stderr.on('data', (chunk) => { stderr += chunk; });
    child.on('error', reject);
    child.on('close', (status, signal) => resolve({ status, signal, stdout, stderr }));
  });
}

test('route removal is exact, conflict-safe, idempotent, and preserves shared routes', async (t) => {
  const temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'q4-caddy-remove-'));
  t.after(() => fs.rmSync(temporary, { recursive: true, force: true }));

  let routes = [
    hostRoute('keep-a.suby.pl'),
    hostRoute('q4.suby.pl'),
    hostRoute('not-q4.suby.pl'),
    hostRoute('q4.suby.pl'),
  ];
  let revision = 1;
  let injectConflict = true;
  const deletes = [];

  const server = http.createServer((request, response) => {
    const etag = `"${routesPath} revision-${revision}"`;
    if (request.method === 'GET' && request.url === routesPath) {
      response.setHeader('Content-Type', 'application/json');
      response.setHeader('ETag', etag);
      response.end(JSON.stringify(routes));
      return;
    }

    const match = request.url?.match(new RegExp(`^${routesPath}/([0-9]+)$`));
    if (request.method === 'DELETE' && match) {
      deletes.push({ index: Number(match[1]), etag: request.headers['if-match'] });
      if (injectConflict) {
        injectConflict = false;
        routes.unshift(hostRoute('concurrent.suby.pl'));
        revision += 1;
        response.writeHead(412).end();
        return;
      }
      if (request.headers['if-match'] !== etag) {
        response.writeHead(412).end();
        return;
      }
      routes.splice(Number(match[1]), 1);
      revision += 1;
      response.writeHead(200).end();
      return;
    }

    response.writeHead(404).end();
  });
  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  t.after(() => new Promise((resolve) => server.close(resolve)));

  const address = server.address();
  assert.ok(address && typeof address === 'object');
  const env = {
    Q4_CADDY_ADMIN: `http://127.0.0.1:${address.port}`,
    Q4_CADDY_ROUTE_LOCK: path.join(temporary, 'route.lock'),
    Q4_CADDY_REMOVE_MAX_CONFLICTS: '4',
    Q4_CADDY_REMOVE_MAX_MUTATIONS: '8',
  };

  const removed = await runHelper(env);
  assert.equal(removed.status, 0, removed.stderr);
  assert.match(removed.stdout, /removed 2 route\(s\)/);
  assert.deepEqual(
    routes.flatMap((route) => route.match[0].host),
    ['concurrent.suby.pl', 'keep-a.suby.pl', 'not-q4.suby.pl'],
  );
  assert.equal(deletes.length, 3, 'one conflict and two successful deletes are expected');
  assert.ok(deletes.every((request) => /^"\/config\/.+ revision-[0-9]+"$/.test(request.etag)));

  const deleteCount = deletes.length;
  const repeated = await runHelper(env);
  assert.equal(repeated.status, 0, repeated.stderr);
  assert.match(repeated.stdout, /removed 0 route\(s\)/);
  assert.equal(deletes.length, deleteCount, 'an absent route must not trigger DELETE');

  routes = [hostRoute('q4.suby.pl', 'keep-shared.suby.pl')];
  revision += 1;
  const shared = await runHelper(env);
  assert.notEqual(shared.status, 0);
  assert.match(shared.stderr, /refusing to delete shared route/);
  assert.equal(deletes.length, deleteCount, 'a shared route must never be deleted');
});
