import { mkdirSync } from "node:fs";
import { type IncomingMessage, type ServerResponse } from "node:http";
import { createRequire } from "node:module";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import type {
  AppConfig,
  GatewayPluginAppConfig,
  GatewayPluginConfig,
  GatewayPluginProxyRouteConfig,
  GatewayProviderConfig,
  InstalledBrowserApp,
  ProviderAccountMeter,
  ProviderAccountPluginConnectorConfig,
  ProviderAccountSnapshot
} from "@ccr/core/contracts/app";
import { backendService, type RegisteredHttpBackend, type SqliteStore, type SqliteStoreOptions } from "@ccr/core/plugins/backend-service";
import { CONFIGDIR, DATADIR } from "@ccr/core/config/constants";

type MaybePromise<T> = T | Promise<T>;
type PluginLogger = {
  debug: (...args: unknown[]) => void;
  error: (...args: unknown[]) => void;
  info: (...args: unknown[]) => void;
  warn: (...args: unknown[]) => void;
};

export type GatewayPluginRouteHandler = (
  request: IncomingMessage,
  response: ServerResponse,
  context: GatewayPluginRouteContext
) => MaybePromise<void>;

export type GatewayPluginRouteRegistration = {
  auth?: "gateway" | "none";
  handler: GatewayPluginRouteHandler;
  id?: string;
  method?: string;
  methods?: string[];
  path?: string;
  pathPrefix?: string;
};

export type GatewayPluginProxyRouteRegistration = Omit<GatewayPluginProxyRouteConfig, "upstream"> & {
  upstream: string | URL | (() => string | URL);
};

export type GatewayPluginHttpBackendRegistration = {
  handler: GatewayPluginRouteHandler;
  host?: string;
  id?: string;
  port?: number;
};

export type GatewayPluginProviderAccountRequest = {
  config: AppConfig;
  connector: ProviderAccountPluginConnectorConfig;
  now: string;
  provider: GatewayProviderConfig;
};

export type GatewayPluginProviderAccountConnector = {
  id: string;
  resolve: (request: GatewayPluginProviderAccountRequest) => MaybePromise<ProviderAccountMeter[] | ProviderAccountSnapshot | undefined>;
};

export type GatewayPluginRegistration = {
  apps?: GatewayPluginAppConfig[];
  coreGateway?: {
    config?: Record<string, unknown>;
    providerPlugins?: unknown[];
    virtualModelProfiles?: unknown[];
  };
  gatewayRoutes?: GatewayPluginRouteRegistration[];
  onStop?: () => MaybePromise<void>;
  providerAccountConnectors?: GatewayPluginProviderAccountConnector[];
  proxyRoutes?: GatewayPluginProxyRouteRegistration[];
  stop?: () => MaybePromise<void>;
  virtualModelProfiles?: unknown[];
};

export type GatewayPluginContext = {
  config: AppConfig;
  logger: PluginLogger;
  paths: {
    configDir: string;
    dataDir: string;
    pluginDataDir: string;
  };
  pluginConfig: unknown;
  pluginId: string;
  openSqliteStore: (options?: PluginSqliteStoreOptions) => Promise<PluginSqliteStore>;
  registerCoreGatewayProviderPlugin: (providerPlugin: unknown) => void;
  registerCoreGatewayVirtualModelProfile: (profile: unknown) => void;
  registerApp: (app: GatewayPluginAppConfig) => void;
  registerGatewayRoute: (route: GatewayPluginRouteRegistration) => void;
  registerHttpBackend: (backend: GatewayPluginHttpBackendRegistration) => Promise<RegisteredHttpBackend>;
  registerProviderAccountConnector: (connector: GatewayPluginProviderAccountConnector) => void;
  registerProxyRoute: (route: GatewayPluginProxyRouteRegistration) => void;
};

export type GatewayPluginRouteContext = Pick<
  GatewayPluginContext,
  "config" | "logger" | "openSqliteStore" | "paths" | "pluginConfig" | "pluginId"
> & {
  readBody: (request: IncomingMessage) => Promise<Buffer>;
  readJson: (request: IncomingMessage) => Promise<unknown>;
  sendJson: (response: ServerResponse, statusCode: number, body: unknown) => void;
};

export type PluginSqliteStoreOptions = SqliteStoreOptions;
export type PluginSqliteStore = SqliteStore;

export type GatewayPluginRouteMatch = RegisteredGatewayRoute;

export type GatewayPluginProxyRouteMatch = {
  headers?: Record<string, string>;
  id: string;
  preserveHost: boolean;
  pluginId: string;
  targetUrl: URL;
  upstreamUrl: URL;
};

type RegisteredGatewayRoute = Required<Pick<GatewayPluginRouteRegistration, "handler" | "id">> & {
  auth: "gateway" | "none";
  methods?: string[];
  path?: string;
  pathPrefix?: string;
  pluginId: string;
};

type RegisteredProxyRoute = Omit<GatewayPluginProxyRouteRegistration, "host" | "id" | "paths"> & {
  host: string;
  id: string;
  paths?: string[];
  pluginId: string;
};

type LoadedPlugin = {
  activate?: (context: GatewayPluginContext) => MaybePromise<GatewayPluginRegistration | void>;
  setup?: (context: GatewayPluginContext) => MaybePromise<GatewayPluginRegistration | void>;
  stop?: () => MaybePromise<void>;
};

type PluginServiceStateSnapshot = {
  apps: InstalledBrowserApp[];
  coreGatewayConfig: Record<string, unknown>;
  coreProviderPlugins: unknown[];
  gatewayRoutes: RegisteredGatewayRoute[];
  providerAccountConnectors: Map<string, GatewayPluginProviderAccountConnector>;
  proxyRoutes: RegisteredProxyRoute[];
  resourceOwnerIds: Set<string>;
  stopHooks: Array<() => MaybePromise<void>>;
  virtualModelProfiles: unknown[];
};

const requireFromHere = createRequire(__filename);
const builtInMarketplacePluginModules = new Map<string, string>([
  ["claude-design", path.join(__dirname, "..", "marketplace", "plugins", "claude-design-plugin.cjs")],
  ["cursor-proxy", path.join(__dirname, "..", "marketplace", "plugins", "cursor-proxy-plugin.cjs")]
]);

class GatewayPluginService {
  private config?: AppConfig;
  private coreGatewayConfig: Record<string, unknown> = {};
  private coreProviderPlugins: unknown[] = [];
  private apps: InstalledBrowserApp[] = [];
  private gatewayRoutes: RegisteredGatewayRoute[] = [];
  private proxyRoutes: RegisteredProxyRoute[] = [];
  private providerAccountConnectors = new Map<string, GatewayPluginProviderAccountConnector>();
  private resourceOwnerIds = new Set<string>();
  private running = false;
  private stopHooks: Array<() => MaybePromise<void>> = [];
  private virtualModelProfiles: unknown[] = [];

  async start(config: AppConfig): Promise<void> {
    await this.stop();
    this.config = config;
    this.running = true;

    for (const pluginConfig of config.plugins ?? []) {
      if (pluginConfig.enabled === false) {
        continue;
      }
      const snapshot = this.createStateSnapshot();
      this.resourceOwnerIds.add(pluginConfig.id);
      try {
        await this.loadConfiguredPlugin(pluginConfig);
      } catch (error) {
        await this.rollbackConfiguredPluginLoad(pluginConfig.id, snapshot);
        console.warn(`[plugin:${pluginConfig.id}] Disabled after startup failure: ${formatError(error)}`);
      }
    }
  }

  async stop(): Promise<void> {
    const stopHooks = [...this.stopHooks].reverse();
    this.stopHooks = [];

    for (const stopHook of stopHooks) {
      try {
        await stopHook();
      } catch (error) {
        console.warn(`[plugin] Stop hook failed: ${formatError(error)}`);
      }
    }

    const resourceOwnerIds = [...this.resourceOwnerIds].reverse();
    this.resourceOwnerIds.clear();
    for (const ownerId of resourceOwnerIds) {
      await backendService.stopOwner(ownerId);
    }

    this.config = undefined;
    this.apps = [];
    this.coreGatewayConfig = {};
    this.coreProviderPlugins = [];
    this.gatewayRoutes = [];
    this.proxyRoutes = [];
    this.providerAccountConnectors.clear();
    this.running = false;
    this.virtualModelProfiles = [];
  }

  hasGatewayRoutes(): boolean {
    return this.gatewayRoutes.length > 0;
  }

  getCoreGatewayConfig(): Record<string, unknown> {
    return { ...this.coreGatewayConfig };
  }

  getCoreProviderPlugins(): unknown[] {
    return [...this.coreProviderPlugins];
  }

  getVirtualModelProfiles(): unknown[] {
    return [...this.virtualModelProfiles];
  }

  getApps(): InstalledBrowserApp[] {
    return this.apps.map((app) => ({ ...app }));
  }

  getProviderAccountConnector(pluginId: string, connectorId: string): GatewayPluginProviderAccountConnector | undefined {
    return this.providerAccountConnectors.get(providerAccountConnectorKey(pluginId, connectorId));
  }

  getProxyRouteHosts(): string[] {
    return [...new Set(this.proxyRoutes.map((route) => route.host))];
  }

  getProxyRouteTargets(): Array<{ host: string; paths?: string[] }> {
    return this.proxyRoutes.map((route) => ({
      host: route.host,
      paths: route.paths ? [...route.paths] : undefined
    }));
  }

  matchGatewayRoute(method: string | undefined, requestPath: string): GatewayPluginRouteMatch | undefined {
    const normalizedMethod = (method || "GET").toUpperCase();
    return this.gatewayRoutes.find((route) => {
      if (route.methods?.length && !route.methods.includes(normalizedMethod)) {
        return false;
      }
      if (route.path && requestPath === route.path) {
        return true;
      }
      if (route.pathPrefix && matchesPathPrefix(route.pathPrefix, requestPath)) {
        return true;
      }
      return false;
    });
  }

  async handleGatewayRoute(route: GatewayPluginRouteMatch, request: IncomingMessage, response: ServerResponse): Promise<void> {
    if (!this.config) {
      throw new Error("Gateway plugin service is not configured.");
    }
    await route.handler(request, response, this.createRouteContext(route.pluginId));
  }

  resolveProxyRoute(targetUrl: URL): GatewayPluginProxyRouteMatch | undefined {
    let bestMatch: { matchedPathPrefix: string; route: RegisteredProxyRoute } | undefined;
    for (const route of this.proxyRoutes) {
      const matchedPathPrefix = matchProxyRoute(route, targetUrl);
      if (matchedPathPrefix === undefined) {
        continue;
      }

      if (!bestMatch || matchedPathPrefix.length > bestMatch.matchedPathPrefix.length) {
        bestMatch = { matchedPathPrefix, route };
      }
    }
    if (!bestMatch) {
      return undefined;
    }

    return {
      headers: bestMatch.route.headers,
      id: bestMatch.route.id,
      pluginId: bestMatch.route.pluginId,
      preserveHost: bestMatch.route.preserveHost === true,
      targetUrl,
      upstreamUrl: buildPluginProxyUpstreamUrl(bestMatch.route, targetUrl, bestMatch.matchedPathPrefix)
    };
  }

  private async loadConfiguredPlugin(pluginConfig: GatewayPluginConfig): Promise<void> {
    this.registerConfiguredCoreGateway(pluginConfig);
    this.registerConfiguredApps(pluginConfig);
    for (const route of pluginConfig.proxy?.routes ?? []) {
      this.registerProxyRoute(pluginConfig.id, route);
    }

    const modulePath = pluginConfig.module || builtInMarketplacePluginModules.get(pluginConfig.id);
    if (!modulePath) {
      return;
    }

    const loadedPlugin = await loadPluginModule(modulePath);
    const plugin = normalizeLoadedPlugin(loadedPlugin);
    const context = this.createPluginContext(pluginConfig);
    const registration = plugin.setup
      ? await plugin.setup(context)
      : plugin.activate
        ? await plugin.activate(context)
        : undefined;

    if (registration) {
      this.applyPluginRegistration(pluginConfig.id, registration);
    }
    if (plugin.stop) {
      this.stopHooks.push(() => plugin.stop?.());
    }
  }

  private applyPluginRegistration(pluginId: string, registration: GatewayPluginRegistration): void {
    for (const app of registration.apps ?? []) {
      this.registerApp(pluginId, app);
    }
    for (const route of registration.gatewayRoutes ?? []) {
      this.registerGatewayRoute(pluginId, route);
    }
    for (const route of registration.proxyRoutes ?? []) {
      this.registerProxyRoute(pluginId, route);
    }
    for (const connector of registration.providerAccountConnectors ?? []) {
      this.registerProviderAccountConnector(pluginId, connector);
    }
    for (const providerPlugin of registration.coreGateway?.providerPlugins ?? []) {
      this.coreProviderPlugins.push(providerPlugin);
    }
    for (const profile of [
      ...(registration.coreGateway?.virtualModelProfiles ?? []),
      ...(registration.virtualModelProfiles ?? [])
    ]) {
      this.virtualModelProfiles.push(profile);
    }
    if (registration.coreGateway?.config) {
      this.coreGatewayConfig = {
        ...this.coreGatewayConfig,
        ...registration.coreGateway.config
      };
    }
    if (registration.stop) {
      this.stopHooks.push(registration.stop);
    }
    if (registration.onStop) {
      this.stopHooks.push(registration.onStop);
    }
  }

  private registerConfiguredApps(pluginConfig: GatewayPluginConfig): void {
    for (const app of pluginConfig.apps ?? []) {
      this.registerApp(pluginConfig.id, app);
    }
  }

  private registerApp(pluginId: string, app: GatewayPluginAppConfig): void {
    const normalized = normalizePluginApp(pluginId, app, this.apps.length + 1);
    if (!normalized) {
      return;
    }
    this.apps = this.apps.filter((item) => !(item.pluginId === pluginId && item.id === normalized.id));
    this.apps.push(normalized);
  }

  private registerConfiguredCoreGateway(pluginConfig: GatewayPluginConfig): void {
    for (const providerPlugin of pluginConfig.coreGateway?.providerPlugins ?? []) {
      this.coreProviderPlugins.push(providerPlugin);
    }
    for (const profile of pluginConfig.coreGateway?.virtualModelProfiles ?? []) {
      this.virtualModelProfiles.push(profile);
    }
    if (pluginConfig.coreGateway?.config) {
      this.coreGatewayConfig = {
        ...this.coreGatewayConfig,
        ...pluginConfig.coreGateway.config
      };
    }
  }

  private registerGatewayRoute(pluginId: string, route: GatewayPluginRouteRegistration): void {
    if (!route.path && !route.pathPrefix) {
      throw new Error(`Plugin ${pluginId} registered a gateway route without path or pathPrefix.`);
    }

    this.gatewayRoutes.push({
      auth: route.auth ?? "gateway",
      handler: route.handler,
      id: route.id || `${pluginId}:gateway:${this.gatewayRoutes.length + 1}`,
      methods: normalizeMethods(route),
      path: normalizeRoutePath(route.path),
      pathPrefix: normalizeRoutePath(route.pathPrefix),
      pluginId
    });
  }

  private registerProxyRoute(pluginId: string, route: GatewayPluginProxyRouteRegistration): void {
    const host = route.host.trim().toLowerCase();
    if (!host) {
      throw new Error(`Plugin ${pluginId} registered a proxy route without host.`);
    }

    this.proxyRoutes.push({
      ...route,
      host,
      id: route.id || `${pluginId}:proxy:${this.proxyRoutes.length + 1}`,
      paths: route.paths?.map(normalizeRoutePath).filter((path): path is string => Boolean(path)),
      pluginId
    });
  }

  private createPluginContext(pluginConfig: GatewayPluginConfig): GatewayPluginContext {
    const pluginDataDir = path.join(DATADIR, "plugins", sanitizeFileSegment(pluginConfig.id));
    mkdirSync(pluginDataDir, { recursive: true });
    const logger = createPluginLogger(pluginConfig.id);

    return {
      config: this.config ?? ({} as AppConfig),
      logger,
      paths: {
        configDir: CONFIGDIR,
        dataDir: DATADIR,
        pluginDataDir
      },
      pluginConfig: pluginConfig.config,
      pluginId: pluginConfig.id,
      openSqliteStore: (options) => this.openSqliteStore(pluginConfig.id, pluginDataDir, options),
      registerCoreGatewayProviderPlugin: (providerPlugin) => {
        this.coreProviderPlugins.push(providerPlugin);
      },
      registerCoreGatewayVirtualModelProfile: (profile) => {
        this.virtualModelProfiles.push(profile);
      },
      registerApp: (app) => this.registerApp(pluginConfig.id, app),
      registerGatewayRoute: (route) => this.registerGatewayRoute(pluginConfig.id, route),
      registerHttpBackend: (backend) => this.registerHttpBackend(pluginConfig.id, pluginDataDir, logger, backend),
      registerProviderAccountConnector: (connector) => this.registerProviderAccountConnector(pluginConfig.id, connector),
      registerProxyRoute: (route) => this.registerProxyRoute(pluginConfig.id, route)
    };
  }

  private registerProviderAccountConnector(pluginId: string, connector: GatewayPluginProviderAccountConnector): void {
    const id = connector.id.trim();
    if (!id || typeof connector.resolve !== "function") {
      throw new Error(`Plugin ${pluginId} registered an invalid provider account connector.`);
    }
    this.providerAccountConnectors.set(providerAccountConnectorKey(pluginId, id), {
      ...connector,
      id
    });
  }

  private createRouteContext(pluginId: string): GatewayPluginRouteContext {
    const pluginDataDir = path.join(DATADIR, "plugins", sanitizeFileSegment(pluginId));
    const logger = createPluginLogger(pluginId);
    return {
      config: this.config ?? ({} as AppConfig),
      logger,
      paths: {
        configDir: CONFIGDIR,
        dataDir: DATADIR,
        pluginDataDir
      },
      pluginConfig: this.config?.plugins.find((plugin) => plugin.id === pluginId)?.config,
      pluginId,
      openSqliteStore: (options) => this.openSqliteStore(pluginId, pluginDataDir, options),
      readBody,
      readJson,
      sendJson
    };
  }

  private async registerHttpBackend(
    pluginId: string,
    pluginDataDir: string,
    logger: PluginLogger,
    backend: GatewayPluginHttpBackendRegistration
  ): Promise<RegisteredHttpBackend> {
    return backendService.registerHttpBackend(pluginId, {
      host: backend.host,
      id: backend.id,
      port: backend.port,
      handler: (request, response) =>
        backend.handler(request, response, {
          config: this.config ?? ({} as AppConfig),
          logger,
          paths: {
            configDir: CONFIGDIR,
            dataDir: DATADIR,
            pluginDataDir
          },
          pluginConfig: this.config?.plugins.find((plugin) => plugin.id === pluginId)?.config,
          pluginId,
          openSqliteStore: (options) => this.openSqliteStore(pluginId, pluginDataDir, options),
          readBody,
          readJson,
          sendJson
        })
    });
  }

  private async openSqliteStore(
    pluginId: string,
    pluginDataDir: string,
    options: PluginSqliteStoreOptions = {}
  ): Promise<PluginSqliteStore> {
    return backendService.openSqliteStore(pluginId, pluginDataDir, options);
  }

  private createStateSnapshot(): PluginServiceStateSnapshot {
    return {
      apps: [...this.apps],
      coreGatewayConfig: { ...this.coreGatewayConfig },
      coreProviderPlugins: [...this.coreProviderPlugins],
      gatewayRoutes: [...this.gatewayRoutes],
      providerAccountConnectors: new Map(this.providerAccountConnectors),
      proxyRoutes: [...this.proxyRoutes],
      resourceOwnerIds: new Set(this.resourceOwnerIds),
      stopHooks: [...this.stopHooks],
      virtualModelProfiles: [...this.virtualModelProfiles]
    };
  }

  private async rollbackConfiguredPluginLoad(pluginId: string, snapshot: PluginServiceStateSnapshot): Promise<void> {
    const newStopHooks = this.stopHooks.slice(snapshot.stopHooks.length).reverse();
    this.apps = snapshot.apps;
    this.coreGatewayConfig = snapshot.coreGatewayConfig;
    this.coreProviderPlugins = snapshot.coreProviderPlugins;
    this.gatewayRoutes = snapshot.gatewayRoutes;
    this.providerAccountConnectors = snapshot.providerAccountConnectors;
    this.proxyRoutes = snapshot.proxyRoutes;
    this.resourceOwnerIds = snapshot.resourceOwnerIds;
    this.stopHooks = snapshot.stopHooks;
    this.virtualModelProfiles = snapshot.virtualModelProfiles;

    for (const stopHook of newStopHooks) {
      try {
        await stopHook();
      } catch (error) {
        console.warn(`[plugin:${pluginId}] Rollback stop hook failed: ${formatError(error)}`);
      }
    }

    try {
      await backendService.stopOwner(pluginId);
    } catch (error) {
      console.warn(`[plugin:${pluginId}] Rollback resource cleanup failed: ${formatError(error)}`);
    }
  }
}

export const pluginService = new GatewayPluginService();

async function loadPluginModule(modulePath: string): Promise<unknown> {
  const resolved = resolvePluginModule(modulePath);
  return import(pathToFileURL(resolved).href);
}

function resolvePluginModule(modulePath: string): string {
  const resolved = requireFromHere.resolve(resolveLocalModulePath(modulePath, "Plugin module"));
  assertJavaScriptModulePath(resolved, "Plugin module");
  return resolved;
}

function resolveLocalModulePath(value: string, label: string): string {
  const trimmed = value.trim();
  if (!trimmed) {
    throw new Error(`${label} path is required.`);
  }

  const expanded = expandHome(trimmed);
  if (path.isAbsolute(expanded)) {
    return expanded;
  }
  if (isProtocolSpecifier(expanded)) {
    throw new Error(`${label} must be a local JavaScript file path, not a URL or protocol specifier.`);
  }
  if (!expanded.startsWith(".")) {
    throw new Error(`${label} must be an explicit local JavaScript path. Package specifiers are not loaded from configuration.`);
  }

  const resolved = path.resolve(CONFIGDIR, expanded);
  if (!isPathInside(resolved, CONFIGDIR)) {
    throw new Error(`${label} relative paths must stay inside the CCR config directory.`);
  }
  return resolved;
}

function assertJavaScriptModulePath(resolved: string, label: string): void {
  const extension = path.extname(resolved).toLowerCase();
  if (![".cjs", ".js", ".mjs"].includes(extension)) {
    throw new Error(`${label} must resolve to a JavaScript module file.`);
  }
}

function isProtocolSpecifier(value: string): boolean {
  return /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(value);
}

function isPathInside(file: string, root: string): boolean {
  const relative = path.relative(root, file);
  return relative === "" || (Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative));
}

function normalizeLoadedPlugin(moduleValue: unknown): LoadedPlugin {
  const record = isRecord(moduleValue) ? moduleValue : {};
  const candidate = record.default ?? record.plugin ?? moduleValue;
  if (typeof candidate === "function") {
    return { setup: candidate as LoadedPlugin["setup"] };
  }
  if (isRecord(candidate)) {
    return candidate as LoadedPlugin;
  }
  throw new Error("Plugin module must export a function, default plugin, or plugin object.");
}

function matchProxyRoute(route: RegisteredProxyRoute, targetUrl: URL): string | undefined {
  if (!matchesHost(route.host, targetUrl.hostname)) {
    return undefined;
  }
  if (!route.paths?.length) {
    return "";
  }
  let matchedPathPrefix: string | undefined;
  for (const pathPrefix of route.paths) {
    const normalizedPathPrefix = normalizeRoutePath(pathPrefix) ?? "/";
    if (!matchesPathPrefix(normalizedPathPrefix, targetUrl.pathname)) {
      continue;
    }
    if (!matchedPathPrefix || normalizedPathPrefix.length > matchedPathPrefix.length) {
      matchedPathPrefix = normalizedPathPrefix;
    }
  }
  return matchedPathPrefix;
}

function buildPluginProxyUpstreamUrl(route: RegisteredProxyRoute, targetUrl: URL, matchedPathPrefix: string): URL {
  const upstreamValue = typeof route.upstream === "function" ? route.upstream() : route.upstream;
  const upstreamUrl = new URL(upstreamValue.toString());
  const basePath = upstreamUrl.pathname === "/" ? "" : upstreamUrl.pathname.replace(/\/+$/, "");
  let forwardedPath = targetUrl.pathname;
  const stripPrefix = resolveStripPathPrefix(route.stripPathPrefix, matchedPathPrefix);

  if (stripPrefix && matchesPathPrefix(stripPrefix, forwardedPath)) {
    forwardedPath = forwardedPath.slice(stripPrefix.length) || "/";
    if (!forwardedPath.startsWith("/")) {
      forwardedPath = `/${forwardedPath}`;
    }
  }
  if (route.rewritePathPrefix !== undefined) {
    const rewritePrefix = normalizeRoutePath(route.rewritePathPrefix) ?? "/";
    const suffix = matchedPathPrefix && matchesPathPrefix(matchedPathPrefix, targetUrl.pathname)
      ? targetUrl.pathname.slice(matchedPathPrefix.length)
      : targetUrl.pathname;
    forwardedPath = joinUrlPaths(rewritePrefix, suffix || "/");
  }

  upstreamUrl.pathname = joinUrlPaths(basePath, forwardedPath);
  upstreamUrl.search = targetUrl.search;
  return upstreamUrl;
}

function resolveStripPathPrefix(value: boolean | string | undefined, matchedPathPrefix: string): string | undefined {
  if (value === true) {
    return matchedPathPrefix || undefined;
  }
  if (typeof value === "string") {
    return normalizeRoutePath(value);
  }
  return undefined;
}

function normalizePluginApp(pluginId: string, app: GatewayPluginAppConfig, index: number): InstalledBrowserApp | undefined {
  const name = app.name?.trim();
  const url = app.url?.trim();
  if (!name || !url) {
    return undefined;
  }

  return {
    ...(app.description?.trim() ? { description: app.description.trim() } : {}),
    ...(app.icon?.trim() ? { icon: app.icon.trim() } : {}),
    id: app.id?.trim() || sanitizeFileSegment(`${name}-${url}`) || `app-${index}`,
    name,
    pluginId,
    url
  };
}

function normalizeMethods(route: GatewayPluginRouteRegistration): string[] | undefined {
  const methods = [...(route.methods ?? []), ...(route.method ? [route.method] : [])]
    .map((method) => method.trim().toUpperCase())
    .filter(Boolean);
  return methods.length ? [...new Set(methods)] : undefined;
}

function normalizeRoutePath(value: string | undefined): string | undefined {
  if (typeof value !== "string" || !value.trim()) {
    return undefined;
  }
  const trimmed = value.trim();
  return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
}

function matchesHost(pattern: string, hostname: string): boolean {
  const normalizedPattern = pattern.toLowerCase();
  const normalizedHost = hostname.toLowerCase();
  if (normalizedPattern === normalizedHost) {
    return true;
  }
  if (normalizedPattern.startsWith("*.")) {
    const suffix = normalizedPattern.slice(1);
    return normalizedHost.endsWith(suffix) && normalizedHost !== suffix.slice(1);
  }
  if (normalizedPattern.startsWith(".")) {
    return normalizedHost.endsWith(normalizedPattern);
  }
  return false;
}

function matchesPathPrefix(prefix: string, requestPath: string): boolean {
  const normalizedPrefix = normalizeRoutePath(prefix) ?? "/";
  const normalizedPath = normalizeRoutePath(requestPath) ?? "/";
  return normalizedPath === normalizedPrefix || normalizedPath.startsWith(`${normalizedPrefix.replace(/\/+$/, "")}/`);
}

function joinUrlPaths(prefix: string, suffix: string): string {
  const normalizedPrefix = prefix === "/" ? "" : prefix.replace(/\/+$/, "");
  const normalizedSuffix = suffix.startsWith("/") ? suffix : `/${suffix}`;
  return `${normalizedPrefix}${normalizedSuffix}` || "/";
}

function createPluginLogger(pluginId: string): PluginLogger {
  const prefix = `[plugin:${pluginId}]`;
  return {
    debug: (...args) => console.debug(prefix, ...args),
    error: (...args) => console.error(prefix, ...args),
    info: (...args) => console.info(prefix, ...args),
    warn: (...args) => console.warn(prefix, ...args)
  };
}

function sendJson(response: ServerResponse, statusCode: number, body: unknown): void {
  response.writeHead(statusCode, { "content-type": "application/json" });
  response.end(`${JSON.stringify(body)}\n`);
}

function readJson(request: IncomingMessage): Promise<unknown> {
  return readBody(request).then((body) => JSON.parse(body.toString("utf8") || "{}") as unknown);
}

function readBody(request: IncomingMessage): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    const chunks: Buffer[] = [];
    request.on("data", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
    request.once("end", () => resolve(Buffer.concat(chunks)));
    request.once("error", reject);
  });
}

function expandHome(value: string): string {
  if (value === "~") {
    return os.homedir();
  }
  if (value.startsWith("~/")) {
    return path.join(os.homedir(), value.slice(2));
  }
  return value;
}

function sanitizeFileSegment(value: string): string {
  return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "plugin";
}

function providerAccountConnectorKey(pluginId: string, connectorId: string): string {
  return `${pluginId.trim()}:${connectorId.trim()}`;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

function formatError(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}
