// SPEC: PJ2026-01060703 CI/CD branch follower controller render helpers.
// Responsibility: Kubernetes controller/reconcile Job manifests and controller bootstrap scripts.
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { rootPath } from "./config";
import { shQuote } from "./platform-infra-ops-library";
import type { BranchFollowerDebugStep, BranchFollowerRegistry, ParsedOptions } from "./cicd-types";

const SPEC_REF = "PJ2026-01060703";

export function renderControllerReconcileJob(registry: BranchFollowerRegistry, options: ParsedOptions, jobName: string, mode: { dryRun: boolean; wait?: boolean; recordState: boolean }, timeoutSeconds: number): Record<string, unknown> {
  const labels = { ...registry.controller.labels, "app.kubernetes.io/component": "cicd-reconcile-job" };
  const commandArgs = [
    "bun",
    "scripts/cli.ts",
    "cicd",
    "branch-follower",
    "run-once",
    ...(options.followerId === null ? ["--all"] : ["--follower", options.followerId]),
    mode.dryRun ? "--dry-run" : "--confirm",
    "--wait",
    "--in-cluster",
    "--config",
    "config/cicd-branch-followers.yaml",
    "--timeout-seconds",
    String(timeoutSeconds),
    ...(mode.recordState ? ["--record-state"] : []),
  ];
  return {
    apiVersion: "batch/v1",
    kind: "Job",
    metadata: { name: jobName, namespace: registry.controller.namespace, labels },
    spec: {
      backoffLimit: registry.controller.budgets.reconcileJobBackoffLimit,
      ttlSecondsAfterFinished: registry.controller.budgets.reconcileJobTtlSeconds,
      activeDeadlineSeconds: timeoutSeconds + registry.controller.budgets.reconcileJobDeadlineGraceSeconds,
      template: {
        metadata: { labels },
        spec: {
          restartPolicy: "Never",
          serviceAccountName: registry.controller.serviceAccountName,
          volumes: [
            { name: "registry", configMap: { name: registry.controller.configMapName, defaultMode: 0o755 } },
            { name: "git-mirror-cache", persistentVolumeClaim: { claimName: registry.controller.source.gitMirrorCachePvcName } },
            { name: "git-ssh", secret: { secretName: registry.controller.source.githubSsh.secretName, defaultMode: 0o400 } },
            { name: "work", emptyDir: {} },
          ],
          containers: [
            {
              name: "reconcile",
              image: registry.controller.image,
              imagePullPolicy: "IfNotPresent",
              command: ["/bin/sh", "/etc/unidesk-cicd-branch-follower/controller-one-shot.sh"],
              args: commandArgs,
              env: [
                { name: "UNIDESK_CONTROLLER_SOURCE_BRANCH", value: registry.controller.source.branch },
                { name: "UNIDESK_CONTROLLER_SOURCE_REPOSITORY", value: registry.controller.source.repository },
                { name: "UNIDESK_CONTROLLER_SOURCE_SNAPSHOT_PREFIX", value: registry.controller.source.sourceSnapshot.stageRefPrefix.replaceAll("{branch}", registry.controller.source.branch) },
                { name: "UNIDESK_CONTROLLER_GITHUB_SSH_PRIVATE_KEY", value: `/git-ssh/${registry.controller.source.githubSsh.privateKeySecretKey}` },
                { name: "UNIDESK_CONTROLLER_GITHUB_PROXY_HOST", value: registry.controller.source.githubSsh.proxyHost },
                { name: "UNIDESK_CONTROLLER_GITHUB_PROXY_PORT", value: String(registry.controller.source.githubSsh.proxyPort) },
              ],
              volumeMounts: [
                { name: "registry", mountPath: "/etc/unidesk-cicd-branch-follower", readOnly: true },
                { name: "git-mirror-cache", mountPath: "/cache" },
                { name: "git-ssh", mountPath: "/git-ssh", readOnly: true },
                { name: "work", mountPath: "/work" },
              ],
            },
          ],
        },
      },
    },
  };
}

export function renderControllerDebugJob(registry: BranchFollowerRegistry, options: ParsedOptions, jobName: string, step: BranchFollowerDebugStep, timeoutSeconds: number): Record<string, unknown> {
  if (options.followerId === null) throw new Error("debug-step target job requires --follower <id>");
  const labels = { ...registry.controller.labels, "app.kubernetes.io/component": "cicd-debug-job" };
  const commandArgs = [
    "bun",
    "scripts/cli.ts",
    "cicd",
    "branch-follower",
    "debug-step",
    "--follower",
    options.followerId,
    "--step",
    step,
    options.confirm ? "--confirm" : "--dry-run",
    "--in-cluster",
    "--config",
    "config/cicd-branch-followers.yaml",
    "--timeout-seconds",
    String(timeoutSeconds),
    "--json",
  ];
  return {
    apiVersion: "batch/v1",
    kind: "Job",
    metadata: { name: jobName, namespace: registry.controller.namespace, labels },
    spec: {
      backoffLimit: registry.controller.budgets.reconcileJobBackoffLimit,
      ttlSecondsAfterFinished: registry.controller.budgets.reconcileJobTtlSeconds,
      activeDeadlineSeconds: timeoutSeconds + registry.controller.budgets.reconcileJobDeadlineGraceSeconds,
      template: {
        metadata: { labels },
        spec: {
          restartPolicy: "Never",
          serviceAccountName: registry.controller.serviceAccountName,
          volumes: [
            { name: "registry", configMap: { name: registry.controller.configMapName, defaultMode: 0o755 } },
            { name: "git-mirror-cache", persistentVolumeClaim: { claimName: registry.controller.source.gitMirrorCachePvcName } },
            { name: "git-ssh", secret: { secretName: registry.controller.source.githubSsh.secretName, defaultMode: 0o400 } },
            { name: "work", emptyDir: {} },
          ],
          containers: [
            {
              name: "debug",
              image: registry.controller.image,
              imagePullPolicy: "IfNotPresent",
              command: ["/bin/sh", "/etc/unidesk-cicd-branch-follower/controller-one-shot.sh"],
              args: commandArgs,
              env: [
                { name: "UNIDESK_CONTROLLER_SOURCE_BRANCH", value: registry.controller.source.branch },
                { name: "UNIDESK_CONTROLLER_SOURCE_REPOSITORY", value: registry.controller.source.repository },
                { name: "UNIDESK_CONTROLLER_SOURCE_SNAPSHOT_PREFIX", value: registry.controller.source.sourceSnapshot.stageRefPrefix.replaceAll("{branch}", registry.controller.source.branch) },
                { name: "UNIDESK_CONTROLLER_GITHUB_SSH_PRIVATE_KEY", value: `/git-ssh/${registry.controller.source.githubSsh.privateKeySecretKey}` },
                { name: "UNIDESK_CONTROLLER_GITHUB_PROXY_HOST", value: registry.controller.source.githubSsh.proxyHost },
                { name: "UNIDESK_CONTROLLER_GITHUB_PROXY_PORT", value: String(registry.controller.source.githubSsh.proxyPort) },
              ],
              volumeMounts: [
                { name: "registry", mountPath: "/etc/unidesk-cicd-branch-follower", readOnly: true },
                { name: "git-mirror-cache", mountPath: "/cache" },
                { name: "git-ssh", mountPath: "/git-ssh", readOnly: true },
                { name: "work", mountPath: "/work" },
              ],
            },
          ],
        },
      },
    },
  };
}

export function waitForJobShell(namespace: string, jobName: string, timeoutSeconds: number): string {
  return [
    `NAMESPACE=${shQuote(namespace)}`,
    `JOB_NAME=${shQuote(jobName)}`,
    `TIMEOUT_SECONDS=${shQuote(String(timeoutSeconds))}`,
    "export NAMESPACE JOB_NAME TIMEOUT_SECONDS",
    nativeCicdScript("wait-job.sh"),
  ].join("\n");
}

export function renderControllerManifests(registry: BranchFollowerRegistry): Record<string, unknown>[] {
  const labels = registry.controller.labels;
  const selector = labels;
  const controllerConfigData = {
    "cicd-branch-followers.yaml": registry.rawText,
    "sync-source.sh": nativeCicdScript("sync-source.sh"),
    "controller-one-shot.sh": nativeCicdScript("controller-one-shot.sh"),
    "controller-loop.sh": nativeCicdScript("controller-loop.sh"),
    "hwlab-node-control-plane-refresh.mjs": nativeCicdScript("hwlab-node-control-plane-refresh.mjs"),
    "github-proxy-connect.mjs": nativeCicdScript("github-proxy-connect.mjs"),
    "git-ssh-proxy.sh": nativeCicdScript("git-ssh-proxy.sh"),
    "runtime-gitops-observability.mjs": nativeHwlabScript("runtime-gitops-observability.mjs"),
    "runtime-gitops-postprocess.mjs": nativeHwlabScript("runtime-gitops-postprocess.mjs"),
    "runtime-gitops-verify.mjs": nativeHwlabScript("runtime-gitops-verify.mjs"),
  };
  const controllerConfigSha = sha256(JSON.stringify(controllerConfigData));
  return [
    {
      apiVersion: "v1",
      kind: "Namespace",
      metadata: { name: registry.controller.namespace, labels },
    },
    {
      apiVersion: "v1",
      kind: "ServiceAccount",
      metadata: { name: registry.controller.serviceAccountName, namespace: registry.controller.namespace, labels },
    },
    {
      apiVersion: "rbac.authorization.k8s.io/v1",
      kind: "Role",
      metadata: { name: registry.controller.serviceAccountName, namespace: registry.controller.namespace, labels },
      rules: [
        { apiGroups: [""], resources: ["configmaps", "pods", "events"], verbs: ["get", "list", "watch", "create", "update", "patch"] },
        { apiGroups: ["apps"], resources: ["deployments"], verbs: ["get", "list", "watch"] },
        { apiGroups: ["batch"], resources: ["jobs"], verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] },
        { apiGroups: ["coordination.k8s.io"], resources: ["leases"], verbs: ["get", "list", "watch", "create", "update", "patch"] },
      ],
    },
    {
      apiVersion: "rbac.authorization.k8s.io/v1",
      kind: "RoleBinding",
      metadata: { name: registry.controller.serviceAccountName, namespace: registry.controller.namespace, labels },
      subjects: [{ kind: "ServiceAccount", name: registry.controller.serviceAccountName, namespace: registry.controller.namespace }],
      roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: registry.controller.serviceAccountName },
    },
    {
      apiVersion: "rbac.authorization.k8s.io/v1",
      kind: "ClusterRole",
      metadata: { name: registry.controller.serviceAccountName, labels },
      rules: [
        { apiGroups: [""], resources: ["pods", "pods/log", "events"], verbs: ["get", "list", "watch"] },
        { apiGroups: [""], resources: ["configmaps"], verbs: ["get", "list", "watch", "create", "update", "patch"] },
        { apiGroups: [""], resources: ["pods/exec"], verbs: ["create"] },
        { apiGroups: ["batch"], resources: ["jobs"], verbs: ["get", "list", "watch", "create", "delete"] },
        { apiGroups: ["apps"], resources: ["deployments", "statefulsets"], verbs: ["get", "list", "watch"] },
        { apiGroups: ["tekton.dev"], resources: ["pipelineruns"], verbs: ["get", "list", "watch", "create", "patch", "delete"] },
        { apiGroups: ["tekton.dev"], resources: ["pipelines"], verbs: ["get", "list", "watch", "create", "update", "patch"] },
        { apiGroups: ["tekton.dev"], resources: ["taskruns"], verbs: ["get", "list", "watch"] },
        { apiGroups: ["argoproj.io"], resources: ["applications"], verbs: ["get", "list", "watch", "patch"] },
      ],
    },
    {
      apiVersion: "rbac.authorization.k8s.io/v1",
      kind: "ClusterRoleBinding",
      metadata: { name: registry.controller.serviceAccountName, labels },
      subjects: [{ kind: "ServiceAccount", name: registry.controller.serviceAccountName, namespace: registry.controller.namespace }],
      roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "ClusterRole", name: registry.controller.serviceAccountName },
    },
    {
      apiVersion: "v1",
      kind: "ConfigMap",
      metadata: { name: registry.controller.configMapName, namespace: registry.controller.namespace, labels },
      data: controllerConfigData,
    },
    {
      apiVersion: "v1",
      kind: "ConfigMap",
      metadata: { name: registry.controller.stateConfigMapName, namespace: registry.controller.namespace, labels },
      data: {
        _createdAt: new Date().toISOString(),
        _specRef: SPEC_REF,
        _registrySha256: registry.rawSha256,
        _controllerConfigSha256: controllerConfigSha,
      },
    },
    {
      apiVersion: "coordination.k8s.io/v1",
      kind: "Lease",
      metadata: { name: registry.controller.leaseName, namespace: registry.controller.namespace, labels },
      spec: { holderIdentity: "unidesk-cicd-branch-follower", leaseDurationSeconds: registry.controller.loop.leaseDurationSeconds },
    },
    {
      apiVersion: "apps/v1",
      kind: "Deployment",
      metadata: { name: registry.controller.deploymentName, namespace: registry.controller.namespace, labels },
      spec: {
        replicas: 1,
        selector: { matchLabels: selector },
        template: {
          metadata: {
            labels: selector,
            annotations: {
              "unidesk.pikapython.com/spec-ref": SPEC_REF,
              "unidesk.pikapython.com/registry-sha256": registry.rawSha256,
              "unidesk.pikapython.com/controller-config-sha256": controllerConfigSha,
              "unidesk.pikapython.com/host-worktree-authority": "false",
            },
          },
          spec: {
            serviceAccountName: registry.controller.serviceAccountName,
            terminationGracePeriodSeconds: registry.controller.loop.terminationGracePeriodSeconds,
            volumes: [
              { name: "registry", configMap: { name: registry.controller.configMapName, defaultMode: 0o755 } },
              { name: "git-mirror-cache", persistentVolumeClaim: { claimName: registry.controller.source.gitMirrorCachePvcName } },
              { name: "git-ssh", secret: { secretName: registry.controller.source.githubSsh.secretName, defaultMode: 0o400 } },
              { name: "work", emptyDir: {} },
            ],
            containers: [
              {
                name: "controller",
                image: registry.controller.image,
                imagePullPolicy: "IfNotPresent",
                command: ["/bin/sh", "/etc/unidesk-cicd-branch-follower/controller-loop.sh"],
                env: [
                  { name: "UNIDESK_CICD_BRANCH_FOLLOWER_INTERVAL_SECONDS", value: String(registry.controller.loop.intervalSeconds) },
                  { name: "UNIDESK_CICD_BRANCH_FOLLOWER_TIMEOUT_SECONDS", value: String(registry.controller.loop.reconcileTimeoutSeconds) },
                  { name: "UNIDESK_CONTROLLER_GIT_MIRROR_READ_URL", value: registry.controller.source.gitMirrorReadUrl },
                  { name: "UNIDESK_CONTROLLER_SOURCE_BRANCH", value: registry.controller.source.branch },
                  { name: "UNIDESK_CONTROLLER_SOURCE_REPOSITORY", value: registry.controller.source.repository },
                  { name: "UNIDESK_CONTROLLER_SOURCE_SNAPSHOT_PREFIX", value: registry.controller.source.sourceSnapshot.stageRefPrefix.replaceAll("{branch}", registry.controller.source.branch) },
                  { name: "UNIDESK_CONTROLLER_GITHUB_SSH_PRIVATE_KEY", value: `/git-ssh/${registry.controller.source.githubSsh.privateKeySecretKey}` },
                  { name: "UNIDESK_CONTROLLER_GITHUB_PROXY_HOST", value: registry.controller.source.githubSsh.proxyHost },
                  { name: "UNIDESK_CONTROLLER_GITHUB_PROXY_PORT", value: String(registry.controller.source.githubSsh.proxyPort) },
                ],
                volumeMounts: [
                  { name: "registry", mountPath: "/etc/unidesk-cicd-branch-follower", readOnly: true },
                  { name: "git-mirror-cache", mountPath: "/cache" },
                  { name: "git-ssh", mountPath: "/git-ssh", readOnly: true },
                  { name: "work", mountPath: "/work" },
                ],
              },
            ],
          },
        },
      },
    },
  ];
}

function nativeCicdScript(name: string): string {
  return readFileSync(rootPath("scripts/native/cicd", name), "utf8");
}

function nativeHwlabScript(name: string): string {
  return readFileSync(rootPath("scripts/native/hwlab", name), "utf8");
}

function sha256(value: string): string {
  return createHash("sha256").update(value).digest("hex");
}
