import { describe, expect, it } from "vitest";
import {
  buildExecutionEnv,
  buildPlaywrightEnv,
  buildPortMap,
  finalizeString,
  getWorkerServiceStateDir,
  normalizeSocketHost,
  numericPortFromUrl,
  resolveServiceStateDir,
  resolveTemplateString,
  resolveWorkerRuntimeConfigs,
  rewriteUrlPort,
  socketFromUrl,
} from "./template.mjs";

function makeRuntimeConfig(name, local, extras = {}) {
  return {
    name,
    testkit: {
      local,
      serviceEnv: extras.serviceEnv || {},
      databaseFrom: extras.databaseFrom,
      migrate: extras.migrate,
      seed: extras.seed,
    },
  };
}

describe("runner-template", () => {
  it("builds port maps and detects collisions", () => {
    const configs = [
      makeRuntimeConfig("api", { port: 3000, baseUrl: "http://127.0.0.1:{port}" }),
      makeRuntimeConfig("frontend", { port: 3001, baseUrl: "http://127.0.0.1:{port}" }),
    ];

    expect([...buildPortMap(configs, 2).entries()]).toEqual([
      ["api", 3100],
      ["frontend", 3101],
    ]);

    expect(() =>
      buildPortMap(
        [
          makeRuntimeConfig("api", { port: 3000, baseUrl: "http://127.0.0.1:{port}" }),
          makeRuntimeConfig("other", { port: 3000, baseUrl: "http://127.0.0.1:{port}" }),
        ],
        1
      )
    ).toThrow("Worker port collision");
  });

  it("resolves template strings and URL rewrites", () => {
    const context = {
      workerId: 2,
      targetName: "frontend",
      serviceName: "frontend",
      serviceStateDir: "/tmp/state",
      portMap: new Map([
        ["frontend", 3200],
        ["api", 3100],
      ]),
      baseUrlByService: new Map([["api", "http://127.0.0.1:3100"]]),
      readyUrlByService: new Map([["api", "http://127.0.0.1:3100/health"]]),
      urlMappings: [["http://api:3000", "http://127.0.0.1:3100"]],
    };

    expect(resolveTemplateString("{worker}:{target}:{service}", context)).toBe("2:frontend:frontend");
    expect(resolveTemplateString("{baseUrl:api}", context)).toBe("http://127.0.0.1:3100");
    expect(finalizeString("API={baseUrl:api} OLD=http://api:3000", context)).toBe(
      "API=http://127.0.0.1:3100 OLD=http://127.0.0.1:3100"
    );
    expect(rewriteUrlPort("http://127.0.0.1:3000/health", 3200)).toBe(
      "http://127.0.0.1:3200/health"
    );
  });

  it("builds worker runtime configs and execution env", () => {
    const api = makeRuntimeConfig("api", {
      cwd: ".",
      start: "npm run api",
      port: 3000,
      baseUrl: "http://127.0.0.1:{port}",
      readyUrl: "http://127.0.0.1:{port}/health",
      env: {
        PORT: "{port}",
      },
    }, {
      serviceEnv: {
        API_KEY: "secret",
      },
    });
    const frontend = makeRuntimeConfig("frontend", {
      cwd: "frontend",
      start: "npm run web",
      port: 3001,
      baseUrl: "http://127.0.0.1:{port}",
      readyUrl: "http://127.0.0.1:{port}",
      env: {
        NEXT_PUBLIC_API_URL: "{baseUrl:api}",
      },
    });

    const resolved = resolveWorkerRuntimeConfigs(frontend, [api, frontend], 2, "/tmp/w2");
    expect(resolved[0].testkit.local.port).toBe(3100);
    expect(resolved[1].testkit.local.env.NEXT_PUBLIC_API_URL).toBe("http://127.0.0.1:3100");
    expect(resolveServiceStateDir("/tmp/w2", "frontend", api)).toBe("/tmp/w2/deps/api");
    expect(getWorkerServiceStateDir("/tmp/w2", "frontend", "frontend")).toBe("/tmp/w2");

    expect(
      buildExecutionEnv(
        {
          workerId: 2,
          testkit: {
            serviceEnv: {
              API_KEY: "secret",
            },
          },
        },
        {
          DATABASE_URL: "gone",
        },
        {
          PATH: "/usr/bin",
        }
      )
    ).toEqual({
      PATH: "/usr/bin",
      API_KEY: "secret",
      TESTKIT_ACTIVE: "1",
      TESTKIT_WORKER_ID: "2",
    });

    expect(buildPlaywrightEnv({ workerId: 3, testkit: { serviceEnv: {} } }, "http://localhost:3000", {}))
      .toMatchObject({
        BASE_URL: "http://localhost:3000",
        TESTKIT_WORKER_ID: "3",
        PLAYWRIGHT_HTML_OPEN: "never",
      });
  });

  it("parses runtime sockets", () => {
    expect(numericPortFromUrl("http://localhost:3000")).toBe(3000);
    expect(socketFromUrl("http://localhost:3000")).toEqual({
      host: "127.0.0.1",
      port: 3000,
    });
    expect(normalizeSocketHost("[::1]")).toBe("::1");
  });
});
