export type RuntimeEndpointKind = "web" | "api";

type EnvLike = Record<string, string | undefined>;
type ParsedArgs = Record<string, unknown>;

type RuntimeHints = {
  lane: string | null;
  rawLane: string | null;
  namespace: string | null;
  rawNamespace: string | null;
};

type EndpointCandidate = {
  value: string | null;
  source: string;
  sourceName: string;
  explicitOverride?: boolean;
};

export type RuntimeEndpoint = {
  kind: RuntimeEndpointKind;
  baseUrl: string;
  source: string;
  sourceName: string;
  explicitOverride: boolean;
  lane: string | null;
  namespace: string | null;
  urlLane: string | null;
  urlNamespace: string | null;
  warnings: Array<Record<string, unknown>>;
};

export class RuntimeEndpointError extends Error {
  code: string;
  details: Record<string, unknown>;

  constructor(code: string, message: string, details: Record<string, unknown> = {}) {
    super(message);
    this.name = "RuntimeEndpointError";
    this.code = code;
    this.details = details;
  }
}

export function resolveRuntimeEndpoint({ kind, parsed = {}, env = {} }: { kind: RuntimeEndpointKind; parsed?: ParsedArgs; env?: EnvLike }): RuntimeEndpoint {
  const hints = runtimeHints(parsed, env);
  assertNoManualUrlWhenLocked(kind, parsed, env);
  for (const candidate of endpointCandidates(kind, parsed, env, hints)) {
    const baseUrl = normalizeUrl(candidate.value);
    if (!baseUrl) continue;
    return validateEndpoint({ kind, baseUrl, candidate, hints });
  }
  throw new RuntimeEndpointError(
    "runtime_endpoint_required",
    `HWLAB ${kind} endpoint could not be resolved from the current runtime context`,
    {
      kind,
      lane: hints.lane,
      rawLane: hints.rawLane,
      namespace: hints.namespace,
      rawNamespace: hints.rawNamespace,
      required: kind === "web"
        ? ["HWLAB_RUNTIME_WEB_URL", "HWLAB_RUNTIME_NAMESPACE", "HWLAB_CLOUD_WEB_URL"]
        : ["HWLAB_RUNTIME_API_URL", "HWLAB_RUNTIME_NAMESPACE", "HWLAB_CLOUD_API_URL"],
      suggestion: "let WEB/AgentRun assemble HWLAB_RUNTIME_* for the current lane; do not pass endpoint URLs in locked HWLAB runners"
    }
  );
}

export function runtimeEndpointVisibility(endpoint: RuntimeEndpoint | null | undefined) {
  if (!endpoint) return null;
  return pruneUndefined({
    kind: endpoint.kind,
    baseUrl: endpoint.baseUrl,
    source: endpoint.source,
    sourceName: endpoint.sourceName,
    explicitOverride: endpoint.explicitOverride,
    lane: endpoint.lane,
    namespace: endpoint.namespace,
    urlLane: endpoint.urlLane,
    urlNamespace: endpoint.urlNamespace,
    warnings: endpoint.warnings.length > 0 ? endpoint.warnings : undefined
  });
}

export function runtimeEndpointScopeForUrl(value: unknown) {
  const baseUrl = normalizeUrl(value);
  if (!baseUrl) return { baseUrl: null, lane: null, namespace: null };
  const namespace = namespaceForUrl(baseUrl);
  const lane = laneForUrl(baseUrl) ?? laneForNamespace(namespace);
  return { baseUrl, lane, namespace };
}

export function sameRuntimeEndpointScope(left: unknown, right: unknown) {
  const leftScope = runtimeEndpointScopeForUrl(left);
  const rightScope = runtimeEndpointScopeForUrl(right);
  if (!leftScope.baseUrl || !rightScope.baseUrl) return false;
  if (leftScope.baseUrl === rightScope.baseUrl) return true;
  if (leftScope.namespace && rightScope.namespace) return leftScope.namespace === rightScope.namespace;
  return Boolean(leftScope.lane && rightScope.lane && leftScope.lane === rightScope.lane);
}

export function runtimeHints(parsed: ParsedArgs = {}, env: EnvLike = {}): RuntimeHints {
  const rawNamespace = firstText(
    parsed.runtimeNamespace,
    parsed.hwlabNamespace,
    env.HWLAB_RUNTIME_NAMESPACE,
    env.HWLAB_NAMESPACE,
    validHwlabNamespace(env.POD_NAMESPACE),
    validHwlabNamespace(env.KUBERNETES_NAMESPACE)
  );
  const rawLane = firstText(
    parsed.runtimeLane,
    parsed.runtimeProfile,
    parsed.hwlabLane,
    env.HWLAB_RUNTIME_LANE,
    env.HWLAB_RUNTIME_PROFILE,
    env.HWLAB_GITOPS_LANE,
    env.HWLAB_ARTIFACT_LANE,
    env.HWLAB_GITOPS_PROFILE,
    env.HWLAB_ENVIRONMENT,
    env.HWLAB_BOOT_REF
  );
  const laneFromRaw = normalizeLane(rawLane);
  const namespace = normalizeNamespace(rawNamespace) ?? namespaceForLane(laneFromRaw);
  return {
    lane: laneFromRaw ?? laneForNamespace(namespace),
    rawLane: rawLane || null,
    namespace,
    rawNamespace: rawNamespace || null
  };
}

function endpointCandidates(kind: RuntimeEndpointKind, parsed: ParsedArgs, env: EnvLike, hints: RuntimeHints): EndpointCandidate[] {
  const namespaceUrl = hints.namespace ? runtimeUrlForNamespace(kind, hints.namespace, parsed, env) : null;
  if (kind === "web") {
    return withLocalDebugOverride([
      candidate(firstText(env.HWLAB_RUNTIME_WEB_URL, env.HWLAB_CURRENT_WEB_URL), "runtime-env", "HWLAB_RUNTIME_WEB_URL"),
      candidate(namespaceUrl, "runtime-namespace", "HWLAB_RUNTIME_NAMESPACE"),
      candidate(firstText(env.HWLAB_PUBLIC_WEB_URL, env.HWLAB_CLOUD_WEB_PUBLIC_URL), "public-env", "HWLAB_PUBLIC_WEB_URL"),
      candidate(firstText(env.HWLAB_CLIENT_BASE_URL, env.HWLAB_CLOUD_WEB_URL, env.HWLAB_CLI_BASE_URL), "env", "HWLAB_CLIENT_BASE_URL/HWLAB_CLOUD_WEB_URL/HWLAB_CLI_BASE_URL")
    ], candidate(parsed.baseUrl, "explicit-override", "--base-url", true), env);
  }
  return withLocalDebugOverride([
    candidate(firstText(env.HWLAB_RUNTIME_API_URL, env.HWLAB_CURRENT_API_URL), "runtime-env", "HWLAB_RUNTIME_API_URL"),
    candidate(namespaceUrl, "runtime-namespace", "HWLAB_RUNTIME_NAMESPACE"),
    candidate(firstText(env.HWLAB_PUBLIC_API_URL, env.HWLAB_PUBLIC_ENDPOINT), "public-env", "HWLAB_PUBLIC_API_URL/HWLAB_PUBLIC_ENDPOINT"),
    candidate(firstText(env.HWLAB_CLOUD_API_URL, env.HWLAB_CLOUD_API_BASE_URL), "env", "HWLAB_CLOUD_API_URL")
  ], candidate(firstText(parsed.apiBaseUrl, parsed.apiUrl, parsed.cloudApiUrl, parsed.baseUrl), "explicit-override", "--api-base-url", true), env);
}

function withLocalDebugOverride(candidates: EndpointCandidate[], manual: EndpointCandidate, env: EnvLike) {
  if (endpointLocked(env)) return candidates;
  return manual.value ? [manual, ...candidates] : candidates;
}

function assertNoManualUrlWhenLocked(kind: RuntimeEndpointKind, parsed: ParsedArgs, env: EnvLike) {
  if (!endpointLocked(env)) return;
  const manualValue = kind === "web"
    ? firstText(parsed.baseUrl)
    : firstText(parsed.apiBaseUrl, parsed.apiUrl, parsed.cloudApiUrl, parsed.baseUrl);
  if (!manualValue) return;
  throw new RuntimeEndpointError(
    "runtime_endpoint_manual_url_forbidden",
    `HWLAB ${kind} endpoint is assembled by runtime context; manual URL parameters are forbidden in locked runner environments`,
    {
      kind,
      forbidden: kind === "web" ? ["--base-url"] : ["--api-base-url", "--api-url", "--cloud-api-url", "--base-url"],
      source: "HWLAB_RUNTIME_ENDPOINT_LOCKED",
      suggestion: "drop the URL argument and let hwpod-cli/hwlab-cli auto-locate from the assembled runtime env"
    }
  );
}

function endpointLocked(env: EnvLike) {
  return [env.HWLAB_RUNTIME_ENDPOINT_LOCKED, env.HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME].some((value) => ["1", "true", "yes", "locked"].includes(firstText(value).toLowerCase()));
}

function runtimeUrlForNamespace(kind: RuntimeEndpointKind, namespace: string, parsed: ParsedArgs, env: EnvLike) {
  if (runningInsideKubernetes(env)) return serviceUrlForNamespace(kind, namespace, parsed, env);
  return publicUrlForNamespace(kind, namespace);
}

function runningInsideKubernetes(env: EnvLike) {
  return Boolean(firstText(env.KUBERNETES_SERVICE_HOST, env.KUBERNETES_PORT, env.POD_NAMESPACE, env.KUBERNETES_NAMESPACE));
}

function publicUrlForNamespace(kind: RuntimeEndpointKind, namespace: string) {
  const lane = laneForNamespace(namespace);
  if (lane === "v02") return kind === "web" ? "http://74.48.78.17:19666" : "http://74.48.78.17:19667";
  if (lane === "prod") return kind === "web" ? "http://74.48.78.17:18666" : "http://74.48.78.17:18667";
  if (lane === "dev") return kind === "web" ? "http://74.48.78.17:17666" : "http://74.48.78.17:17667";
  return null;
}

function serviceUrlForNamespace(kind: RuntimeEndpointKind, namespace: string, parsed: ParsedArgs, env: EnvLike) {
  if (kind === "web") {
    const serviceName = firstText(parsed.webServiceName, env.HWLAB_CLOUD_WEB_SERVICE_NAME) || "hwlab-cloud-web";
    const port = positiveInteger(firstText(parsed.webServicePort, env.HWLAB_CLOUD_WEB_PORT), 8080);
    return `http://${serviceName}.${namespace}.svc.cluster.local:${port}`;
  }
  const serviceName = firstText(parsed.apiServiceName, env.HWLAB_CODE_AGENT_INTERNAL_API_SERVICE_NAME, env.HWLAB_CLOUD_API_SERVICE_NAME) || "hwlab-cloud-api";
  const port = positiveInteger(firstText(parsed.apiServicePort, env.HWLAB_CODE_AGENT_INTERNAL_API_PORT, env.HWLAB_CLOUD_API_PORT), 6667);
  return `http://${serviceName}.${namespace}.svc.cluster.local:${port}`;
}

function validateEndpoint({ kind, baseUrl, candidate, hints }: { kind: RuntimeEndpointKind; baseUrl: string; candidate: EndpointCandidate; hints: RuntimeHints }): RuntimeEndpoint {
  const urlLane = laneForUrl(baseUrl);
  const urlNamespace = namespaceForUrl(baseUrl);
  const explicitOverride = candidate.explicitOverride === true;
  const warnings: Array<Record<string, unknown>> = [];
  if (!explicitOverride) {
    if (hints.namespace && urlNamespace && urlNamespace !== hints.namespace) {
      throw endpointMismatch(kind, baseUrl, candidate, hints, { urlLane, urlNamespace, field: "namespace" });
    }
    if (hints.lane && urlLane && urlLane !== hints.lane) {
      throw endpointMismatch(kind, baseUrl, candidate, hints, { urlLane, urlNamespace, field: "lane" });
    }
    if (!hints.lane && !hints.namespace && candidate.source === "env" && urlLane === "dev") {
      throw new RuntimeEndpointError(
        "runtime_context_required",
        `HWLAB ${kind} endpoint ${candidate.sourceName} points at the legacy DEV lane but no current runtime lane/namespace is set`,
        {
          kind,
          baseUrl,
          source: candidate.source,
          sourceName: candidate.sourceName,
          urlLane,
          suggestion: "use assembled HWLAB_RUNTIME_* env for the current lane; manual URL is only a local debug override outside locked runners"
        }
      );
    }
  } else if ((hints.lane && urlLane && urlLane !== hints.lane) || (hints.namespace && urlNamespace && urlNamespace !== hints.namespace)) {
    warnings.push({
      code: "runtime_endpoint_explicit_mismatch",
      message: "explicit endpoint override does not match current runtime hints",
      expectedLane: hints.lane,
      expectedNamespace: hints.namespace,
      urlLane,
      urlNamespace
    });
  }
  return pruneUndefined({
    kind,
    baseUrl,
    source: candidate.source,
    sourceName: candidate.sourceName,
    explicitOverride,
    lane: hints.lane,
    namespace: hints.namespace,
    urlLane,
    urlNamespace,
    warnings
  }) as RuntimeEndpoint;
}

function endpointMismatch(kind: RuntimeEndpointKind, baseUrl: string, candidate: EndpointCandidate, hints: RuntimeHints, observed: Record<string, unknown>) {
  return new RuntimeEndpointError(
    "runtime_endpoint_mismatch",
    `HWLAB ${kind} endpoint source ${candidate.sourceName} does not match the current runtime lane/namespace`,
    {
      kind,
      baseUrl,
      source: candidate.source,
      sourceName: candidate.sourceName,
      expectedLane: hints.lane,
      expectedNamespace: hints.namespace,
      rawLane: hints.rawLane,
      rawNamespace: hints.rawNamespace,
      ...observed,
      suggestion: "fix the runtime env injected by WEB/AgentRun; manual URL is only a local debug override outside locked runners"
    }
  );
}

function candidate(value: unknown, source: string, sourceName: string, explicitOverride = false): EndpointCandidate {
  return { value: firstText(value), source, sourceName, explicitOverride };
}

function normalizeUrl(value: unknown) {
  const raw = firstText(value);
  if (!raw) return null;
  return raw.replace(/\/+$/u, "");
}

function laneForUrl(value: string) {
  const text = value.toLowerCase();
  if (/hwlab-v02|:19666(?:\/|$)|:19667(?:\/|$)/u.test(text)) return "v02";
  if (/hwlab-prod|:18666(?:\/|$)|:18667(?:\/|$)/u.test(text)) return "prod";
  if (/hwlab-dev|:17666(?:\/|$)|:17667(?:\/|$)|:16666(?:\/|$)|:16667(?:\/|$)/u.test(text)) return "dev";
  return null;
}

function namespaceForUrl(value: string) {
  const match = value.match(/\.((?:hwlab-[a-z0-9-]+))\.svc\.cluster\.local(?::|\/|$)/iu);
  return normalizeNamespace(match?.[1]);
}

function normalizeNamespace(value: unknown) {
  const namespace = firstText(value);
  if (!namespace || !/^hwlab-[a-z0-9-]+$/u.test(namespace)) return null;
  return namespace;
}

function validHwlabNamespace(value: unknown) {
  return normalizeNamespace(value) ?? null;
}

function normalizeLane(value: unknown) {
  const raw = firstText(value).toLowerCase();
  if (!raw) return null;
  if (["v02", "v0.2", "0.2", "v2", "v0_2", "v0-2"].includes(raw)) return "v02";
  if (["g14", "dev", "development"].includes(raw)) return "dev";
  if (["prod", "production"].includes(raw)) return "prod";
  return null;
}

function namespaceForLane(lane: string | null) {
  if (lane === "v02") return "hwlab-v02";
  if (lane === "prod") return "hwlab-prod";
  if (lane === "dev") return "hwlab-dev";
  return null;
}

function laneForNamespace(namespace: string | null) {
  if (namespace === "hwlab-v02") return "v02";
  if (namespace === "hwlab-prod") return "prod";
  if (namespace === "hwlab-dev") return "dev";
  return null;
}

function positiveInteger(value: unknown, fallback: number) {
  const parsed = Number.parseInt(firstText(value), 10);
  return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
}

function firstText(...values: unknown[]) {
  for (const value of values) {
    const text = String(value ?? "").trim();
    if (text) return text;
  }
  return "";
}

function pruneUndefined<T extends Record<string, unknown>>(value: T): T {
  return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined)) as T;
}
