import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { basename, dirname, relative, resolve } from "node:path";

import { resolveCliChildJsonCommandResult } from "./cli-child-json-recovery";
import { rootPath } from "./config";
import { hwlabRuntimeLaneSpecForNode, isHwlabRuntimeLane } from "./hwlab-node-lanes";
import { isSafeWebProbeScriptArtifactPath, runTransWorkspaceStdinScript } from "./hwlab-node/public-exposure";
import { compactCommandResultRedacted, shellQuote } from "./hwlab-node/utils";
import { CliInputError } from "./output";

interface WebProbeArtifactInspectOptions {
  readonly node: string;
  readonly lane: string;
  readonly path: string;
  readonly sha256: string;
}

export function parseWebProbeArtifactInspectOptions(args: string[]): WebProbeArtifactInspectOptions {
  if (args[0] !== "inspect") throw artifactInputError("web-probe artifact 需要 inspect action");
  const values = new Map<string, string>();
  const known = new Set(["--node", "--lane", "--path", "--sha256"]);
  for (let index = 1; index < args.length; index += 1) {
    const item = args[index];
    const equals = item.indexOf("=");
    const key = equals > 0 ? item.slice(0, equals) : item;
    if (!known.has(key)) throw artifactInputError(`web-probe artifact inspect 不支持参数 ${item}`, item);
    const value = equals > 0 ? item.slice(equals + 1) : args[++index];
    if (value === undefined || value.startsWith("--")) throw artifactInputError(`${key} 需要一个值`, key);
    if (values.has(key)) throw artifactInputError(`${key} 不能重复`, key);
    values.set(key, value);
  }
  const node = required(values, "--node");
  const lane = required(values, "--lane");
  const path = required(values, "--path");
  const sha256 = required(values, "--sha256");
  if (!/^[A-Za-z0-9_.-]+$/u.test(node)) throw artifactInputError("--node 只能包含字母、数字、点、下划线和连字符", "--node");
  if (!isHwlabRuntimeLane(lane)) throw artifactInputError(`未知 --lane ${lane}`, "--lane");
  if (!isSafeWebProbeScriptArtifactPath(path) && !isSafeDownloadedWebProbeArtifactPath(path)) {
    throw artifactInputError("--path 不是受控 web-probe script artifact 路径", "--path");
  }
  if (!/^sha256:[a-f0-9]{64}$/u.test(sha256)) throw artifactInputError("--sha256 必须是 sha256:<64位小写十六进制>", "--sha256");
  return { node, lane, path, sha256 };
}

export function runWebProbeArtifactInspect(args: string[]): Record<string, unknown> {
  const options = parseWebProbeArtifactInspectOptions(args);
  if (isSafeDownloadedWebProbeArtifactPath(options.path)) return inspectDownloadedWebProbeArtifact(options);
  const spec = hwlabRuntimeLaneSpecForNode(options.lane, options.node);
  const result = runTransWorkspaceStdinScript(options.node, spec.workspace, artifactInspectScript(options), 30);
  const resolution = resolveCliChildJsonCommandResult({
    result,
    requestedStdoutType: "bounded web-probe artifact inspection JSON",
    acceptParsed: (value) => typeof value.ok === "boolean" && typeof value.sha256 === "string" && typeof value.path === "string",
  });
  const artifact = resolution.parsed;
  return {
    ok: result.exitCode === 0 && artifact?.ok === true,
    status: result.exitCode === 0 && artifact?.ok === true ? "inspected" : "blocked",
    command: "web-probe artifact inspect",
    node: options.node,
    lane: options.lane,
    artifact,
    stdoutRecovery: resolution.diagnostics,
    result: compactCommandResultRedacted(result, []),
    valuesRedacted: true,
  };
}

function inspectDownloadedWebProbeArtifact(options: WebProbeArtifactInspectOptions): Record<string, unknown> {
  const bytes = readFileSync(rootPath(options.path));
  const sha256 = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
  const verified = sha256 === options.sha256;
  const report = options.path.endsWith(".json") ? JSON.parse(bytes.toString("utf8")) as unknown : null;
  return {
    ok: verified,
    status: verified ? "inspected" : "blocked",
    command: "web-probe artifact inspect",
    node: options.node,
    lane: options.lane,
    artifact: {
      ok: verified,
      kind: report === null ? "screenshot" : "json",
      path: options.path,
      byteCount: bytes.length,
      sha256,
      expectedSha256: options.sha256,
      verified,
      evidence: report === null ? null : compactDownloadedReport(report),
      valuesRedacted: true,
    },
    valuesRedacted: true,
  };
}

function compactDownloadedReport(value: unknown): Record<string, unknown> {
  const report = objectValue(value);
  const summary = objectValue(report.summary);
  return {
    ok: report.ok === true,
    status: typeof report.status === "string" ? report.status : null,
    failureKind: typeof report.failureKind === "string" ? report.failureKind : null,
    auth: compactArtifactValue(report.auth),
    summary: {
      ok: summary.ok === true,
      status: summary.status ?? null,
      degradedReason: summary.degradedReason ?? null,
      failureKind: summary.failureKind ?? null,
      failedCondition: compactArtifactValue(summary.failedCondition),
      nextAction: compactArtifactValue(summary.nextAction),
      baseUrl: summary.baseUrl ?? null,
      finalUrl: summary.finalUrl ?? null,
      reportPath: summary.reportPath ?? null,
      reportSha256: summary.reportSha256 ?? null,
      lastStep: compactArtifactValue(summary.lastStep),
      issueEvidence: compactIssueEvidence(summary.issueEvidence),
    },
    issueEvidence: compactIssueEvidence(report.issueEvidence),
    failureEvidence: compactArtifactValue(report.failureEvidence),
    lastStep: compactArtifactValue(report.lastStep ?? summary.lastStep),
    steps: compactArtifactValue(report.steps),
    artifacts: compactArtifactValue(report.artifacts),
    valuesRedacted: true,
  };
}

function compactIssueEvidence(value: unknown): unknown {
  const issue = objectValue(value);
  const result = objectValue(issue.result);
  if (!("pageErrors" in result || "consoleErrors" in result || "requestFailures" in result || "responseErrors" in result)) {
    return compactArtifactValue(value);
  }
  return {
    ok: issue.ok ?? result.ok ?? null,
    status: issue.status ?? result.status ?? null,
    profileName: result.profileName ?? null,
    configRef: result.configRef ?? null,
    failureCount: result.failureCount ?? null,
    failures: compactArtifactValue(result.failures),
    pageErrorCount: result.pageErrorCount ?? null,
    pageErrors: compactArtifactValue(result.pageErrors),
    consoleErrorCount: result.consoleErrorCount ?? null,
    consoleErrors: compactArtifactValue(result.consoleErrors),
    requestFailureCount: result.requestFailureCount ?? null,
    requestFailures: compactArtifactValue(result.requestFailures),
    responseErrorCount: result.responseErrorCount ?? null,
    responseErrors: compactArtifactValue(result.responseErrors),
    valuesRedacted: true,
  };
}

function compactArtifactValue(value: unknown, depth = 0): unknown {
  if (value === null || value === undefined) return value ?? null;
  if (typeof value === "string") return value.replace(/\s+/gu, " ").trim().slice(0, 300);
  if (typeof value === "number" || typeof value === "boolean") return value;
  if (depth >= 5) return "[max-depth]";
  if (Array.isArray(value)) return value.slice(0, 4).map((item) => compactArtifactValue(item, depth + 1));
  if (typeof value === "object") {
    const output: Record<string, unknown> = {};
    for (const [key, nested] of Object.entries(value as Record<string, unknown>).slice(0, 24)) {
      output[key] = compactArtifactValue(nested, depth + 1);
    }
    return output;
  }
  return String(value).slice(0, 300);
}

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

function isSafeDownloadedWebProbeArtifactPath(path: string): boolean {
  const root = resolve(rootPath(".state/web-probe-script-artifacts"));
  const candidate = resolve(rootPath(path));
  const relativePath = relative(root, candidate);
  return dirname(relativePath) === "."
    && basename(relativePath) === relativePath
    && /^[A-Za-z0-9_.-]+[.](?:png|json)$/u.test(relativePath);
}

export function artifactInspectScript(options: WebProbeArtifactInspectOptions): string {
  return [
    "set -eu",
    `node - ${shellQuote(options.path)} ${shellQuote(options.sha256)} <<'NODE'`,
    "const fs = require('fs');",
    "const { createHash } = require('crypto');",
    "const path = process.argv[2];",
    "const expectedSha256 = process.argv[3];",
    "const bytes = fs.readFileSync(path);",
    "const sha256 = 'sha256:' + createHash('sha256').update(bytes).digest('hex');",
    "const compact = (value, depth = 0) => {",
    "  if (value === null || value === undefined) return value ?? null;",
    "  if (typeof value === 'string') return value.replace(/\\s+/gu, ' ').trim().slice(0, 600);",
    "  if (typeof value === 'number' || typeof value === 'boolean') return value;",
    "  if (depth >= 6) return '[max-depth]';",
    "  if (Array.isArray(value)) return value.slice(-8).map((item) => compact(item, depth + 1));",
    "  if (typeof value === 'object') {",
    "    const out = {};",
    "    for (const [key, nested] of Object.entries(value).slice(0, 32)) out[key] = compact(nested, depth + 1);",
    "    return out;",
    "  }",
    "  return String(value).slice(0, 600);",
    "};",
    "let evidence = null;",
    "if (path.endsWith('.json')) {",
    "  const report = JSON.parse(bytes.toString('utf8'));",
    "  evidence = {",
    "    ok: report.ok === true,",
    "    status: typeof report.status === 'string' ? report.status : null,",
    "    failureKind: typeof report.failureKind === 'string' ? report.failureKind : null,",
    "    auth: compact(report.auth),",
    "    summary: compact(report.summary),",
    "    issueEvidence: compact(report.issueEvidence),",
    "    failureEvidence: compact(report.failureEvidence),",
    "    lastStep: compact(report.lastStep ?? report.summary?.lastStep),",
    "    steps: compact(report.steps),",
    "    artifacts: compact(report.artifacts),",
    "    valuesRedacted: true,",
    "  };",
    "}",
    "const ok = sha256 === expectedSha256;",
    "console.log(JSON.stringify({ ok, kind: path.endsWith('.json') ? 'json' : 'screenshot', path, byteCount: bytes.length, sha256, expectedSha256, verified: ok, evidence, valuesRedacted: true }));",
    "if (!ok) process.exitCode = 3;",
    "NODE",
  ].join("\n");
}

function required(values: Map<string, string>, key: string): string {
  const value = values.get(key);
  if (value === undefined || value.length === 0) throw artifactInputError(`${key} 是必填参数`, key);
  return value;
}

function artifactInputError(message: string, argument?: string): CliInputError {
  return new CliInputError(message, {
    code: "web-probe-artifact-input-invalid",
    reason: message,
    mutation: false,
    argument,
    usage: "bun scripts/cli.ts web-probe artifact inspect --node NODE --lane LANE --path PATH --sha256 sha256:<digest>",
    hint: "仅使用 product-smoke 输出的受控 artifact 路径和对应 SHA。",
  });
}
