import type { UniDeskConfig } from "../config";
import type { RenderedCliResult } from "../output";
import { capture, compactCapture, parseJsonOutput, redactSensitiveUnknown, shQuote } from "../platform-infra-ops-library";
import { resolveTarget } from "./actions";
import { readObservabilityConfig } from "./config";
import { formatTable, shortenEnd, textValue } from "./manifest";
import { imageReference, indent, targetSummary } from "./summary";
import type { MetricsQueryOptions, ObservabilityConfig, ObservabilityTarget } from "./types";
import { metricsEnabledForTarget, metricsTargetDisposition } from "./metrics-target";

export function prometheusManifests(observability: ObservabilityConfig, target: ObservabilityTarget): string[] {
  const prometheus = observability.metricsBackend;
  if (!metricsEnabledForTarget(observability, target)) return [];
  const namespaceNames = prometheus.discovery.namespaces.length > 0
    ? `\n      namespaces:\n        names:\n${prometheus.discovery.namespaces.map((name) => `          - ${name}`).join("\n")}`
    : "";
  const config = `global:\n  scrape_interval: ${prometheus.discovery.scrapeInterval}\n  scrape_timeout: ${prometheus.discovery.scrapeTimeout}\nscrape_configs:\n  - job_name: kubernetes-pods\n    kubernetes_sd_configs:\n      - role: pod${namespaceNames}\n    relabel_configs:\n      - source_labels: [__meta_kubernetes_pod_annotation_${prometheusMetaLabel(prometheus.discovery.annotationKeys.scrape)}]\n        action: keep\n        regex: \"true\"\n      - source_labels: [__meta_kubernetes_pod_label_${prometheusMetaLabel(prometheus.discovery.labelSelector.key)}]\n        action: keep\n        regex: '${re2Literal(prometheus.discovery.labelSelector.value)}'\n      - source_labels: [__meta_kubernetes_pod_annotation_${prometheusMetaLabel(prometheus.discovery.annotationKeys.path)}]\n        action: replace\n        target_label: __metrics_path__\n        regex: (/.+)\n      - source_labels: [__address__, __meta_kubernetes_pod_annotation_${prometheusMetaLabel(prometheus.discovery.annotationKeys.port)}]\n        action: replace\n        regex: ([^:]+)(?::\\d+)?;(\\d+)\n        replacement: \$1:\$2\n        target_label: __address__\n      - source_labels: [__meta_kubernetes_pod_annotation_${prometheusMetaLabel(prometheus.discovery.annotationKeys.path)}]\n        action: replace\n        target_label: __metrics_path__\n        regex: ^$\n        replacement: ${prometheus.discovery.defaultPath}\n`;
  const labels = `    app.kubernetes.io/name: ${prometheus.deploymentName}\n    app.kubernetes.io/component: metrics-backend\n    app.kubernetes.io/part-of: platform-infra\n    app.kubernetes.io/managed-by: unidesk`;
  return [
    `apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: ${prometheus.serviceAccountName}\n  namespace: ${target.namespace}\n  labels:\n${labels}\n`,
    `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: ${prometheus.clusterRoleName}\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"nodes\", \"nodes/proxy\", \"services\", \"endpoints\", \"pods\"]\n    verbs: [\"get\", \"list\", \"watch\"]\n`,
    `apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: ${prometheus.clusterRoleBindingName}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: ${prometheus.clusterRoleName}\nsubjects:\n  - kind: ServiceAccount\n    name: ${prometheus.serviceAccountName}\n    namespace: ${target.namespace}\n`,
    `apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: ${prometheus.configMapName}\n  namespace: ${target.namespace}\n  labels:\n${labels}\ndata:\n  prometheus.yml: |\n${indent(config, 4)}\n`,
    `apiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: ${prometheus.persistentVolumeClaimName}\n  namespace: ${target.namespace}\n  labels:\n${labels}\nspec:\n  accessModes: [ReadWriteOnce]\n  resources:\n    requests:\n      storage: ${prometheus.storage.size}\n`,
    `apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: ${prometheus.deploymentName}\n  namespace: ${target.namespace}\n  labels:\n${labels}\nspec:\n  replicas: ${prometheus.replicas}\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: ${prometheus.deploymentName}\n      app.kubernetes.io/component: metrics-backend\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: ${prometheus.deploymentName}\n        app.kubernetes.io/component: metrics-backend\n        app.kubernetes.io/part-of: platform-infra\n    spec:\n      serviceAccountName: ${prometheus.serviceAccountName}\n      containers:\n        - name: prometheus\n          image: ${imageReference(observability.images.prometheus)}\n          imagePullPolicy: ${observability.images.prometheus.pullPolicy}\n          args:\n            - --config.file=/etc/prometheus/prometheus.yml\n            - --storage.tsdb.path=/prometheus\n            - --storage.tsdb.retention.time=${prometheus.storage.retention}\n          ports:\n            - name: http\n              containerPort: ${prometheus.httpPort}\n          readinessProbe:\n            httpGet:\n              path: /-/ready\n              port: http\n          volumeMounts:\n            - name: config\n              mountPath: /etc/prometheus/prometheus.yml\n              subPath: prometheus.yml\n              readOnly: true\n            - name: data\n              mountPath: /prometheus\n      volumes:\n        - name: config\n          configMap:\n            name: ${prometheus.configMapName}\n        - name: data\n          persistentVolumeClaim:\n            claimName: ${prometheus.persistentVolumeClaimName}\n`,
    `apiVersion: v1\nkind: Service\nmetadata:\n  name: ${prometheus.serviceName}\n  namespace: ${target.namespace}\n  labels:\n${labels}\nspec:\n  type: ClusterIP\n  selector:\n    app.kubernetes.io/name: ${prometheus.deploymentName}\n    app.kubernetes.io/component: metrics-backend\n  ports:\n    - name: http\n      port: ${prometheus.httpPort}\n      targetPort: http\n`,
  ];
}

export async function metricsQuery(config: UniDeskConfig, options: MetricsQueryOptions): Promise<Record<string, unknown> | RenderedCliResult> {
  if (options.query === null || options.query.trim() === "") throw new Error("observability metrics-query requires --query <promql>");
  const observability = readObservabilityConfig();
  const target = resolveTarget(observability, options.targetId);
  const prometheus = observability.metricsBackend;
  const disposition = metricsTargetDisposition(observability, target);
  if (disposition !== "enabled") return { ok: false, action: "platform-infra-observability-metrics-query", mutation: false, target: targetSummary(target), metrics: { enabled: false, disposition, targetIds: prometheus.targetIds }, warnings: [`Prometheus is ${disposition} in config/platform-infra/observability.yaml`] };
  const path = `${prometheus.query.path}?query=${encodeURIComponent(options.query)}`;
  const script = `set -u\npython3 - <<'PY'\nimport json, subprocess\npath = ${JSON.stringify(`/api/v1/namespaces/${target.namespace}/services/http:${prometheus.serviceName}:http/proxy${path}`)}\nproc = subprocess.run([\"kubectl\", \"get\", \"--raw\", path], text=True, capture_output=True, timeout=${prometheus.query.timeoutSeconds})\nbody = proc.stdout[:${prometheus.query.maxResponseBytes}]\ntry:\n  parsed = json.loads(body) if body else None\nexcept Exception:\n  parsed = None\nresult = parsed.get(\"data\", {}).get(\"result\", []) if isinstance(parsed, dict) else []\nprint(json.dumps({\"ok\": proc.returncode == 0 and isinstance(parsed, dict) and parsed.get(\"status\") == \"success\", \"exitCode\": proc.returncode, \"truncated\": len(proc.stdout) > ${prometheus.query.maxResponseBytes} or len(result) > ${prometheus.query.maxSeries}, \"resultType\": parsed.get(\"data\", {}).get(\"resultType\") if isinstance(parsed, dict) else None, \"result\": result[:${prometheus.query.maxSeries}], \"stderrTail\": proc.stderr[-2000:]}, ensure_ascii=False))\nPY`;
  const result = await capture(config, target.route, ["sh"], script);
  const parsed = parseJsonOutput(result.stdout);
  const ok = result.exitCode === 0 && parsed?.ok === true;
  if (options.full || options.raw) return { ok, action: "platform-infra-observability-metrics-query", mutation: false, target: targetSummary(target), query: options.query, budgets: prometheus.query, result: parsed === null ? compactCapture(result, { full: true }) : redactSensitiveUnknown(parsed) };
  const rows = Array.isArray(parsed?.result) ? parsed.result.slice(0, 20).map((item: unknown) => [shortenEnd(JSON.stringify(item), 160)]) : [];
  return { ok, command: "platform-infra observability metrics-query", contentType: "text/plain", renderedText: [`platform-infra observability metrics-query (${ok ? "ok" : "not-ok"})`, "", `target=${target.id} prometheus=${prometheus.serviceName}.${target.namespace}.svc.cluster.local:${prometheus.httpPort}`, `query=${options.query}`, `budget=timeout:${prometheus.query.timeoutSeconds}s series:${prometheus.query.maxSeries} bytes:${prometheus.query.maxResponseBytes} truncated=${textValue(parsed?.truncated)}`, "", formatTable(["RESULT"], rows.length > 0 ? rows : [["-"]]), "", `Next: bun scripts/cli.ts platform-infra observability metrics-query --target ${target.id} --query ${shQuote(options.query)} --full`].join("\n") };
}

export function prometheusMetaLabel(value: string): string {
  return value.replace(/[^A-Za-z0-9_]/gu, "_");
}

export function re2Literal(value: string): string {
  return `^${value.replace(/[\\.^$|?*+()[\]{}]/gu, "\\$&")}$`;
}
