import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { rootPath } from "./config";
import {
  listAgentRunLaneTargetsForNode,
  type AgentRunLaneSpec,
} from "./agentrun-lanes";

const controllerSourcePath = rootPath("scripts", "native", "agentrun", "managed-repository-reconciler.mjs");
const askPassSourcePath = rootPath("scripts", "native", "agentrun", "git-askpass.mjs");
const controllerMountPath = "/etc/agentrun-managed-repository";
const authMountPath = "/var/run/secrets/agentrun-managed-repository-auth";

export interface AgentRunManagedRepositoryDesiredConfig {
  readonly schemaVersion: 1;
  readonly kind: "AgentRunManagedRepositoryReconciler";
  readonly nodeId: string;
  readonly lane: string;
  readonly cacheMountPath: string;
  readonly stateDirectory: string;
  readonly reconcileIntervalMs: number;
  readonly fetchTimeoutMs: number;
  readonly shutdownGraceMs: number;
  readonly maxConcurrentRepositories: number;
  readonly retry: {
    readonly maxAttempts: number;
    readonly initialDelayMs: number;
    readonly maxDelayMs: number;
  };
  readonly freshness: {
    readonly maxAgeMs: number;
  };
  readonly lifecycle: {
    readonly undeclaredRepositoryPolicy: "retain";
    readonly managedRefs: "source-branch-only";
  };
  readonly repositories: readonly {
    readonly key: string;
    readonly repository: string;
    readonly remote: string;
    readonly sourceBranch: string;
    readonly additionalSourceBranches: readonly string[];
    readonly desiredFingerprint: string;
  }[];
  readonly remoteAuth: {
    readonly configRef: string;
    readonly capabilitySha256: string;
    readonly authMode: "github-https-token";
    readonly host: "github.com";
    readonly tokenFile: string;
    readonly askPassFile: string;
    readonly proxy: {
      readonly host: string;
      readonly port: number;
    };
  };
  readonly desiredHash: string;
}

export function agentRunManagedRepositoryDesiredConfig(spec: AgentRunLaneSpec): AgentRunManagedRepositoryDesiredConfig {
  const reconciler = spec.gitMirror.repositoryReconciler;
  const desired = {
    schemaVersion: 1 as const,
    kind: "AgentRunManagedRepositoryReconciler" as const,
    nodeId: spec.nodeId,
    lane: spec.lane,
    cacheMountPath: reconciler.cacheMountPath,
    stateDirectory: reconciler.stateDirectory,
    reconcileIntervalMs: reconciler.reconcileIntervalMs,
    fetchTimeoutMs: reconciler.fetchTimeoutMs,
    shutdownGraceMs: reconciler.shutdownGraceMs,
    maxConcurrentRepositories: reconciler.maxConcurrentRepositories,
    retry: reconciler.retry,
    freshness: reconciler.freshness,
    lifecycle: reconciler.lifecycle,
    repositories: spec.gitMirror.repositories.map((repository) => ({
      key: repository.key,
      repository: repository.repository,
      remote: repository.remote,
      sourceBranch: repository.sourceBranch,
      additionalSourceBranches: repository.additionalSourceBranches,
      desiredFingerprint: sha256([repository.key, repository.repository, repository.remote, repository.sourceBranch, ...repository.additionalSourceBranches].join("\0")),
    })),
    remoteAuth: {
      configRef: reconciler.remoteAuth.configRef,
      capabilitySha256: reconciler.remoteAuth.capabilitySha256,
      authMode: reconciler.remoteAuth.authMode,
      host: reconciler.remoteAuth.host,
      tokenFile: `${authMountPath}/token`,
      askPassFile: `${authMountPath}/git-askpass.mjs`,
      proxy: spec.gitMirror.githubProxy,
    },
  };
  return { ...desired, desiredHash: sha256(JSON.stringify(desired)) };
}

export function agentRunManagedRepositoryRenderHash(spec: AgentRunLaneSpec): string {
  const config = agentRunManagedRepositoryDesiredConfig(spec);
  return sha256([
    JSON.stringify(config),
    JSON.stringify(managedRepositoryManifestIdentity(spec)),
    readFileSync(controllerSourcePath, "utf8"),
    readFileSync(askPassSourcePath, "utf8"),
  ].join("\0"));
}

export function renderAgentRunManagedRepositoryReconcilerFragment(nodeId: string, env: NodeJS.ProcessEnv = process.env): string {
  const targets = listAgentRunLaneTargetsForNode(nodeId, env)
    .filter(({ spec }) => spec.gitMirror.repositoryReconciler.enabled);
  assertUniqueManagedRepositoryOwnership(targets.map(({ spec }) => spec));
  const objects = targets
    .flatMap(({ spec }) => managedRepositoryObjects(spec));
  if (objects.length === 0) return "";
  return `${objects.map((object) => Bun.YAML.stringify(object).trim()).join("\n---\n")}\n`;
}

function assertUniqueManagedRepositoryOwnership(specs: readonly AgentRunLaneSpec[]): void {
  const objectNames = new Set<string>();
  const cacheRepositories = new Map<string, string>();
  for (const spec of specs) {
    const reconciler = spec.gitMirror.repositoryReconciler;
    for (const [kind, name] of [["Deployment", reconciler.deploymentName], ["ConfigMap", reconciler.configMapName], ["ServiceAccount", reconciler.serviceAccountName]]) {
      const identity = `${kind}:${spec.gitMirror.namespace}/${name}`;
      if (objectNames.has(identity)) throw new Error(`AgentRun managed repository renderer has duplicate object ownership: ${identity}`);
      objectNames.add(identity);
    }
    const cacheIdentity = spec.gitMirror.cacheHostPath === null
      ? `pvc:${spec.gitMirror.namespace}/${spec.gitMirror.cachePvc}`
      : `hostPath:${spec.nodeId}/${spec.gitMirror.cacheHostPath}`;
    for (const repository of spec.gitMirror.repositories) {
      const identity = `${cacheIdentity}/${repository.repository}`;
      const owner = cacheRepositories.get(identity);
      if (owner !== undefined) {
        console.warn(JSON.stringify({
          code: "agentrun-managed-repository-shared-cache",
          level: "warning",
          blocking: false,
          mutation: false,
          repository: identity,
          owners: [owner, spec.lane],
          message: `AgentRun managed repository ${identity} is shared by lanes ${owner} and ${spec.lane}`,
        }));
      } else {
        cacheRepositories.set(identity, spec.lane);
      }
    }
  }
}

function managedRepositoryObjects(spec: AgentRunLaneSpec): Record<string, unknown>[] {
  const reconciler = spec.gitMirror.repositoryReconciler;
  const desired = agentRunManagedRepositoryDesiredConfig(spec);
  const desiredJson = JSON.stringify(desired, null, 2);
  const controllerSource = readFileSync(controllerSourcePath, "utf8");
  const askPassSource = readFileSync(askPassSourcePath, "utf8");
  const renderHash = agentRunManagedRepositoryRenderHash(spec);
  const labels = {
    "app.kubernetes.io/name": reconciler.deploymentName,
    "app.kubernetes.io/component": "managed-repository-reconciler",
    "app.kubernetes.io/part-of": "agentrun",
    "app.kubernetes.io/managed-by": "unidesk",
    "agentrun.pikastech.local/lane": spec.lane,
    "agentrun.pikastech.local/node": spec.nodeId,
  };
  const annotations = {
    "unidesk.ai/managed-repository-desired-sha": desired.desiredHash,
    "unidesk.ai/managed-repository-render-sha": renderHash,
    "unidesk.ai/git-fetch-credential-sha": reconciler.remoteAuth.capabilitySha256,
    "unidesk.ai/undeclared-repository-policy": reconciler.lifecycle.undeclaredRepositoryPolicy,
    "unidesk.ai/managed-refs": reconciler.lifecycle.managedRefs,
  };
  const cacheVolume = spec.gitMirror.cacheHostPath === null
    ? { name: "cache", persistentVolumeClaim: { claimName: spec.gitMirror.cachePvc } }
    : { name: "cache", hostPath: { path: spec.gitMirror.cacheHostPath, type: "DirectoryOrCreate" } };
  return [
    {
      apiVersion: "v1",
      kind: "ServiceAccount",
      metadata: {
        name: reconciler.serviceAccountName,
        namespace: spec.gitMirror.namespace,
        labels,
      },
      automountServiceAccountToken: false,
    },
    {
      apiVersion: "v1",
      kind: "ConfigMap",
      metadata: {
        name: reconciler.configMapName,
        namespace: spec.gitMirror.namespace,
        labels,
        annotations,
      },
      data: {
        "config.json": `${desiredJson}\n`,
        "controller.mjs": controllerSource,
        "git-askpass.mjs": askPassSource,
      },
    },
    {
      apiVersion: "apps/v1",
      kind: "Deployment",
      metadata: {
        name: reconciler.deploymentName,
        namespace: spec.gitMirror.namespace,
        labels,
        annotations,
      },
      spec: {
        replicas: 1,
        strategy: { type: "Recreate" },
        selector: {
          matchLabels: {
            "app.kubernetes.io/name": reconciler.deploymentName,
            "app.kubernetes.io/component": "managed-repository-reconciler",
          },
        },
        template: {
          metadata: {
            labels,
            annotations: {
              "unidesk.ai/managed-repository-desired-sha": desired.desiredHash,
              "unidesk.ai/managed-repository-render-sha": renderHash,
            },
          },
          spec: {
            serviceAccountName: reconciler.serviceAccountName,
            automountServiceAccountToken: false,
            terminationGracePeriodSeconds: Math.ceil(reconciler.shutdownGraceMs / 1000),
            hostNetwork: reconciler.hostNetwork,
            dnsPolicy: reconciler.dnsPolicy,
            containers: [{
              name: "managed-repository-reconciler",
              image: spec.gitMirror.toolsImage,
              imagePullPolicy: reconciler.imagePullPolicy,
              command: [
                "tini",
                "--",
                "node",
                `${controllerMountPath}/controller.mjs`,
                "--config",
                `${controllerMountPath}/config.json`,
              ],
              readinessProbe: {
                exec: {
                  command: [
                    "node",
                    `${controllerMountPath}/controller.mjs`,
                    "--health",
                    "--config",
                    `${controllerMountPath}/config.json`,
                  ],
                },
                initialDelaySeconds: reconciler.readiness.initialDelaySeconds,
                periodSeconds: reconciler.readiness.periodSeconds,
                timeoutSeconds: reconciler.readiness.timeoutSeconds,
                failureThreshold: reconciler.readiness.failureThreshold,
              },
              resources: reconciler.resources,
              volumeMounts: [
                { name: "config", mountPath: controllerMountPath, readOnly: true },
                { name: "cache", mountPath: reconciler.cacheMountPath },
                { name: "git-auth", mountPath: authMountPath, readOnly: true },
              ],
            }],
            volumes: [
              { name: "config", configMap: { name: reconciler.configMapName, defaultMode: 0o555 } },
              cacheVolume,
              {
                name: "git-auth",
                projected: {
                  sources: [
                    {
                      configMap: {
                        name: reconciler.configMapName,
                        items: [{ key: "git-askpass.mjs", path: "git-askpass.mjs", mode: 0o555 }],
                      },
                    },
                    {
                      secret: {
                        name: reconciler.remoteAuth.secretRef.name,
                        items: [{ key: reconciler.remoteAuth.secretRef.key, path: "token", mode: 0o400 }],
                      },
                    },
                  ],
                },
              },
            ],
          },
        },
      },
    },
  ];
}

function managedRepositoryManifestIdentity(spec: AgentRunLaneSpec): Record<string, unknown> {
  const reconciler = spec.gitMirror.repositoryReconciler;
  return {
    namespace: spec.gitMirror.namespace,
    deploymentName: reconciler.deploymentName,
    configMapName: reconciler.configMapName,
    serviceAccountName: reconciler.serviceAccountName,
    hostNetwork: reconciler.hostNetwork,
    dnsPolicy: reconciler.dnsPolicy,
    image: spec.gitMirror.toolsImage,
    imagePullPolicy: reconciler.imagePullPolicy,
    cachePvc: spec.gitMirror.cachePvc,
    cacheHostPath: spec.gitMirror.cacheHostPath,
    remoteAuth: {
      configRef: reconciler.remoteAuth.configRef,
      capabilitySha256: reconciler.remoteAuth.capabilitySha256,
      secretRef: reconciler.remoteAuth.secretRef,
    },
    readiness: reconciler.readiness,
    resources: reconciler.resources,
  };
}

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