import { describe, expect, test } from "bun:test";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join, relative } from "node:path";
import type { UniDeskConfig } from "./config";
import { projectPikaoaAttachmentsRepositoryPrerequisite, readPikaoaAttachmentsBackupSpec, renderPikaoaAttachmentsBackupManifest, renderPikaoaAttachmentsInitJob, renderPikaoaAttachmentsRestoreSmokeJob, runPikaoaAttachmentsBackupCommand } from "./pikaoa-attachments-backup";
import { runSecretsCommand } from "./secrets";

describe("PikaOA attachments backup", () => {
  test("owning YAML renders separate read-only backup and maintenance CronJobs", () => {
    const spec = readPikaoaAttachmentsBackupSpec("config/pikaoa.yaml");
    const manifest = renderPikaoaAttachmentsBackupManifest(spec);

    expect(spec.schedule).toBe("17 4 * * *");
    expect(spec.source.excludes).toContain(".tmp");
    expect(spec.destination).toMatchObject({
      type: "local-directory",
      route: "NC01",
      path: "/var/backups/unidesk/pikaoa/attachments-restic",
      mountPath: "/repository",
    });
    expect(manifest).toContain("name: pikaoa-attachments-backup");
    expect(manifest).toContain("name: pikaoa-attachments-restic-check");
    expect(manifest).toContain("name: pikaoa-attachments-restic-forget-prune");
    expect(manifest).toContain("readOnly: true");
    expect(manifest).toContain("hostPath:");
    expect(manifest).toContain("path: /var/backups/unidesk/pikaoa/attachments-restic");
    expect(manifest).toContain("type: DirectoryOrCreate");
    expect(manifest).toContain("mountPath: /repository");
    expect(manifest).toContain("--exclude=.tmp");
    expect(manifest).toContain("forget --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --prune");
    expect(manifest).not.toContain("RESTIC_PASSWORD=");
    expect(manifest).not.toContain("RESTIC_SFTP_COMMAND");
    expect(manifest).not.toContain("SFTP_PRIVATE_KEY");
    expect(manifest.split("---\n").map((document) => Bun.YAML.parse(document))).toHaveLength(4);
  });

  test("local directory is created by Kubernetes while missing SFTP directories remain blocking", () => {
    expect(projectPikaoaAttachmentsRepositoryPrerequisite("local-directory", false)).toEqual({
      exists: false,
      disposition: "will-create",
      blocking: false,
    });
    expect(projectPikaoaAttachmentsRepositoryPrerequisite("sftp", false)).toEqual({
      exists: false,
      disposition: "waiting",
      blocking: true,
    });
  });

  test("missing RESTIC_PASSWORD is pending generation instead of blocking secrets plan", async () => {
    const fixtureRoot = mkdtempSync(join(process.cwd(), ".pikaoa-attachments-secret-plan-"));
    try {
      const sourceConfig = Bun.YAML.parse(readFileSync("config/secrets-distribution.yaml", "utf8")) as {
        version: unknown;
        kind: unknown;
        metadata: unknown;
        sources: { files: Array<Record<string, unknown>> };
        targets: Array<Record<string, unknown>>;
        kubernetesSecrets: Array<Record<string, unknown>>;
      };
      const source = sourceConfig.sources.files.find((item: Record<string, unknown>) => item.sourceRef === "platform-infra/pikaoa-attachments-backup.env");
      const target = sourceConfig.targets.find((item: Record<string, unknown>) => item.id === "pikaoa-nc01");
      const secret = sourceConfig.kubernetesSecrets.find((item: Record<string, unknown>) => item.name === "pikaoa-attachments-backup");
      const fixtureConfig = {
        version: sourceConfig.version,
        kind: sourceConfig.kind,
        metadata: sourceConfig.metadata,
        sources: { root: join(fixtureRoot, "secrets"), files: [source], externalFiles: [] },
        targets: [target],
        kubernetesSecrets: [secret],
      };
      const fixturePath = join(fixtureRoot, "secrets-distribution.yaml");
      writeFileSync(fixturePath, Bun.YAML.stringify(fixtureConfig));

      const planned = await runSecretsCommand({} as UniDeskConfig, ["plan", "--config", relative(process.cwd(), fixturePath), "--scope", "pikaoa", "--target", "pikaoa-nc01"]);
      const localSources = planned.localSources as { entries: Array<Record<string, unknown>> };
      const desiredSecrets = planned.desiredSecrets as Array<Record<string, unknown>>;

      expect(planned.ok).toBeTrue();
      expect(localSources.entries[0]).toMatchObject({
        action: "create",
        generatedKeys: ["RESTIC_PASSWORD"],
        unmaterializedGeneratedKeys: ["RESTIC_PASSWORD"],
        missingKeys: [],
        valuesPrinted: false,
      });
      expect(desiredSecrets[0]).toMatchObject({
        missingKeys: [],
        pendingGeneratedKeys: ["RESTIC_PASSWORD"],
        valuesPrinted: false,
      });
    } finally {
      rmSync(fixtureRoot, { recursive: true, force: true });
    }
  });

  test("restore smoke only uses a temporary emptyDir", () => {
    const spec = readPikaoaAttachmentsBackupSpec("config/pikaoa.yaml");
    const manifest = renderPikaoaAttachmentsRestoreSmokeJob(spec, "pikaoa-attachments-restore-smoke-test");

    expect(manifest).toContain("--target /restore");
    expect(manifest).toContain("emptyDir:");
    expect(manifest).toContain("mountPath: /repository");
    expect(manifest).not.toContain(`claimName: ${spec.source.claimName}`);
    expect(manifest).not.toContain(`mountPath: ${spec.source.mountPath}`);
    expect(Bun.YAML.parse(manifest)).toBeTruthy();
  });

  test("repository init is explicit, idempotent and never mounts the production PVC", async () => {
    const spec = readPikaoaAttachmentsBackupSpec("config/pikaoa.yaml");
    const manifest = renderPikaoaAttachmentsInitJob(spec, "pikaoa-attachments-restic-init-test");

    expect(manifest).toContain('cat config 2>&1');
    expect(manifest).toContain('if [ "$cat_status" -eq 0 ]');
    expect(manifest).toContain("result=already-initialized mutation=false");
    expect(manifest).toContain('if [ "$cat_status" -ne 10 ]');
    expect(manifest).toContain("restic init");
    expect(manifest).toContain("mountPath: /repository");
    expect(manifest).not.toContain("persistentVolumeClaim:");
    expect(manifest).not.toContain(`claimName: ${spec.source.claimName}`);
    expect(manifest).not.toContain(`mountPath: ${spec.source.mountPath}`);
    expect(Bun.YAML.parse(manifest)).toBeTruthy();

    await expect(runPikaoaAttachmentsBackupCommand({} as UniDeskConfig, ["init"])).rejects.toMatchObject({ code: "confirmation-required" });
  });
});
