import { createHash } from "node:crypto";

import { hwlabRuntimeLaneSpecForNode, isHwlabRuntimeLane } from "./hwlab-node-lanes";
import type { NodeWebProbeScriptOptions } from "./hwlab-node/entry";
import { runManagedNodeWebProbeScript } from "./hwlab-node/web-observe-scripts";
import {
  readTemporalConfig,
  resolveTemporalTarget,
  type TemporalWebProbeSmokeProfile,
} from "./platform-infra-temporal";
import { readDeclaredSecretValues } from "./secrets";

export interface TemporalProductSmokeOptions {
  readonly target: string;
  readonly profile: string | null;
}

export function resolveTemporalProductSmoke(options: TemporalProductSmokeOptions): {
  readonly targetId: string;
  readonly profileName: string;
  readonly profile: TemporalWebProbeSmokeProfile;
  readonly configRef: string;
  readonly commandLabel: string;
  readonly scriptOptions: NodeWebProbeScriptOptions;
} {
  const temporal = readTemporalConfig();
  const target = resolveTemporalTarget(temporal, options.target);
  const webProbe = temporal.webProbe;
  if (!webProbe.enabled) throw new Error("config/platform-infra/temporal.yaml.webProbe.enabled=false，禁止执行 Temporal Web smoke");
  const profileName = options.profile ?? webProbe.defaultSmokeProfile;
  const profile = webProbe.smokeProfiles[profileName];
  if (profile === undefined) throw new Error(`未知 --profile ${profileName}；可用项：${Object.keys(webProbe.smokeProfiles).sort().join(", ")}`);
  if (!isHwlabRuntimeLane(webProbe.runner.lane)) throw new Error(`Temporal Web smoke runner lane 未在 HWLAB YAML 中声明：${webProbe.runner.lane}`);
  hwlabRuntimeLaneSpecForNode(webProbe.runner.lane, webProbe.runner.node);
  const configRef = `config/platform-infra/temporal.yaml#webProbe.smokeProfiles.${profileName}`;
  const commandLabel = `web-probe product-smoke --product temporal --target ${target.id} --profile ${profileName}`;
  const scriptText = temporalAdminReadonlySmokeScript(profileName, profile, configRef, temporal.publicExposure.healthPath);
  const scriptOptions: NodeWebProbeScriptOptions = {
    action: "script",
    node: webProbe.runner.node,
    lane: webProbe.runner.lane,
    url: temporal.publicExposure.publicBaseUrl,
    originName: "public",
    originMode: "public",
    originConfigPath: "config/platform-infra/temporal.yaml#publicExposure.publicBaseUrl",
    browserProxyMode: webProbe.origin.browserProxyMode,
    browserProxyModeSource: "yaml-origin",
    timeoutMs: profile.navigationTimeoutMs,
    viewport: `${profile.viewport.width}x${profile.viewport.height}`,
    commandTimeoutSeconds: profile.commandTimeoutSeconds,
    scriptText,
    commandLabel,
    suppressAdHocWarning: true,
    generatedHints: [
      "该命令由 Temporal owning YAML 驱动，并复用统一管理员密码、远端浏览器、内存门禁和证据恢复链。",
      "smoke 只执行 Basic Auth 后的页面导航、DOM/控制台/网络读取和截图，不修改 Temporal 状态。",
    ],
    generatedPreferredCommands: { rerun: commandLabel },
    scriptSource: {
      kind: "generated",
      path: `builtin:product-smoke/temporal/${profileName}`,
      byteCount: Buffer.byteLength(scriptText),
      sha256: `sha256:${createHash("sha256").update(scriptText).digest("hex")}`,
    },
  };
  return { targetId: target.id, profileName, profile, configRef, commandLabel, scriptOptions };
}

export function runWebProbeTemporalSmoke(options: TemporalProductSmokeOptions): Record<string, unknown> {
  const temporal = readTemporalConfig();
  const resolved = resolveTemporalProductSmoke(options);
  const webProbe = temporal.webProbe;
  const credentials = webProbe.authentication.credentials;
  const declared = readDeclaredSecretValues({
    configPath: credentials.configRef,
    targetId: credentials.targetId,
    secretName: credentials.secretName,
    targetKeys: [credentials.passwordTargetKey],
  });
  const password = declared.values[credentials.passwordTargetKey];
  if (password === undefined) throw new Error("Temporal Web smoke 密码声明不完整");
  const laneSpec = hwlabRuntimeLaneSpecForNode(webProbe.runner.lane, webProbe.runner.node);
  const result = runManagedNodeWebProbeScript(
    resolved.scriptOptions,
    laneSpec,
    { username: temporal.runtime.ui.auth.username, password },
    { product: "temporal", target: resolved.targetId, source: declared.summary, valuesPrinted: false },
    {
      mode: "basic-auth",
      loginPath: webProbe.authentication.path,
      authenticatedSelector: webProbe.authentication.authenticatedSelector,
    },
  );
  return {
    ...result,
    productSmoke: {
      product: "temporal",
      target: resolved.targetId,
      profile: resolved.profileName,
      configRef: resolved.configRef,
      mutation: false,
      valuesPrinted: false,
    },
    valuesPrinted: false,
  };
}

export function temporalAdminReadonlySmokeScript(
  profileName: string,
  profile: TemporalWebProbeSmokeProfile,
  configRef: string,
  healthPath: string,
): string {
  const config = { profileName, configRef, healthPath, ...profile };
  return String.raw`const config = ${JSON.stringify(config)};

export default async function temporalAdminReadonlySmoke({ page, goto, wait, screenshot, jsonArtifact, recordStep }) {
  const failures = [];
  const network = [];
  const consoleErrors = [];
  let networkCount = 0;
  let consoleCount = 0;
  const addFailure = (value) => {
    if (failures.length < config.outputLimits.failures) failures.push(String(value).replace(/\s+/gu, " ").slice(0, 300));
  };
  page.on("response", (response) => {
    const parsed = safeUrl(response.url());
    if (parsed === null || parsed.origin !== new URL(page.url()).origin) return;
    networkCount += 1;
    if (network.length < config.outputLimits.network) network.push({ method: response.request().method(), path: parsed.pathname, status: response.status() });
    if (response.status() >= 500) addFailure("HTTP " + response.status() + " " + parsed.pathname);
  });
  page.on("requestfailed", (request) => {
    const parsed = safeUrl(request.url());
    if (parsed === null) return;
    networkCount += 1;
    const failure = String(request.failure()?.errorText || "request failed").slice(0, 160);
    const browserAborted = failure.includes("net::ERR_ABORTED");
    if (network.length < config.outputLimits.network) network.push({ method: request.method(), path: parsed.pathname, status: null, failure, browserAborted });
    if (!browserAborted) addFailure(parsed.pathname + "：" + failure);
  });
  page.on("console", (message) => {
    if (message.type() !== "error") return;
    consoleCount += 1;
    const text = message.text().replace(/\s+/gu, " ").slice(0, 240);
    if (consoleErrors.length < config.outputLimits.console) consoleErrors.push({ type: "error", text });
    addFailure("console.error：" + text);
  });
  page.on("pageerror", (error) => {
    consoleCount += 1;
    const text = String(error?.message || error).replace(/\s+/gu, " ").slice(0, 240);
    if (consoleErrors.length < config.outputLimits.console) consoleErrors.push({ type: "pageerror", text });
    addFailure("pageerror：" + text);
  });

  await goto(config.path, { selectors: [config.readySelector], readinessTimeoutMs: config.navigationTimeoutMs, attempts: 3 });
  if (config.settleMs > 0) await wait(config.settleMs);
  const healthResponse = await page.request.get(new URL(config.healthPath, page.url()).toString(), { timeout: config.navigationTimeoutMs });
  const health = { path: config.healthPath, status: healthResponse.status(), ok: healthResponse.ok() };
  if (!health.ok || health.status !== 200) addFailure("健康检查未返回 200");
  const dom = await page.evaluate(({ readySelector }) => {
    const ready = document.querySelector(readySelector);
    const rect = ready?.getBoundingClientRect();
    const bodyText = document.body?.innerText || "";
    return {
      title: document.title.slice(0, 160),
      bodyTextBytes: new TextEncoder().encode(bodyText).byteLength,
      readySelectorPresent: ready !== null,
      readySelectorVisible: Boolean(rect && rect.width > 0 && rect.height > 0),
      appElementCount: document.querySelectorAll("main, nav, [role=main], #root, #app").length,
      viewport: { width: window.innerWidth, height: window.innerHeight },
      document: { width: document.documentElement.scrollWidth, height: document.documentElement.scrollHeight },
    };
  }, { readySelector: config.readySelector });
  if (dom.bodyTextBytes < config.minimumBodyTextBytes) addFailure("页面正文小于 YAML 声明的最小字节数");
  if (!dom.readySelectorPresent || !dom.readySelectorVisible) addFailure("YAML 声明的 readySelector 不可见");
  const shot = await screenshot(config.screenshotName);
  const summary = {
    ok: failures.length === 0,
    finalPath: new URL(page.url()).pathname,
    health,
    dom,
    networkCount,
    network,
    consoleCount,
    consoleErrors,
    failures,
    screenshot: shot,
    mutation: false,
    valuesRedacted: true,
  };
  const artifact = await jsonArtifact("temporal-admin-readonly.json", summary);
  recordStep("验证 Temporal Basic Auth 管理页面", { ...summary, artifact });
  return { ...summary, artifact };
}

function safeUrl(value) {
  try { return new URL(value); } catch { return null; }
}`;
}
