// Responsibility: resolve semantic YAML-owned HWLAB web-probe origins without CLI URL/IP selection.
import { hwlabRuntimeLaneConfigPath, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";

import type { WebProbeBrowserProxyMode, WebProbeOriginName, WebProbeOriginSelection } from "./entry";
import { resolveKubernetesServiceClusterIp } from "./web-probe-observe-action-config";

export interface ResolvedNodeWebProbeOrigin extends WebProbeOriginSelection {
  readonly url: string;
}

export function webProbeOriginSummary(origin: WebProbeOriginSelection): Record<string, unknown> {
  return {
    name: origin.originName,
    mode: origin.originMode,
    configPath: origin.originConfigPath,
    browserProxyMode: origin.browserProxyMode,
    browserProxyModeSource: origin.browserProxyModeSource,
    source: origin.originSource ?? null,
    valuesRedacted: true,
  };
}

type ServiceClusterIpResolver = (
  spec: HwlabRuntimeLaneSpec,
  namespace: string,
  serviceName: string,
  serviceCache: Map<string, string>,
) => string;

export function parseWebProbeOriginName(value: string): Exclude<WebProbeOriginName, "custom"> {
  if (value === "internal" || value === "public" || value === "native") return value;
  throw new Error(`web-probe --origin must be internal, public, or native, got ${value}`);
}

export function resolveNodeWebProbeOrigin(
  spec: HwlabRuntimeLaneSpec,
  name: Exclude<WebProbeOriginName, "custom">,
  serviceResolver: ServiceClusterIpResolver = resolveKubernetesServiceClusterIp,
): ResolvedNodeWebProbeOrigin {
  if (name === "native") {
    const configPath = `${hwlabRuntimeLaneConfigPath()} node=${spec.nodeId} lane=${spec.lane}#nativeDevelopment.workbench.publicExposure.publicBaseUrl`;
    const publicBaseUrl = spec.nativeDevelopment?.workbench.publicExposure.publicBaseUrl;
    if (publicBaseUrl === undefined) throw new Error(`${configPath} must declare the L1 native Workbench HTTPS origin`);
    return {
      originName: name,
      originMode: "native-public",
      originConfigPath: configPath,
      url: normalizeHttpOrigin(publicBaseUrl, configPath),
      ...resolvedOriginBrowserProxyMode(spec, undefined),
    };
  }
  const origin = spec.webProbe?.origins?.[name];
  const configPath = `${hwlabRuntimeLaneConfigPath()} node=${spec.nodeId} lane=${spec.lane}#webProbe.origins.${name}`;
  if (origin === undefined) throw new Error(`${configPath} must declare the semantic ${name} web-probe origin`);
  const browserProxy = resolvedOriginBrowserProxyMode(spec, origin.browserProxyMode);
  if (name === "public") {
    if (origin.mode !== "public") throw new Error(`${configPath}.mode must be public`);
    return {
      originName: name,
      originMode: origin.mode,
      originConfigPath: configPath,
      url: normalizeHttpOrigin(origin.baseUrl ?? spec.publicWebUrl, configPath),
      ...browserProxy,
    };
  }
  if (origin.mode !== "k8s-service-cluster-ip") throw new Error(`${configPath}.mode must be k8s-service-cluster-ip`);
  const namespace = materializeOriginTemplate(origin.namespace, spec);
  const serviceName = materializeOriginTemplate(origin.serviceName, spec);
  const clusterIp = serviceResolver(spec, namespace, serviceName, new Map());
  return {
    originName: name,
    originMode: origin.mode,
    originConfigPath: configPath,
    url: `${origin.scheme}://${clusterIp}:${origin.port}`,
    originSource: {
      namespace,
      serviceName,
      clusterIp,
      port: origin.port,
      scheme: origin.scheme,
    },
    ...browserProxy,
  };
}

function materializeOriginTemplate(value: string, spec: HwlabRuntimeLaneSpec): string {
  return value
    .replace(/\$\{lane\}/gu, spec.lane)
    .replace(/\$\{NODE\}/gu, spec.nodeId)
    .replace(/\$\{nodeLower\}/gu, spec.nodeId.toLowerCase());
}

export function resolveNodeWebProbeCliOrigin(
  spec: HwlabRuntimeLaneSpec,
  requestedOrigin: string | undefined,
  customUrl: string | undefined,
  requestedBrowserProxyMode: WebProbeBrowserProxyMode | undefined,
): ResolvedNodeWebProbeOrigin {
  if (requestedOrigin !== undefined && customUrl !== undefined) {
    throw new Error("web-probe accepts --origin or --url, not both; use --origin internal|public|native for HWLAB runtime selection");
  }
  const selected = customUrl === undefined
    ? resolveNodeWebProbeOrigin(spec, parseWebProbeOriginName(requestedOrigin ?? spec.webProbe?.defaultOrigin ?? "public"))
    : resolveCustomWebProbeOrigin(spec, customUrl);
  if (requestedBrowserProxyMode === undefined) return selected;
  return { ...selected, browserProxyMode: requestedBrowserProxyMode, browserProxyModeSource: "cli" };
}

function resolveCustomWebProbeOrigin(spec: HwlabRuntimeLaneSpec, value: string): ResolvedNodeWebProbeOrigin {
  const browserProxyMode = spec.webProbe?.browserProxyMode ?? "auto";
  return {
    originName: "custom",
    originMode: "custom-url",
    originConfigPath: null,
    url: normalizeCustomHttpUrl(value, "web-probe --url"),
    browserProxyMode,
    browserProxyModeSource: spec.webProbe?.browserProxyMode === undefined ? "default" : "yaml-web-probe",
  };
}

function resolvedOriginBrowserProxyMode(
  spec: HwlabRuntimeLaneSpec,
  originMode: WebProbeBrowserProxyMode | undefined,
): Pick<ResolvedNodeWebProbeOrigin, "browserProxyMode" | "browserProxyModeSource"> {
  if (originMode !== undefined) return { browserProxyMode: originMode, browserProxyModeSource: "yaml-origin" };
  if (spec.webProbe?.browserProxyMode !== undefined) {
    return { browserProxyMode: spec.webProbe.browserProxyMode, browserProxyModeSource: "yaml-web-probe" };
  }
  return { browserProxyMode: "auto", browserProxyModeSource: "default" };
}

function normalizeHttpOrigin(value: string, source: string): string {
  let parsed: URL;
  try {
    parsed = new URL(value);
  } catch (error) {
    throw new Error(`${source} must be an absolute HTTP(S) origin: ${error instanceof Error ? error.message : String(error)}`);
  }
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`${source} must use http or https`);
  if (parsed.username || parsed.password || parsed.search || parsed.hash) throw new Error(`${source} must not contain credentials, query, or fragment`);
  return parsed.toString().replace(/\/$/u, "");
}

function normalizeCustomHttpUrl(value: string, source: string): string {
  let parsed: URL;
  try {
    parsed = new URL(value);
  } catch (error) {
    throw new Error(`${source} must be an absolute HTTP(S) URL: ${error instanceof Error ? error.message : String(error)}`);
  }
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`${source} must use http or https`);
  if (parsed.username || parsed.password || parsed.hash) throw new Error(`${source} must not contain credentials or fragment`);
  if (parsed.pathname === "/" && parsed.search === "") return parsed.toString().replace(/\/$/u, "");
  return parsed.toString();
}
