import type { UniDeskConfig } from "./config";
import { rootPath } from "./config";
import { startJob } from "./jobs";
import {
  arrayField,
  booleanField,
  capture,
  compactCapture,
  integerField,
  recordField,
  parseJsonOutput,
  parseOpsApplyOptions,
  parseOpsCommonOptions,
  readYamlRecord,
  shQuote,
  stringArrayField,
  stringField,
} from "./platform-infra-ops-library";
import { fingerprintSecretValues, readEnvSourceFile, requiredEnvValue } from "./secrets";

const configPath = rootPath("config", "platform-infra", "temporal.yaml");
const configLabel = "config/platform-infra/temporal.yaml";
const secretRoot = "/root/.unidesk/.state/secrets";
const fieldManager = "unidesk-platform-temporal";

export interface TemporalTarget {
  id: string;
  route: string;
  namespace: string;
  createNamespace: boolean;
  enabled: boolean;
}

export interface TemporalWebProbeSmokeProfile {
  path: string;
  readySelector: string;
  minimumBodyTextBytes: number;
  screenshotName: string;
  viewport: { width: number; height: number };
  navigationTimeoutMs: number;
  settleMs: number;
  commandTimeoutSeconds: number;
  outputLimits: { failures: number; network: number; console: number };
}

export interface TemporalWebProbeSpec {
  enabled: boolean;
  productId: "temporal";
  runner: { node: string; lane: string };
  origin: { browserProxyMode: "auto" | "direct" };
  authentication: {
    mode: "basic-auth";
    path: string;
    authenticatedSelector: string;
    credentials: { configRef: string; targetId: string; secretName: string; passwordTargetKey: string };
  };
  defaultSmokeProfile: string;
  smokeProfiles: Record<string, TemporalWebProbeSmokeProfile>;
}

export interface TemporalConfig {
  defaults: { targetId: string };
  delivery: {
    enabled: boolean;
    repositoryRef: string;
    sourceBranch: string;
    sourceSnapshotPrefix: string;
    pipeline: { name: string; runPrefix: string; namespace: string; serviceAccount: string; timeout: string };
    gitops: { readUrl: string; writeUrl: string; branch: string; path: string; manifestPath: string; author: { name: string; email: string } };
    argo: { namespace: string; application: string; project: string; destinationNamespace: string; automated: boolean };
    materializationPaths: string[];
  };
  images: { server: string; ui: string; authProxy: string; pullPolicy: string };
  targets: TemporalTarget[];
  database: {
    configRef: string;
    sourceRef: string;
    sourceKeys: { user: string; password: string; database: string; visibilityDatabase: string };
    host: string;
    port: number;
    sslMode: string;
    disableHostVerification: boolean;
    skipCreate: boolean;
    secretName: string;
  };
  runtime: {
    server: {
      deploymentName: string;
      serviceName: string;
      replicas: number;
      frontendPort: number;
      dynamicConfigFilePath: string;
      enableServiceLinks: boolean;
      shareProcessNamespace: boolean;
    };
    ui: {
      deploymentName: string;
      serviceName: string;
      replicas: number;
      port: number;
      enableServiceLinks: boolean;
      auth: { username: string; proxyPort: number; secretName: string; passwordKey: string };
    };
    logicalNamespaces: Array<{ name: string; retention: string }>;
    rolloutTimeoutSeconds: number;
  };
  publicExposure: {
    enabled: boolean;
    publicBaseUrl: string;
    hostname: string;
    upstream: string;
    healthPath: string;
    expectedA: string;
  };
  webProbe: TemporalWebProbeSpec;
}

interface DatabaseMaterial {
  user: string;
  password: string;
  database: string;
  visibilityDatabase: string;
  fingerprint: string;
}

export function temporalHelp(): Record<string, unknown> {
  return {
    command: "platform-infra temporal plan|apply|status|validate",
    usage: [
      "bun scripts/cli.ts platform-infra temporal plan --target NC01",
      "bun scripts/cli.ts platform-infra temporal apply --target NC01 --dry-run",
      "bun scripts/cli.ts platform-infra temporal apply --target NC01 --confirm",
      "bun scripts/cli.ts platform-infra temporal status --target NC01",
      "bun scripts/cli.ts platform-infra temporal validate --target NC01",
    ],
    configTruth: configLabel,
    databaseTruth: "config/platform-db/postgres-nc01.yaml",
    publicUrl: "https://temporal.hwpod.com",
    secretPolicy: "YAML sourceRef values are rendered into a Kubernetes Secret and never printed.",
  };
}

export async function runTemporalCommand(config: UniDeskConfig, args: string[]): Promise<Record<string, unknown>> {
  const [action = "plan"] = args;
  if (action === "help" || action === "--help" || action === "-h") return temporalHelp();
  if (action === "plan") return plan(parseOpsCommonOptions(args.slice(1)));
  if (action === "apply") return await apply(config, parseOpsApplyOptions(args.slice(1)));
  if (action === "status") return await observe(config, "status", parseOpsCommonOptions(args.slice(1)));
  if (action === "validate") return await observe(config, "validate", parseOpsCommonOptions(args.slice(1)));
  return { ok: false, error: "unsupported-platform-infra-temporal-command", args, help: temporalHelp() };
}

function plan(options: ReturnType<typeof parseOpsCommonOptions>): Record<string, unknown> {
  const temporal = readTemporalConfig();
  const target = resolveTemporalTarget(temporal, options.targetId);
  const manifest = renderManifest(temporal, target, null);
  const checks = policyChecks(temporal, target, manifest);
  return {
    ok: checks.every((check) => check.ok),
    action: "platform-infra-temporal-plan",
    mutation: false,
    config: summary(temporal, target),
    policy: checks,
    rendered: { objectCount: manifest.split("\n---\n").length, secretValuesPrepared: false },
    next: {
      databasePlan: `bun scripts/cli.ts platform-db postgres plan --config ${temporal.database.configRef}`,
      databaseApply: `bun scripts/cli.ts platform-db postgres apply --config ${temporal.database.configRef} --confirm`,
      dryRun: `bun scripts/cli.ts platform-infra temporal apply --target ${target.id} --dry-run`,
      apply: `bun scripts/cli.ts platform-infra temporal apply --target ${target.id} --confirm`,
      publicEdge: `bun scripts/cli.ts platform-infra public-edge plan --target ${target.id}`,
      webAdminSecret: "bun scripts/cli.ts secrets sync --config config/secrets-distribution.yaml --scope temporal --target temporal-nc01 --confirm",
    },
  };
}

async function apply(config: UniDeskConfig, options: ReturnType<typeof parseOpsApplyOptions>): Promise<Record<string, unknown>> {
  const temporal = readTemporalConfig();
  const target = resolveTemporalTarget(temporal, options.targetId);
  const checks = policyChecks(temporal, target, renderManifest(temporal, target, null));
  if (!checks.every((check) => check.ok)) return { ok: false, action: "platform-infra-temporal-apply", mode: "policy-blocked", policy: checks };
  if (options.confirm && !options.wait) {
    const job = startJob(
      `platform_infra_temporal_apply_${target.id.toLowerCase()}`,
      ["bun", "scripts/cli.ts", "platform-infra", "temporal", "apply", "--target", target.id, "--confirm", "--wait"],
      `Apply ${target.id} Temporal manifests and initialize YAML-declared logical namespaces`,
    );
    return {
      ok: true,
      action: "platform-infra-temporal-apply",
      mode: "async-job",
      mutation: true,
      job,
      statusCommand: `bun scripts/cli.ts job status ${job.id} --tail-bytes 12000`,
    };
  }
  const material = options.confirm ? readDatabaseMaterial(temporal) : null;
  const manifest = renderManifest(temporal, target, material);
  const script = options.dryRun ? dryRunScript(target, manifest) : applyScript(temporal, target, manifest);
  const result = await capture(config, target.route, ["sh"], script, { runtimeTimeoutMs: (temporal.runtime.rolloutTimeoutSeconds + 30) * 1000 });
  const parsed = parseJsonOutput(result.stdout);
  return {
    ok: result.exitCode === 0 && parsed?.ok === true,
    action: "platform-infra-temporal-apply",
    mode: options.dryRun ? "dry-run" : "confirmed",
    mutation: !options.dryRun && result.exitCode === 0,
    target: targetSummary(target),
    database: {
      configRef: temporal.database.configRef,
      sourceRef: temporal.database.sourceRef,
      secretName: temporal.database.secretName,
      fingerprint: material?.fingerprint ?? null,
      valuesPrinted: false,
    },
    remote: parsed ?? compactCapture(result, { full: true }),
    next: {
      status: `bun scripts/cli.ts platform-infra temporal status --target ${target.id}`,
      validate: `bun scripts/cli.ts platform-infra temporal validate --target ${target.id}`,
      publicEdge: `bun scripts/cli.ts platform-infra public-edge status --target ${target.id}`,
    },
  };
}

async function observe(
  config: UniDeskConfig,
  action: "status" | "validate",
  options: ReturnType<typeof parseOpsCommonOptions>,
): Promise<Record<string, unknown>> {
  const temporal = readTemporalConfig();
  const target = resolveTemporalTarget(temporal, options.targetId);
  const result = await capture(config, target.route, ["sh"], statusScript(temporal, target, action === "validate"));
  const parsed = parseJsonOutput(result.stdout);
  return {
    ok: result.exitCode === 0 && parsed?.ok === true,
    action: `platform-infra-temporal-${action}`,
    mutation: false,
    target: targetSummary(target),
    summary: parsed,
    remote: compactCapture(result, { full: options.full || result.exitCode !== 0 }),
    ...(options.raw ? { raw: result } : {}),
  };
}

export function renderTemporalGitOpsManifest(temporal: TemporalConfig, target: TemporalTarget, sourceCommit: string): string {
  if (!/^[0-9a-f]{40}$/u.test(sourceCommit)) throw new Error("sourceCommit must be a full Git commit SHA");
  return renderManifest(temporal, target, null, false, sourceCommit);
}

function renderManifest(
  temporal: TemporalConfig,
  target: TemporalTarget,
  material: DatabaseMaterial | null,
  includeDatabaseSecret = true,
  sourceCommit: string | null = null,
): string {
  const server = temporal.runtime.server;
  const ui = temporal.runtime.ui;
  const secretData = material === null
    ? { user: "dry-run", password: "dry-run", database: "dry-run", visibilityDatabase: "dry-run" }
    : material;
  const encoded = (value: string) => Buffer.from(value, "utf8").toString("base64");
  const namespace = target.namespace;
  const sourceIdentityLabel = sourceCommit === null ? "" : `\n        unidesk.ai/source-commit: ${sourceCommit}`;
  const logicalNamespaceCommands = temporal.runtime.logicalNamespaces.map((item) => `
              if ! temporal operator namespace describe --address ${server.serviceName}:${server.frontendPort} --namespace ${item.name} >/dev/null 2>&1; then
                temporal operator namespace create --address ${server.serviceName}:${server.frontendPort} --namespace ${item.name} --retention ${item.retention}
              fi`).join("");
  const databaseSecret = includeDatabaseSecret ? `---
apiVersion: v1
kind: Secret
metadata:
  name: ${temporal.database.secretName}
  namespace: ${namespace}
  labels:
    app.kubernetes.io/part-of: temporal
type: Opaque
data:
  user: ${encoded(secretData.user)}
  password: ${encoded(secretData.password)}
  database: ${encoded(secretData.database)}
  visibilityDatabase: ${encoded(secretData.visibilityDatabase)}
` : "";
  return `apiVersion: v1
kind: Namespace
metadata:
  name: ${namespace}
  labels:
    app.kubernetes.io/part-of: temporal
${databaseSecret}---
apiVersion: v1
kind: Service
metadata:
  name: ${server.serviceName}
  namespace: ${namespace}
spec:
  selector:
    app.kubernetes.io/name: temporal
  ports:
    - name: grpc
      port: ${server.frontendPort}
      targetPort: grpc
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${server.deploymentName}
  namespace: ${namespace}
  labels:
    app.kubernetes.io/name: temporal
    app.kubernetes.io/part-of: temporal
spec:
  replicas: ${server.replicas}
  selector:
    matchLabels:
      app.kubernetes.io/name: temporal
  template:
    metadata:
      labels:
        app.kubernetes.io/name: temporal
        app.kubernetes.io/part-of: temporal${sourceIdentityLabel}
    spec:
      enableServiceLinks: ${server.enableServiceLinks}
      shareProcessNamespace: ${server.shareProcessNamespace}
      containers:
        - name: temporal
          image: ${temporal.images.server}
          imagePullPolicy: ${temporal.images.pullPolicy}
          ports:
            - name: grpc
              containerPort: ${server.frontendPort}
          env:
            - name: DB
              value: postgres12
            - name: DB_PORT
              value: "${temporal.database.port}"
            - name: POSTGRES_SEEDS
              value: ${temporal.database.host}
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: ${temporal.database.secretName}
                  key: user
            - name: POSTGRES_PWD
              valueFrom:
                secretKeyRef:
                  name: ${temporal.database.secretName}
                  key: password
            - name: DBNAME
              valueFrom:
                secretKeyRef:
                  name: ${temporal.database.secretName}
                  key: database
            - name: VISIBILITY_DBNAME
              valueFrom:
                secretKeyRef:
                  name: ${temporal.database.secretName}
                  key: visibilityDatabase
            - name: POSTGRES_TLS_ENABLED
              value: "true"
            - name: POSTGRES_TLS_DISABLE_HOST_VERIFICATION
              value: "${temporal.database.disableHostVerification}"
            - name: SKIP_DB_CREATE
              value: "${temporal.database.skipCreate}"
            - name: DYNAMIC_CONFIG_FILE_PATH
              value: ${server.dynamicConfigFilePath}
          readinessProbe:
            tcpSocket:
              port: grpc
            initialDelaySeconds: 10
            periodSeconds: 5
          livenessProbe:
            tcpSocket:
              port: grpc
            initialDelaySeconds: 30
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: ${ui.serviceName}
  namespace: ${namespace}
spec:
  selector:
    app.kubernetes.io/name: temporal-ui
  ports:
    - name: http
      port: ${ui.port}
      targetPort: auth
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ${ui.deploymentName}
  namespace: ${namespace}
  labels:
    app.kubernetes.io/name: temporal-ui
    app.kubernetes.io/part-of: temporal
spec:
  replicas: ${ui.replicas}
  selector:
    matchLabels:
      app.kubernetes.io/name: temporal-ui
  template:
    metadata:
      labels:
        app.kubernetes.io/name: temporal-ui
        app.kubernetes.io/part-of: temporal${sourceIdentityLabel}
    spec:
      enableServiceLinks: ${ui.enableServiceLinks}
      containers:
        - name: temporal-ui
          image: ${temporal.images.ui}
          imagePullPolicy: ${temporal.images.pullPolicy}
          ports:
            - name: http
              containerPort: ${ui.port}
          env:
            - name: TEMPORAL_ADDRESS
              value: ${server.serviceName}:${server.frontendPort}
            - name: TEMPORAL_CORS_ORIGINS
              value: ${temporal.publicExposure.publicBaseUrl}
          readinessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /
              port: http
            initialDelaySeconds: 20
            periodSeconds: 10
        - name: auth-proxy
          image: ${temporal.images.authProxy}
          imagePullPolicy: ${temporal.images.pullPolicy}
          command:
            - sh
            - -ec
            - |
              password="$(cat /run/secrets/admin-password)"
              hash="$(caddy hash-password --plaintext "$password")"
              cat >/tmp/Caddyfile <<EOF
              :${ui.auth.proxyPort} {
                handle /healthz {
                  respond 200
                }
                handle {
                  basic_auth {
                    ${ui.auth.username} $hash
                  }
                  reverse_proxy 127.0.0.1:${ui.port}
                }
              }
              EOF
              exec caddy run --config /tmp/Caddyfile --adapter caddyfile
          ports:
            - name: auth
              containerPort: ${ui.auth.proxyPort}
          readinessProbe:
            tcpSocket:
              port: auth
            initialDelaySeconds: 3
            periodSeconds: 5
          livenessProbe:
            tcpSocket:
              port: auth
            initialDelaySeconds: 15
            periodSeconds: 10
          volumeMounts:
            - name: web-admin-password
              mountPath: /run/secrets/admin-password
              subPath: ${ui.auth.passwordKey}
              readOnly: true
      volumes:
        - name: web-admin-password
          secret:
            secretName: ${ui.auth.secretName}
---
apiVersion: batch/v1
kind: Job
metadata:
  name: temporal-logical-namespaces
  namespace: ${namespace}
  annotations:
    argocd.argoproj.io/hook: PostSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded
  labels:
    app.kubernetes.io/name: temporal-logical-namespaces
    app.kubernetes.io/part-of: temporal
spec:
  backoffLimit: 1
  template:
    metadata:
      labels:
        app.kubernetes.io/name: temporal-logical-namespaces
        app.kubernetes.io/part-of: temporal${sourceIdentityLabel}
    spec:
      restartPolicy: Never
      containers:
        - name: configure
          image: ${temporal.images.server}
          imagePullPolicy: ${temporal.images.pullPolicy}
          command:
            - sh
            - -ec
            - |
              attempts=${Math.max(1, Math.ceil(temporal.runtime.rolloutTimeoutSeconds / 5))}
              until temporal operator cluster health --address ${server.serviceName}:${server.frontendPort} >/dev/null 2>&1; do
                attempts=$((attempts - 1))
                test "$attempts" -gt 0
                sleep 5
              done${logicalNamespaceCommands}
`;
}

function dryRunScript(target: TemporalTarget, manifest: string): string {
  return manifestScript(manifest, `
if kubectl get namespace ${target.namespace} >/dev/null 2>&1; then
  kubectl apply --server-side --dry-run=server --field-manager=${fieldManager} -f "$manifest" >/dev/null
else
  kubectl apply --dry-run=client --validate=false -f "$manifest" >/dev/null
fi`);
}

function applyScript(temporal: TemporalConfig, target: TemporalTarget, manifest: string): string {
  const server = temporal.runtime.server;
  const ui = temporal.runtime.ui;
  const namespaceCommands = temporal.runtime.logicalNamespaces.map((item) => `
if ! kubectl -n ${target.namespace} exec deploy/${server.deploymentName} -- temporal operator namespace describe --address ${server.serviceName}:${server.frontendPort} --namespace ${item.name} >/dev/null 2>&1; then
  kubectl -n ${target.namespace} exec deploy/${server.deploymentName} -- temporal operator namespace create --address ${server.serviceName}:${server.frontendPort} --namespace ${item.name} --retention ${item.retention} >/dev/null
fi`).join("\n");
  const command = `
kubectl apply --server-side --force-conflicts --field-manager=${fieldManager} -f "$manifest" >/dev/null
kubectl -n ${target.namespace} rollout status deployment/${server.deploymentName} --timeout=${temporal.runtime.rolloutTimeoutSeconds}s >/dev/null
kubectl -n ${target.namespace} rollout status deployment/${ui.deploymentName} --timeout=${temporal.runtime.rolloutTimeoutSeconds}s >/dev/null
${namespaceCommands}
`;
  return manifestScript(manifest, command);
}

function manifestScript(manifest: string, command: string): string {
  const encoded = Buffer.from(manifest, "utf8").toString("base64");
  return `
set -eu
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
manifest="$tmp/temporal.yaml"
printf '%s' ${shQuote(encoded)} | base64 -d >"$manifest"
${command}
printf '%s\n' '{"ok":true,"valuesPrinted":false}'
`;
}

function statusScript(temporal: TemporalConfig, target: TemporalTarget, validate: boolean): string {
  const server = temporal.runtime.server;
  const ui = temporal.runtime.ui;
  const logicalNames = temporal.runtime.logicalNamespaces.map((item) => item.name);
  const logicalArgs = logicalNames.map(shQuote).join(" ");
  return `
set -u
server_ready="$(kubectl -n ${target.namespace} get deploy ${server.deploymentName} -o jsonpath='{.status.readyReplicas}' 2>/dev/null || true)"
ui_ready="$(kubectl -n ${target.namespace} get deploy ${ui.deploymentName} -o jsonpath='{.status.readyReplicas}' 2>/dev/null || true)"
endpoint="$(kubectl -n ${target.namespace} get endpoints ${server.serviceName} -o jsonpath='{.subsets[0].addresses[0].ip}' 2>/dev/null || true)"
ui_endpoint="$(kubectl -n ${target.namespace} get endpoints ${ui.serviceName} -o jsonpath='{.subsets[0].addresses[0].ip}' 2>/dev/null || true)"
secret="$(kubectl -n ${target.namespace} get secret ${temporal.database.secretName} -o name 2>/dev/null || true)"
process_table="$(kubectl -n ${target.namespace} exec deploy/${server.deploymentName} -- ps -eo pid,ppid,stat,comm,args 2>/dev/null || true)"
pid1_command="$(printf '%s\n' "$process_table" | awk 'NR > 1 && $1 == 1 { print $4; exit }')"
temporal_pid="$(printf '%s\n' "$process_table" | awk 'NR > 1 && $4 == "temporal-server" { print $1; exit }')"
zombie_count="$(printf '%s\n' "$process_table" | awk 'NR > 1 && $3 ~ /^Z/ { count += 1 } END { print count + 0 }')"
logical_present=0
for logical in ${logicalArgs}; do
  if kubectl -n ${target.namespace} exec deploy/${server.deploymentName} -- temporal operator namespace describe --address ${server.serviceName}:${server.frontendPort} --namespace "$logical" >/dev/null 2>&1; then
    logical_present=$((logical_present + 1))
  fi
done
python3 - "$server_ready" "$ui_ready" "$endpoint" "$ui_endpoint" "$secret" "$logical_present" "${logicalNames.length}" "$pid1_command" "$temporal_pid" "$zombie_count" "${validate}" <<'PY'
import json, sys
server_ready, ui_ready, endpoint, ui_endpoint, secret = sys.argv[1:6]
logical_present, logical_expected = int(sys.argv[6]), int(sys.argv[7])
pid1_command, temporal_pid = sys.argv[8], sys.argv[9]
zombie_count = int(sys.argv[10])
validate = sys.argv[11] == "true"
ready = int(server_ready or 0) >= ${server.replicas} and int(ui_ready or 0) >= ${ui.replicas}
process_reaping = bool(pid1_command) and pid1_command != "temporal-server" and int(temporal_pid or 0) > 1 and zombie_count == 0
ok = ready and bool(endpoint) and bool(ui_endpoint) and bool(secret) and logical_present == logical_expected and process_reaping
print(json.dumps({
  "ok": ok,
  "mode": "validate" if validate else "status",
  "namespace": "${target.namespace}",
  "server": {"readyReplicas": int(server_ready or 0), "endpointPresent": bool(endpoint), "serviceDns": "${server.serviceName}.${target.namespace}.svc.cluster.local:${server.frontendPort}"},
  "ui": {"readyReplicas": int(ui_ready or 0), "endpointPresent": bool(ui_endpoint), "publicUrl": "${temporal.publicExposure.publicBaseUrl}"},
  "database": {"mode": "nc01-host-native-postgresql", "host": "${temporal.database.host}", "secretPresent": bool(secret), "valuesPrinted": False},
  "logicalNamespaces": {"expected": logical_expected, "present": logical_present, "names": ${JSON.stringify(logicalNames)}},
  "processReaping": {"ok": process_reaping, "pid1Command": pid1_command, "temporalPid": int(temporal_pid or 0), "zombieCount": zombie_count},
}, ensure_ascii=False))
sys.exit(0 if ok else 1)
PY
`;
}

function readDatabaseMaterial(temporal: TemporalConfig): DatabaseMaterial {
  const source = readEnvSourceFile({
    root: secretRoot,
    sourceRef: temporal.database.sourceRef,
    missingMessage: () => `Temporal database source is missing; run platform-db postgres apply --config ${temporal.database.configRef} --confirm first`,
  });
  const keys = temporal.database.sourceKeys;
  const material = {
    user: requiredEnvValue(source.values, keys.user, temporal.database.sourceRef),
    password: requiredEnvValue(source.values, keys.password, temporal.database.sourceRef),
    database: requiredEnvValue(source.values, keys.database, temporal.database.sourceRef),
    visibilityDatabase: requiredEnvValue(source.values, keys.visibilityDatabase, temporal.database.sourceRef),
  };
  return { ...material, fingerprint: fingerprintSecretValues(material, Object.keys(material)) };
}

export function readTemporalConfig(): TemporalConfig {
  const root = readYamlRecord(configPath, "platform-infra-temporal");
  const defaults = recordField(root, "defaults", configLabel);
  const delivery = recordField(root, "delivery", configLabel);
  const deliveryPipeline = recordField(delivery, "pipeline", `${configLabel}.delivery`);
  const deliveryGitops = recordField(delivery, "gitops", `${configLabel}.delivery`);
  const deliveryGitopsAuthor = recordField(deliveryGitops, "author", `${configLabel}.delivery.gitops`);
  const deliveryArgo = recordField(delivery, "argo", `${configLabel}.delivery`);
  const images = recordField(root, "images", configLabel);
  const database = recordField(root, "database", configLabel);
  const sourceKeys = recordField(database, "sourceKeys", `${configLabel}.database`);
  const runtime = recordField(root, "runtime", configLabel);
  const server = recordField(runtime, "server", `${configLabel}.runtime`);
  const ui = recordField(runtime, "ui", `${configLabel}.runtime`);
  const uiAuth = recordField(ui, "auth", `${configLabel}.runtime.ui`);
  const exposure = recordField(root, "publicExposure", configLabel);
  const webProbe = recordField(root, "webProbe", configLabel);
  const webProbeRunner = recordField(webProbe, "runner", `${configLabel}.webProbe`);
  const webProbeOrigin = recordField(webProbe, "origin", `${configLabel}.webProbe`);
  const webProbeAuth = recordField(webProbe, "authentication", `${configLabel}.webProbe`);
  const webProbeCredentials = recordField(webProbeAuth, "credentials", `${configLabel}.webProbe.authentication`);
  const webProbeProfiles = recordField(webProbe, "smokeProfiles", `${configLabel}.webProbe`);
  const smokeProfiles = Object.fromEntries(Object.entries(webProbeProfiles).map(([name, value]) => {
    const path = `${configLabel}.webProbe.smokeProfiles.${name}`;
    const profile = recordField({ value }, "value", path);
    const viewport = recordField(profile, "viewport", path);
    const limits = recordField(profile, "outputLimits", path);
    return [name, {
      path: stringField(profile, "path", path),
      readySelector: stringField(profile, "readySelector", path),
      minimumBodyTextBytes: integerField(profile, "minimumBodyTextBytes", path),
      screenshotName: stringField(profile, "screenshotName", path),
      viewport: { width: integerField(viewport, "width", `${path}.viewport`), height: integerField(viewport, "height", `${path}.viewport`) },
      navigationTimeoutMs: integerField(profile, "navigationTimeoutMs", path),
      settleMs: integerField(profile, "settleMs", path),
      commandTimeoutSeconds: integerField(profile, "commandTimeoutSeconds", path),
      outputLimits: {
        failures: integerField(limits, "failures", `${path}.outputLimits`),
        network: integerField(limits, "network", `${path}.outputLimits`),
        console: integerField(limits, "console", `${path}.outputLimits`),
      },
    } satisfies TemporalWebProbeSmokeProfile];
  }));
  const defaultSmokeProfile = stringField(webProbe, "defaultSmokeProfile", `${configLabel}.webProbe`);
  if (smokeProfiles[defaultSmokeProfile] === undefined) throw new Error(`${configLabel}.webProbe.defaultSmokeProfile must reference smokeProfiles`);
  const webProbeMode = stringField(webProbeAuth, "mode", `${configLabel}.webProbe.authentication`);
  if (webProbeMode !== "basic-auth") throw new Error(`${configLabel}.webProbe.authentication.mode must be basic-auth`);
  const productId = stringField(webProbe, "productId", `${configLabel}.webProbe`);
  if (productId !== "temporal") throw new Error(`${configLabel}.webProbe.productId must be temporal`);
  const browserProxyMode = stringField(webProbeOrigin, "browserProxyMode", `${configLabel}.webProbe.origin`);
  if (browserProxyMode !== "auto" && browserProxyMode !== "direct") throw new Error(`${configLabel}.webProbe.origin.browserProxyMode must be auto or direct`);
  return {
    defaults: { targetId: stringField(defaults, "targetId", `${configLabel}.defaults`) },
    delivery: {
      enabled: booleanField(delivery, "enabled", `${configLabel}.delivery`),
      repositoryRef: stringField(delivery, "repositoryRef", `${configLabel}.delivery`),
      sourceBranch: stringField(delivery, "sourceBranch", `${configLabel}.delivery`),
      sourceSnapshotPrefix: stringField(delivery, "sourceSnapshotPrefix", `${configLabel}.delivery`),
      pipeline: {
        name: stringField(deliveryPipeline, "name", `${configLabel}.delivery.pipeline`),
        runPrefix: stringField(deliveryPipeline, "runPrefix", `${configLabel}.delivery.pipeline`),
        namespace: stringField(deliveryPipeline, "namespace", `${configLabel}.delivery.pipeline`),
        serviceAccount: stringField(deliveryPipeline, "serviceAccount", `${configLabel}.delivery.pipeline`),
        timeout: stringField(deliveryPipeline, "timeout", `${configLabel}.delivery.pipeline`),
      },
      gitops: {
        readUrl: stringField(deliveryGitops, "readUrl", `${configLabel}.delivery.gitops`),
        writeUrl: stringField(deliveryGitops, "writeUrl", `${configLabel}.delivery.gitops`),
        branch: stringField(deliveryGitops, "branch", `${configLabel}.delivery.gitops`),
        path: stringField(deliveryGitops, "path", `${configLabel}.delivery.gitops`),
        manifestPath: stringField(deliveryGitops, "manifestPath", `${configLabel}.delivery.gitops`),
        author: {
          name: stringField(deliveryGitopsAuthor, "name", `${configLabel}.delivery.gitops.author`),
          email: stringField(deliveryGitopsAuthor, "email", `${configLabel}.delivery.gitops.author`),
        },
      },
      argo: {
        namespace: stringField(deliveryArgo, "namespace", `${configLabel}.delivery.argo`),
        application: stringField(deliveryArgo, "application", `${configLabel}.delivery.argo`),
        project: stringField(deliveryArgo, "project", `${configLabel}.delivery.argo`),
        destinationNamespace: stringField(deliveryArgo, "destinationNamespace", `${configLabel}.delivery.argo`),
        automated: booleanField(deliveryArgo, "automated", `${configLabel}.delivery.argo`),
      },
      materializationPaths: stringArrayField(delivery, "materializationPaths", `${configLabel}.delivery`),
    },
    images: {
      server: stringField(images, "server", `${configLabel}.images`),
      ui: stringField(images, "ui", `${configLabel}.images`),
      authProxy: stringField(images, "authProxy", `${configLabel}.images`),
      pullPolicy: stringField(images, "pullPolicy", `${configLabel}.images`),
    },
    targets: arrayField(root, "targets", configLabel).map((item, index) => ({
      id: stringField(item, "id", `${configLabel}.targets[${index}]`),
      route: stringField(item, "route", `${configLabel}.targets[${index}]`),
      namespace: stringField(item, "namespace", `${configLabel}.targets[${index}]`),
      createNamespace: booleanField(item, "createNamespace", `${configLabel}.targets[${index}]`),
      enabled: booleanField(item, "enabled", `${configLabel}.targets[${index}]`),
    })),
    database: {
      configRef: stringField(database, "configRef", `${configLabel}.database`),
      sourceRef: stringField(database, "sourceRef", `${configLabel}.database`),
      sourceKeys: {
        user: stringField(sourceKeys, "user", `${configLabel}.database.sourceKeys`),
        password: stringField(sourceKeys, "password", `${configLabel}.database.sourceKeys`),
        database: stringField(sourceKeys, "database", `${configLabel}.database.sourceKeys`),
        visibilityDatabase: stringField(sourceKeys, "visibilityDatabase", `${configLabel}.database.sourceKeys`),
      },
      host: stringField(database, "host", `${configLabel}.database`),
      port: integerField(database, "port", `${configLabel}.database`),
      sslMode: stringField(database, "sslMode", `${configLabel}.database`),
      disableHostVerification: booleanField(database, "disableHostVerification", `${configLabel}.database`),
      skipCreate: booleanField(database, "skipCreate", `${configLabel}.database`),
      secretName: stringField(database, "secretName", `${configLabel}.database`),
    },
    runtime: {
      server: {
        ...deploymentConfig(server, `${configLabel}.runtime.server`, "frontendPort"),
        dynamicConfigFilePath: stringField(server, "dynamicConfigFilePath", `${configLabel}.runtime.server`),
        enableServiceLinks: booleanField(server, "enableServiceLinks", `${configLabel}.runtime.server`),
        shareProcessNamespace: booleanField(server, "shareProcessNamespace", `${configLabel}.runtime.server`),
      },
      ui: {
        ...deploymentConfig(ui, `${configLabel}.runtime.ui`, "port"),
        enableServiceLinks: booleanField(ui, "enableServiceLinks", `${configLabel}.runtime.ui`),
        auth: {
          username: stringField(uiAuth, "username", `${configLabel}.runtime.ui.auth`),
          proxyPort: integerField(uiAuth, "proxyPort", `${configLabel}.runtime.ui.auth`),
          secretName: stringField(uiAuth, "secretName", `${configLabel}.runtime.ui.auth`),
          passwordKey: stringField(uiAuth, "passwordKey", `${configLabel}.runtime.ui.auth`),
        },
      },
      logicalNamespaces: arrayField(runtime, "logicalNamespaces", `${configLabel}.runtime`).map((item, index) => ({
        name: stringField(item, "name", `${configLabel}.runtime.logicalNamespaces[${index}]`),
        retention: stringField(item, "retention", `${configLabel}.runtime.logicalNamespaces[${index}]`),
      })),
      rolloutTimeoutSeconds: integerField(runtime, "rolloutTimeoutSeconds", `${configLabel}.runtime`),
    },
    publicExposure: {
      enabled: booleanField(exposure, "enabled", `${configLabel}.publicExposure`),
      publicBaseUrl: stringField(exposure, "publicBaseUrl", `${configLabel}.publicExposure`),
      hostname: stringField(exposure, "hostname", `${configLabel}.publicExposure`),
      upstream: stringField(exposure, "upstream", `${configLabel}.publicExposure`),
      healthPath: stringField(exposure, "healthPath", `${configLabel}.publicExposure`),
      expectedA: stringField(exposure, "expectedA", `${configLabel}.publicExposure`),
    },
    webProbe: {
      enabled: booleanField(webProbe, "enabled", `${configLabel}.webProbe`),
      productId,
      runner: {
        node: stringField(webProbeRunner, "node", `${configLabel}.webProbe.runner`),
        lane: stringField(webProbeRunner, "lane", `${configLabel}.webProbe.runner`),
      },
      origin: { browserProxyMode },
      authentication: {
        mode: webProbeMode,
        path: stringField(webProbeAuth, "path", `${configLabel}.webProbe.authentication`),
        authenticatedSelector: stringField(webProbeAuth, "authenticatedSelector", `${configLabel}.webProbe.authentication`),
        credentials: {
          configRef: stringField(webProbeCredentials, "configRef", `${configLabel}.webProbe.authentication.credentials`),
          targetId: stringField(webProbeCredentials, "targetId", `${configLabel}.webProbe.authentication.credentials`),
          secretName: stringField(webProbeCredentials, "secretName", `${configLabel}.webProbe.authentication.credentials`),
          passwordTargetKey: stringField(webProbeCredentials, "passwordTargetKey", `${configLabel}.webProbe.authentication.credentials`),
        },
      },
      defaultSmokeProfile,
      smokeProfiles,
    },
  };
}

function deploymentConfig(record: Record<string, unknown>, path: string, portKey: "frontendPort" | "port"): any {
  return {
    deploymentName: stringField(record, "deploymentName", path),
    serviceName: stringField(record, "serviceName", path),
    replicas: integerField(record, "replicas", path),
    [portKey]: integerField(record, portKey, path),
  };
}

export function resolveTemporalTarget(temporal: TemporalConfig, requested: string | null): TemporalTarget {
  const id = requested ?? temporal.defaults.targetId;
  const target = temporal.targets.find((item) => item.id === id);
  if (target === undefined) throw new Error(`${configLabel}.targets does not contain ${id}`);
  if (!target.enabled) throw new Error(`${configLabel}.targets[id=${id}] is disabled`);
  return target;
}

function policyChecks(temporal: TemporalConfig, target: TemporalTarget, manifest: string): Array<{ name: string; ok: boolean; detail: string }> {
  return [
    { name: "independent-namespace", ok: target.namespace === "temporal", detail: target.namespace },
    { name: "native-postgres", ok: temporal.database.configRef === "config/platform-db/postgres-nc01.yaml", detail: temporal.database.configRef },
    { name: "no-in-cluster-postgres", ok: !/kind: StatefulSet|PersistentVolumeClaim|image: .*postgres/iu.test(manifest), detail: "Temporal manifest contains no PostgreSQL workload or PVC." },
    { name: "cluster-internal-grpc", ok: !/type: (?:NodePort|LoadBalancer)/u.test(manifest), detail: `${temporal.runtime.server.serviceName}.${target.namespace}.svc.cluster.local` },
    { name: "pid1-reaper", ok: temporal.runtime.server.shareProcessNamespace && /shareProcessNamespace: true/u.test(manifest), detail: `${configLabel}.runtime.server.shareProcessNamespace` },
    { name: "public-ui-only", ok: temporal.publicExposure.publicBaseUrl === "https://temporal.hwpod.com", detail: temporal.publicExposure.publicBaseUrl },
    { name: "unified-web-admin", ok: temporal.runtime.ui.auth.secretName === "temporal-web-admin", detail: "Web password is distributed from unified-admin-password.txt through the secrets CLI." },
  ];
}

function targetSummary(target: TemporalTarget): Record<string, unknown> {
  return { id: target.id, route: target.route, namespace: target.namespace };
}

function summary(temporal: TemporalConfig, target: TemporalTarget): Record<string, unknown> {
  return {
    target: targetSummary(target),
    images: temporal.images,
    database: { mode: "host-native", configRef: temporal.database.configRef, sourceRef: temporal.database.sourceRef, host: temporal.database.host, sslMode: temporal.database.sslMode },
    frontend: `${temporal.runtime.server.serviceName}.${target.namespace}.svc.cluster.local:${temporal.runtime.server.frontendPort}`,
    processReaping: { shareProcessNamespace: temporal.runtime.server.shareProcessNamespace },
    logicalNamespaces: temporal.runtime.logicalNamespaces,
    publicExposure: temporal.publicExposure,
  };
}
