import assert from "node:assert/strict";

import {
  ciToolsRunnerImage,
  configObject,
  defaultOutDir,
  label,
  optionalConfigString,
  requiredConfigPositiveInteger,
  requiredConfigString,
  runtimeLaneMirrorSpecs
} from "./core.mjs";
import { renderTemplate, shellSingleQuote } from "./tekton-scripts.mjs";

function registryManifest() {
  return {
    apiVersion: "v1",
    kind: "List",
    items: [
      { apiVersion: "v1", kind: "Namespace", metadata: { name: "hwlab-ci" } },
      {
        apiVersion: "apps/v1",
        kind: "Deployment",
        metadata: { name: "hwlab-registry", namespace: "hwlab-ci", labels: { "app.kubernetes.io/name": "hwlab-registry" } },
        spec: {
          replicas: 1,
          selector: { matchLabels: { "app.kubernetes.io/name": "hwlab-registry" } },
          template: {
            metadata: { labels: { "app.kubernetes.io/name": "hwlab-registry" } },
            spec: {
              hostNetwork: true,
              dnsPolicy: "ClusterFirstWithHostNet",
              containers: [{
                name: "registry",
                image: "registry:2.8.3",
                imagePullPolicy: "IfNotPresent",
                ports: [{ name: "registry", containerPort: 5000, hostPort: 5000 }],
                env: [{ name: "REGISTRY_STORAGE_DELETE_ENABLED", value: "true" }],
                volumeMounts: [{ name: "storage", mountPath: "/var/lib/registry" }],
                readinessProbe: { httpGet: { path: "/v2/", port: "registry" } },
                livenessProbe: { httpGet: { path: "/v2/", port: "registry" } }
              }],
              volumes: [{ name: "storage", hostPath: { path: "/var/lib/hwlab/registry", type: "DirectoryOrCreate" } }]
            }
          }
        }
      },
      {
        apiVersion: "v1",
        kind: "Service",
        metadata: { name: "hwlab-registry", namespace: "hwlab-ci", labels: { "app.kubernetes.io/name": "hwlab-registry" } },
        spec: { selector: { "app.kubernetes.io/name": "hwlab-registry" }, ports: [{ name: "registry", port: 5000, targetPort: "registry" }] }
      }
    ]
  };
}

function uniqueStrings(values) {
  return Array.from(new Set(values.filter((value) => typeof value === "string" && value.length > 0)));
}

function renderGitMirrorSshPrelude({ direct, proxySummary, proxyCommand, proxyUrl, proxyNoProxy }) {
  if (direct) {
    return renderTemplate("git-mirror-ssh-direct.sh", {
      "__HWLAB_PROXY_SUMMARY_SHELL__": shellSingleQuote(proxySummary)
    });
  }
  return renderTemplate("git-mirror-ssh-proxied.sh", {
    "// __HWLAB_GIT_MIRROR_PROXY_CONNECT_MJS__": renderTemplate("git-mirror-proxy-connect.mjs"),
    "__HWLAB_PROXY_SUMMARY_SHELL__": shellSingleQuote(proxySummary),
    "__HWLAB_PROXY_COMMAND_SHELL__": shellSingleQuote(proxyCommand),
    "__HWLAB_PROXY_URL_SHELL__": shellSingleQuote(proxyUrl),
    "__HWLAB_PROXY_NO_PROXY_SHELL__": shellSingleQuote(proxyNoProxy)
  });
}

function gitMirrorConfigScripts({
  fetchConfigCommands,
  gitMirrorSshPrelude,
  gitopsBranchArgs,
  gitopsBranchesJson,
  mirrorBranchesJson,
  requiredRefArgs,
  sourceBranchArgs,
  sourceBranchesJson,
  sourceSnapshotRefPrefix
}) {
  const gitopsBranchesJsonShell = shellSingleQuote(gitopsBranchesJson);
  return {
    "sync.sh": renderTemplate("git-mirror-sync.sh", {
      "# __HWLAB_GIT_MIRROR_SSH_PRELUDE__": gitMirrorSshPrelude,
      "__HWLAB_SOURCE_SNAPSHOT_REF_PREFIX_SHELL__": shellSingleQuote(sourceSnapshotRefPrefix),
      "# __HWLAB_FETCH_CONFIG_COMMANDS__": fetchConfigCommands,
      "__HWLAB_REQUIRED_REF_ARGS__": requiredRefArgs,
      "__HWLAB_SOURCE_BRANCH_ARGS__": sourceBranchArgs,
      "__HWLAB_GITOPS_BRANCH_ARGS__": gitopsBranchArgs,
      "__HWLAB_MIRROR_BRANCHES_JSON_SHELL__": shellSingleQuote(mirrorBranchesJson),
      "__HWLAB_SOURCE_BRANCHES_JSON_SHELL__": shellSingleQuote(sourceBranchesJson),
      "__HWLAB_GITOPS_BRANCHES_JSON_SHELL__": gitopsBranchesJsonShell
    }),
    "install-hooks.sh": renderTemplate("git-mirror-install-hooks.sh", {
      "__HWLAB_GITOPS_BRANCHES_JSON_SHELL__": gitopsBranchesJsonShell
    }),
    "flush.sh": renderTemplate("git-mirror-flush.sh", {
      "# __HWLAB_GIT_MIRROR_SSH_PRELUDE__": gitMirrorSshPrelude,
      "__HWLAB_GITOPS_BRANCH_ARGS__": gitopsBranchArgs,
      "__HWLAB_GITOPS_BRANCHES_JSON_SHELL__": gitopsBranchesJsonShell
    }),
    "http.mjs": renderTemplate("git-mirror-http.mjs")
  };
}

function devopsInfraGitMirrorManifest(args, deploy) {
  const mirrorSpecs = runtimeLaneMirrorSpecs(deploy, { defaultNodeId: args.nodeId, defaultGitopsRoot: defaultOutDir });
  assert.ok(mirrorSpecs.length > 0, "runtime lane git mirror requires at least one deploy.lanes entry");
  const sourceBranches = uniqueStrings(mirrorSpecs.map((spec) => spec.sourceBranch));
  const gitopsBranches = uniqueStrings(mirrorSpecs.map((spec) => spec.gitopsBranch));
  const mirrorBranches = uniqueStrings([...sourceBranches, ...gitopsBranches]);
  const fetchConfigCommands = mirrorBranches
    .map((branch) => `git -C "$repo_path" config --add remote.origin.fetch '+refs/heads/${branch}:refs/mirror-stage/heads/${branch}'`)
    .join("\n");
  const requiredRefArgs = mirrorBranches.map((branch) => shellSingleQuote(`refs/mirror-stage/heads/${branch}`)).join(" ");
  const sourceBranchArgs = sourceBranches.map((branch) => shellSingleQuote(branch)).join(" ");
  const gitopsBranchArgs = gitopsBranches.map((branch) => shellSingleQuote(branch)).join(" ");
  const mirrorBranchesJson = JSON.stringify(mirrorBranches);
  const sourceBranchesJson = JSON.stringify(sourceBranches);
  const gitopsBranchesJson = JSON.stringify(gitopsBranches);
  const sourceSnapshotRefPrefix = "refs/unidesk/snapshots/hwlab-node-runtime";
  const laneConfig = configObject(configObject(deploy.lanes, "deploy.lanes")[args.lane], `deploy.lanes.${args.lane}`);
  const gitMirrorConfig = configObject(laneConfig.gitMirror, `deploy.lanes.${args.lane}.gitMirror`);
  const gitMirrorProxy = configObject(gitMirrorConfig.egressProxy, `deploy.lanes.${args.lane}.gitMirror.egressProxy`);
  const gitMirrorProxyMode = requiredConfigString(gitMirrorProxy, "mode", `deploy.lanes.${args.lane}.gitMirror.egressProxy`);
  assert.ok(gitMirrorProxyMode === "node-global" || gitMirrorProxyMode === "host-proxy-client" || gitMirrorProxyMode === "direct", `deploy.lanes.${args.lane}.gitMirror.egressProxy.mode must be node-global, host-proxy-client, or direct`);
  const useDirectGitMirrorProxy = gitMirrorProxyMode === "direct";
  const useHostGitMirrorProxy = gitMirrorProxyMode === "host-proxy-client";
  const gitMirrorNamespace = requiredConfigString(gitMirrorConfig, "namespace", `deploy.lanes.${args.lane}.gitMirror`);
  const gitMirrorReadName = requiredConfigString(gitMirrorConfig, "serviceReadName", `deploy.lanes.${args.lane}.gitMirror`);
  const gitMirrorWriteName = requiredConfigString(gitMirrorConfig, "serviceWriteName", `deploy.lanes.${args.lane}.gitMirror`);
  const gitMirrorCachePvcName = requiredConfigString(gitMirrorConfig, "cachePvcName", `deploy.lanes.${args.lane}.gitMirror`);
  const gitMirrorCachePvcStorage = requiredConfigString(gitMirrorConfig, "cachePvcStorage", `deploy.lanes.${args.lane}.gitMirror`);
  const gitMirrorCacheHostPath = optionalConfigString(gitMirrorConfig, "cacheHostPath", `deploy.lanes.${args.lane}.gitMirror`);
  const gitMirrorServicePort = requiredConfigPositiveInteger(gitMirrorConfig, "servicePort", `deploy.lanes.${args.lane}.gitMirror`);
  const gitMirrorConfigMapName = requiredConfigString(gitMirrorConfig, "syncConfigMapName", `deploy.lanes.${args.lane}.gitMirror`);
  const proxyNamespace = useDirectGitMirrorProxy || useHostGitMirrorProxy ? "" : requiredConfigString(gitMirrorProxy, "namespace", `deploy.lanes.${args.lane}.gitMirror.egressProxy`);
  const proxyServiceName = useDirectGitMirrorProxy || useHostGitMirrorProxy ? "" : requiredConfigString(gitMirrorProxy, "serviceName", `deploy.lanes.${args.lane}.gitMirror.egressProxy`);
  const proxyPort = useDirectGitMirrorProxy ? 0 : requiredConfigPositiveInteger(gitMirrorProxy, "port", `deploy.lanes.${args.lane}.gitMirror.egressProxy`);
  const proxyClientName = useDirectGitMirrorProxy ? "" : requiredConfigString(gitMirrorProxy, "clientName", `deploy.lanes.${args.lane}.gitMirror.egressProxy`);
  const proxyNoProxy = !useDirectGitMirrorProxy && Array.isArray(gitMirrorProxy.noProxy) ? gitMirrorProxy.noProxy.filter((value) => typeof value === "string" && value.length > 0).join(",") : "";
  const proxyHost = useHostGitMirrorProxy
    ? requiredConfigString(gitMirrorProxy, "host", `deploy.lanes.${args.lane}.gitMirror.egressProxy`)
    : `${proxyServiceName}.${proxyNamespace}.svc.cluster.local`;
  const proxyUrl = `http://${proxyHost}:${proxyPort}`;
  const proxySummary = useDirectGitMirrorProxy
    ? "git-mirror-egress-proxy mode=direct required=false transport=ssh source=yaml"
    : `git-mirror-egress-proxy client=${proxyClientName} mode=${gitMirrorProxyMode} required=true host=${proxyHost} port=${proxyPort} ssh=GIT_SSH-wrapper source=yaml`;
  const proxyCommand = useDirectGitMirrorProxy ? "" : `ProxyCommand=node /tmp/hwlab-github-proxy-connect.mjs ${proxyHost} ${proxyPort} %h %p`;
  const gitMirrorSshPrelude = renderGitMirrorSshPrelude({
    direct: useDirectGitMirrorProxy,
    proxySummary,
    proxyCommand,
    proxyUrl,
    proxyNoProxy
  });
  const cacheVolume = gitMirrorCacheHostPath === null
    ? { name: "cache", persistentVolumeClaim: { claimName: gitMirrorCachePvcName } }
    : { name: "cache", hostPath: { path: gitMirrorCacheHostPath, type: "DirectoryOrCreate" } };
  const labels = {
    "app.kubernetes.io/part-of": "hwlab-node-control-plane",
    "hwlab.pikastech.local/node": args.nodeId,
    "hwlab.pikastech.local/lane": args.lane
  };
  const configLabels = { ...labels, "app.kubernetes.io/name": "git-mirror" };
  const readLabels = { ...labels, "app.kubernetes.io/name": gitMirrorReadName, "hwlab.pikastech.local/git-mirror-mode": "read" };
  const writeLabels = { ...labels, "app.kubernetes.io/name": gitMirrorWriteName, "hwlab.pikastech.local/git-mirror-mode": "write" };
  return {
    apiVersion: "v1",
    kind: "List",
    items: [
      { apiVersion: "v1", kind: "Namespace", metadata: { name: gitMirrorNamespace } },
      ...(gitMirrorCacheHostPath === null ? [{
        apiVersion: "v1",
        kind: "PersistentVolumeClaim",
        metadata: { name: gitMirrorCachePvcName, namespace: gitMirrorNamespace, labels: configLabels },
        spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: gitMirrorCachePvcStorage } } }
      }] : []),
      {
        apiVersion: "v1",
        kind: "ConfigMap",
        metadata: { name: gitMirrorConfigMapName, namespace: gitMirrorNamespace, labels: configLabels },
        data: gitMirrorConfigScripts({
          fetchConfigCommands,
          gitMirrorSshPrelude,
          gitopsBranchArgs,
          gitopsBranchesJson,
          mirrorBranchesJson,
          requiredRefArgs,
          sourceBranchArgs,
          sourceBranchesJson,
          sourceSnapshotRefPrefix
        })
      },
      {
        apiVersion: "apps/v1",
        kind: "Deployment",
        metadata: { name: gitMirrorReadName, namespace: gitMirrorNamespace, labels: readLabels },
        spec: {
          replicas: 1,
          selector: { matchLabels: { "app.kubernetes.io/name": gitMirrorReadName } },
          template: {
            metadata: { labels: readLabels },
            spec: {
              containers: [{
                name: "git-mirror",
                image: ciToolsRunnerImage,
                imagePullPolicy: "IfNotPresent",
                command: ["node"],
                args: ["/etc/git-mirror/http.mjs"],
                ports: [{ name: "http", containerPort: 8080 }],
                readinessProbe: { httpGet: { path: "/pikasTech/HWLAB.git/HEAD", port: "http" }, initialDelaySeconds: 5, periodSeconds: 10 },
                livenessProbe: { httpGet: { path: "/", port: "http" }, initialDelaySeconds: 10, periodSeconds: 30 },
                volumeMounts: [
                  { name: "cache", mountPath: "/cache" },
                  { name: "config", mountPath: "/etc/git-mirror", readOnly: true }
                ]
              }],
              volumes: [
                cacheVolume,
                { name: "config", configMap: { name: gitMirrorConfigMapName, defaultMode: 0o555 } }
              ]
            }
          }
        }
      },
      {
        apiVersion: "v1",
        kind: "Service",
        metadata: { name: gitMirrorReadName, namespace: gitMirrorNamespace, labels: readLabels },
        spec: { selector: { "app.kubernetes.io/name": gitMirrorReadName }, ports: [{ name: "http", port: gitMirrorServicePort, targetPort: "http" }] }
      },
      {
        apiVersion: "v1",
        kind: "Service",
        metadata: { name: gitMirrorWriteName, namespace: gitMirrorNamespace, labels: writeLabels },
        spec: { selector: { "app.kubernetes.io/name": gitMirrorReadName }, ports: [{ name: "http", port: gitMirrorServicePort, targetPort: "http" }] }
      }
    ]
  };
}

export {
  devopsInfraGitMirrorManifest,
  registryManifest,
  uniqueStrings
};
