import test from "node:test";
import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const testDirectory = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(testDirectory, "../../..");

function readRequiredSource(...pathSegments) {
  const sourcePath = path.resolve(repoRoot, ...pathSegments);
  assert.equal(existsSync(sourcePath), true, `Expected config fixture to exist: ${sourcePath}`);
  return readFileSync(sourcePath, "utf8");
}

const baseComposeSource = readRequiredSource("docker-compose.yml");
const exampleComposeSource = readRequiredSource("docker-compose.example.yml");
const standaloneProdComposeSource = readRequiredSource("docker-compose.prod.standalone.yml");
const traefikSource = [
  readRequiredSource("services", "traefik", "traefik.yml"),
  readRequiredSource("services", "traefik", "traefik.prod.yml"),
].join("\n");

function readComposeServiceBlock(composeSource, serviceName) {
  const escapedServiceName = serviceName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  const servicePattern = new RegExp(
    `^[ ]{2}${escapedServiceName}:\\r?\\n([\\s\\S]*?)(?=^[ ]{2}[A-Za-z0-9_-]+:|^volumes:|^networks:|(?![\\s\\S]))`,
    "m"
  );
  const match = composeSource.match(servicePattern);
  assert.ok(match, `Expected docker compose service block for ${serviceName}`);
  return match[0];
}

test("traefik does not expose a dedicated public edge broker port", () => {
  assert.doesNotMatch(traefikSource, /edge-broker:\s*\n\s*address:\s*":4300"/);
});

test("base docker compose routes edge broker traffic through traefik", () => {
  const serviceBlock = readComposeServiceBlock(baseComposeSource, "edge-broker");
  assert.doesNotMatch(serviceBlock, /\n\s+ports:\s*\n[\s\S]*?\n\s+- "4300:4300"/);
  assert.match(serviceBlock, /EDGE_AUTH_MODE:\s*\$\x7bEDGE_AUTH_MODE:-strict\x7d/);
  assert.match(serviceBlock, /EDGE_MANAGER_URL:\s*\$\x7bEDGE_MANAGER_URL:-http:\/\/caddy\x7d/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api\.priority=200/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-local\.priority=200/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api\.rule=Host\(`api\.truckwash\.dk`\) && PathPrefix\(`\/edge-broker`\)/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api-io\.rule=Host\(`api\.truckwash\.io`\) && PathPrefix\(`\/edge-broker`\)/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api-v2\.rule=Host\(`api-v2\.truckwash\.io`\) && PathPrefix\(`\/edge-broker`\)/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-local\.rule=Host\(`localhost`\) && PathPrefix\(`\/api\/edge-broker`\)/);
  assert.match(serviceBlock, /traefik\.http\.middlewares\.edge-broker-strip\.stripPrefix\.prefixes=\/edge-broker/);
  assert.match(serviceBlock, /traefik\.http\.middlewares\.edge-broker-strip-local\.stripPrefix\.prefixes=\/api\/edge-broker/);
  assert.match(serviceBlock, /traefik\.http\.services\.edge-broker\.loadbalancer\.server\.port=4300/);
});

test("example docker compose routes edge broker traffic through traefik", () => {
  const serviceBlock = readComposeServiceBlock(exampleComposeSource, "edge-broker");
  assert.doesNotMatch(serviceBlock, /\n\s+ports:\s*\n[\s\S]*?\n\s+- "4300:4300"/);
  assert.match(serviceBlock, /EDGE_AUTH_MODE:\s*\$\x7bEDGE_AUTH_MODE:-strict\x7d/);
  assert.match(serviceBlock, /EDGE_MANAGER_URL:\s*\$\x7bEDGE_MANAGER_URL:-http:\/\/caddy\x7d/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api\.rule=Host\(`api\.example\.com`\) && PathPrefix\(`\/edge-broker`\)/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-local\.rule=Host\(`localhost`\) && PathPrefix\(`\/api\/edge-broker`\)/);
  assert.match(serviceBlock, /traefik\.http\.services\.edge-broker\.loadbalancer\.server\.port=4300/);
});

test("standalone production compose routes edge broker traffic through traefik", () => {
  const serviceBlock = readComposeServiceBlock(standaloneProdComposeSource, "edge-broker");
  assert.doesNotMatch(serviceBlock, /\n\s+ports:\s*\n[\s\S]*?\n\s+- "4300:4300"/);
  assert.match(serviceBlock, /EDGE_AUTH_MODE:\s*\$\x7bEDGE_AUTH_MODE:-manager\x7d/);
  assert.match(serviceBlock, /EDGE_MANAGER_URL:\s*\$\x7bEDGE_MANAGER_URL:-http:\/\/caddy\x7d/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api\.priority=200/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-local\.priority=200/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api\.rule=Host\(`api\.truckwash\.dk`\) && PathPrefix\(`\/edge-broker`\)/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api-io\.rule=Host\(`api\.truckwash\.io`\) && PathPrefix\(`\/edge-broker`\)/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-api-v2\.rule=Host\(`api-v2\.truckwash\.io`\) && PathPrefix\(`\/edge-broker`\)/);
  assert.match(serviceBlock, /traefik\.http\.routers\.edge-broker-local\.rule=Host\(`localhost`\) && PathPrefix\(`\/api\/edge-broker`\)/);
  assert.match(serviceBlock, /traefik\.http\.services\.edge-broker\.loadbalancer\.server\.port=4300/);
});

test("compose config does not provide insecure broker secret defaults", () => {
  for (const composeSource of [baseComposeSource, exampleComposeSource]) {
    assert.match(composeSource, /EDGE_BROKER_URL:\s*\$\x7bEDGE_BROKER_URL:-http:\/\/edge-broker:4300\x7d/);
    assert.match(composeSource, /EDGE_BROKER_SHARED_SECRET:\s*\$\x7bEDGE_BROKER_SHARED_SECRET:\?set EDGE_BROKER_SHARED_SECRET in \.env\x7d/);
  }
});

test("base docker compose wires the broker into each php worker", () => {
  for (const serviceName of ["php1", "php2", "php3", "php4", "php5", "php-staging", "php-cron"]) {
    const serviceBlock = readComposeServiceBlock(baseComposeSource, serviceName);
    assert.match(serviceBlock, /\n\s+depends_on:\s*\n[\s\S]*?\n\s+- edge-broker/);
    assert.match(serviceBlock, /EDGE_BROKER_URL:\s*\$\x7bEDGE_BROKER_URL:-http:\/\/edge-broker:4300\x7d/);
    assert.match(serviceBlock, /EDGE_BROKER_SHARED_SECRET:\s*\$\x7bEDGE_BROKER_SHARED_SECRET:\?set EDGE_BROKER_SHARED_SECRET in \.env\x7d/);
  }
});
