const API_KEYS = [
  "PDF2HTML_API_HOST",
  "PDF2HTML_API_PORT",
  "PDF2HTML_API_ALLOWED_ORIGIN",
  "PDF2HTML_API_LOG_LEVEL",
] as const;

export type ApiLogLevel = "debug" | "info" | "warn" | "error";

export interface ApiEnvironment {
  readonly host: string;
  readonly port: number;
  readonly allowedOrigin: URL;
  readonly logLevel: ApiLogLevel;
}

export class EnvironmentValidationError extends Error {
  readonly issues: readonly string[];

  constructor(issues: readonly string[]) {
    super(`Invalid API environment: ${issues.join("; ")}`);
    this.name = "EnvironmentValidationError";
    this.issues = issues;
  }
}

function required(source: NodeJS.ProcessEnv, key: (typeof API_KEYS)[number], issues: string[]): string {
  const value = source[key];
  if (value === undefined || value.trim() === "") {
    issues.push(`${key} is required`);
    return "";
  }
  return value;
}

export function parseApiEnvironment(source: NodeJS.ProcessEnv): ApiEnvironment {
  const issues: string[] = [];
  const host = required(source, "PDF2HTML_API_HOST", issues);
  const rawPort = required(source, "PDF2HTML_API_PORT", issues);
  const rawOrigin = required(source, "PDF2HTML_API_ALLOWED_ORIGIN", issues);
  const rawLogLevel = source.PDF2HTML_API_LOG_LEVEL ?? "info";

  const unexpected = Object.keys(source).filter(
    (key) => key.startsWith("PDF2HTML_API_") && !(API_KEYS as readonly string[]).includes(key),
  );
  for (const key of unexpected) issues.push(`${key} is not supported`);

  if (host.includes("://") || /[\s/]/u.test(host)) {
    issues.push("PDF2HTML_API_HOST must be a hostname or IP address");
  }

  const port = Number(rawPort);
  if (!/^[1-9]\d{0,4}$/u.test(rawPort) || !Number.isSafeInteger(port) || port > 65_535) {
    issues.push("PDF2HTML_API_PORT must be an integer from 1 to 65535");
  }

  let allowedOrigin: URL | undefined;
  try {
    allowedOrigin = new URL(rawOrigin);
    if (
      (allowedOrigin.protocol !== "https:" && allowedOrigin.protocol !== "http:") ||
      allowedOrigin.username !== "" ||
      allowedOrigin.password !== "" ||
      allowedOrigin.pathname !== "/" ||
      allowedOrigin.search !== "" ||
      allowedOrigin.hash !== ""
    ) {
      issues.push("PDF2HTML_API_ALLOWED_ORIGIN must be an HTTP(S) origin without credentials, path, query, or fragment");
    }
  } catch {
    issues.push("PDF2HTML_API_ALLOWED_ORIGIN must be a valid URL origin");
  }

  const logLevels: readonly ApiLogLevel[] = ["debug", "info", "warn", "error"];
  if (!logLevels.includes(rawLogLevel as ApiLogLevel)) {
    issues.push("PDF2HTML_API_LOG_LEVEL must be debug, info, warn, or error");
  }

  if (issues.length > 0 || allowedOrigin === undefined) throw new EnvironmentValidationError(issues);

  return Object.freeze({
    host,
    port,
    allowedOrigin,
    logLevel: rawLogLevel as ApiLogLevel,
  });
}
