import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { toErrorMessage } from "../util";

export interface MockServer {
  url: string;
  port: number;
  stop: () => Promise<void>;
}

/**
 * Start a Mockoon mock server from a mock.json file.
 * Returns a handle with the server URL and a stop function.
 */
export async function createMockServer(mockJsonPath: string, port?: number): Promise<MockServer> {
  const { MockoonServer, createLoggerInstance, listenServerEvents } =
    await import("@mockoon/commons-server");
  type Environment = import("@mockoon/commons").Environment;
  const commons = await import("@mockoon/commons");

  const resolvedPath = resolve(mockJsonPath);
  let parsed: unknown;
  try {
    parsed = JSON.parse(readFileSync(resolvedPath, "utf-8"));
  } catch (err) {
    throw new Error(`Failed to read mock config "${resolvedPath}": ${toErrorMessage(err)}`);
  }

  // EnvironmentSchema is a Joi schema typed as `any` in @mockoon/commons;
  // it validates the shape and fills in Mockoon defaults
  const schema = commons.EnvironmentSchema as {
    validate: (
      value: unknown,
      options?: { abortEarly?: boolean },
    ) => { value: Environment; error?: { details: { message: string }[] } };
  };
  const result = schema.validate(parsed, { abortEarly: false });
  if (result.error) {
    const details = result.error.details.map((d) => d.message).join(", ");
    throw new Error(`Invalid Mockoon environment "${resolvedPath}": ${details}`);
  }
  const environment = result.value;
  if (port !== undefined) {
    environment.port = port;
  }
  const actualPort = environment.port;

  const logger = createLoggerInstance(null);
  const server = new MockoonServer(environment, {
    environmentDirectory: dirname(resolvedPath),
    enableAdminApi: false,
    disableTls: true,
    enableRandomLatency: false,
    maxTransactionLogs: 100,
    envVarsPrefix: "MOCKOON_",
  });

  listenServerEvents(server, environment, logger, false);

  await new Promise<void>((resolve, reject) => {
    server.on("started", resolve);
    server.on("error", (errorCode, originalError) => {
      reject(originalError ?? new Error(`Mockoon error: ${errorCode}`));
    });
    server.start();
  });

  return {
    url: `http://127.0.0.1:${actualPort}`,
    port: actualPort,
    stop: () =>
      new Promise<void>((resolve) => {
        server.on("stopped", resolve);
        server.stop();
      }),
  };
}
