import { describe, expect, it } from "vitest";
import { unsafeOpaqueId, type WorkspaceId } from "@awp/contracts";
import {
  KubernetesApiTransport,
  KubernetesWorkspaceProvider,
  WorkspaceProfileRegistry,
  buildWorkspaceResources,
  resolveExecutionProfile,
  type KubernetesObject,
  type KubernetesTransport,
  type WorkspaceExecutionProfile,
} from "../../../packages/providers/workspace-kubernetes/src/index.js";
import { providerContext } from "../../security/execution/provider-context.js";

class MemoryKubernetes implements KubernetesTransport {
  readonly resources = new Map<string, KubernetesObject>();
  private revision = 0;

  async apply(resource: KubernetesObject): Promise<KubernetesObject> {
    this.revision += 1;
    const key = resourceKey(
      resource.apiVersion,
      resource.kind,
      resource.metadata.namespace,
      resource.metadata.name,
    );
    const previous = this.resources.get(key);
    const applied: KubernetesObject = {
      ...resource,
      metadata: {
        ...resource.metadata,
        uid: previous?.metadata.uid ?? `uid-${resource.kind}-${resource.metadata.name}`,
        resourceVersion: String(this.revision),
      },
      ...(resource.kind === "Pod" ? { status: { phase: "Running" } } : {}),
    };
    this.resources.set(key, applied);
    return applied;
  }

  async get(apiVersion: string, kind: string, namespace: string | undefined, name: string) {
    return this.resources.get(resourceKey(apiVersion, kind, namespace, name));
  }

  async annotate(
    apiVersion: string,
    kind: string,
    namespace: string | undefined,
    name: string,
    annotations: Readonly<Record<string, string>>,
  ) {
    const key = resourceKey(apiVersion, kind, namespace, name);
    const previous = this.resources.get(key);
    if (!previous) throw new Error(`missing ${kind}/${name}`);
    this.revision += 1;
    const annotated: KubernetesObject = {
      ...previous,
      metadata: {
        ...previous.metadata,
        annotations: { ...previous.metadata.annotations, ...annotations },
        resourceVersion: String(this.revision),
      },
    };
    this.resources.set(key, annotated);
    return annotated;
  }

  async delete(apiVersion: string, kind: string, namespace: string | undefined, name: string) {
    this.resources.delete(resourceKey(apiVersion, kind, namespace, name));
  }
}

const profile: WorkspaceExecutionProfile = {
  key: "gvisor-typescript",
  namespace: "awp-workspaces",
  image: "node:22-bookworm",
  runtimeClassName: "gvisor",
  persistent: true,
  storageSize: "5Gi",
  requests: { cpu: "250m", memory: "512Mi" },
  limits: { cpu: "2", memory: "4Gi" },
  allowedEgressCidrs: ["203.0.113.10/32"],
  allowDns: true,
  secretProjections: [
    { secretName: "attempt-model-token", mountPath: "/run/awp/model", purpose: "model-inference" },
  ],
  provenance: { image: "project:.devcontainer/devcontainer.json#image" },
};

describe("Kubernetes WorkspaceProvider", () => {
  it("builds the required isolated Pod/PVC/ServiceAccount/NetworkPolicy/RuntimeClass mechanics", () => {
    const workspaceId = unsafeOpaqueId<WorkspaceId>("workspace-alpha");
    const resources = buildWorkspaceResources(workspaceId, profile);

    expect(resources.persistentVolumeClaim?.kind).toBe("PersistentVolumeClaim");
    expect(resources.serviceAccount.kind).toBe("ServiceAccount");
    expect(resources.serviceAccount.automountServiceAccountToken).toBe(false);
    expect(resources.networkPolicy.kind).toBe("NetworkPolicy");
    expect(resources.pod.kind).toBe("Pod");
    expect(resources.pod.spec?.runtimeClassName).toBe("gvisor");
    expect(resources.pod.spec?.automountServiceAccountToken).toBe(false);

    const containers = resources.pod.spec?.containers as readonly Record<string, unknown>[];
    expect(containers[0]?.securityContext).toMatchObject({
      allowPrivilegeEscalation: false,
      privileged: false,
      readOnlyRootFilesystem: true,
    });
    expect(resources.networkPolicy.spec?.policyTypes).toEqual(["Ingress", "Egress"]);
  });

  it("uses server-side apply with scoped bearer authentication for Kubernetes resource mechanics", async () => {
    const calls: Array<{
      url: string;
      method: string;
      headers: Readonly<Record<string, string>>;
      body?: string;
    }> = [];
    const resource: KubernetesObject = {
      apiVersion: "v1",
      kind: "ServiceAccount",
      metadata: { name: "awp-test", namespace: "awp-workspaces" },
      automountServiceAccountToken: false,
    };
    const transport = new KubernetesApiTransport({
      apiBase: "https://kubernetes.default.svc",
      bearerToken: "attempt-scoped-test-token",
      fetcher: async (url, init) => {
        calls.push({ url, ...init });
        return {
          ok: true,
          status: 200,
          statusText: "OK",
          async json() {
            return resource;
          },
          async text() {
            return JSON.stringify(resource);
          },
        };
      },
    });

    await expect(transport.apply(resource)).resolves.toEqual(resource);
    expect(calls).toHaveLength(1);
    expect(calls[0]?.method).toBe("PATCH");
    expect(calls[0]?.url).toContain(
      "/api/v1/namespaces/awp-workspaces/serviceaccounts/awp-test?fieldManager=awp-workspace-provider&force=true",
    );
    expect(calls[0]?.headers.Authorization).toBe("Bearer attempt-scoped-test-token");
    expect(calls[0]?.headers["Content-Type"]).toBe("application/apply-patch+yaml");
  });

  it("retains WIP across pod replacement and exposes a stable PVC checkpoint identity", async () => {
    const kubernetes = new MemoryKubernetes();
    const provider = new KubernetesWorkspaceProvider(
      kubernetes,
      new WorkspaceProfileRegistry([profile]),
    );
    const workspaceId = unsafeOpaqueId<WorkspaceId>("workspace-recovery");

    await provider.create(providerContext(), workspaceId, profile.key);
    const contentDigest = "a".repeat(40);
    await provider.attestCheckpoint(
      providerContext(),
      workspaceId,
      profile.key,
      contentDigest,
      "2026-08-23T02:20:00.000Z",
    );
    const before = await provider.checkpoint(workspaceId, profile.key);
    const firstPod = [...kubernetes.resources.values()].find((resource) => resource.kind === "Pod");
    expect(firstPod).toBeDefined();

    await provider.replaceCompute(providerContext(), workspaceId, profile.key);
    const after = await provider.checkpoint(workspaceId, profile.key);

    expect(after.pvcUid).toBe(before.pvcUid);
    expect(before.digest).toBe(contentDigest);
    expect(after.digest).toBe(contentDigest);
    expect(
      [...kubernetes.resources.values()].filter((resource) => resource.kind === "Pod"),
    ).toHaveLength(1);
  });

  it("separates disposable compute cleanup from destructive checkpoint cleanup", async () => {
    const kubernetes = new MemoryKubernetes();
    const provider = new KubernetesWorkspaceProvider(
      kubernetes,
      new WorkspaceProfileRegistry([profile]),
    );
    const workspaceId = unsafeOpaqueId<WorkspaceId>("workspace-cleanup");

    const created = await provider.create(providerContext(), workspaceId, profile.key);
    const contentDigest = "b".repeat(40);
    await provider.attestCheckpoint(
      providerContext(),
      workspaceId,
      profile.key,
      contentDigest,
      "2026-08-23T02:21:00.000Z",
    );
    const checkpoint = await provider.checkpoint(workspaceId, profile.key);
    const released = await provider.destroy(providerContext(), workspaceId);

    expect(released.value.state).toBe("released");
    expect(released.value.details.checkpointRetained).toBe(true);
    expect([...kubernetes.resources.values()].some((resource) => resource.kind === "Pod")).toBe(
      false,
    );
    expect(
      [...kubernetes.resources.values()].some(
        (resource) => resource.kind === "PersistentVolumeClaim",
      ),
    ).toBe(true);

    await expect(
      provider.cleanupCheckpoint(
        providerContext(),
        workspaceId,
        profile.key,
        checkpoint.digest,
        false,
      ),
    ).rejects.toThrow("collected=true");
    await expect(
      provider.cleanupCheckpoint(providerContext(), workspaceId, profile.key, "wrong", true),
    ).rejects.toThrow("digest changed");
    await provider.cleanupCheckpoint(
      providerContext(),
      workspaceId,
      profile.key,
      checkpoint.digest,
      true,
    );
    expect(
      [...kubernetes.resources.values()].some(
        (resource) => resource.kind === "PersistentVolumeClaim",
      ),
    ).toBe(false);
    expect(created.references).toHaveLength(1);
  });

  it("refuses checkpoint collection when the PVC has no content attestation", async () => {
    const kubernetes = new MemoryKubernetes();
    const provider = new KubernetesWorkspaceProvider(
      kubernetes,
      new WorkspaceProfileRegistry([profile]),
    );
    const workspaceId = unsafeOpaqueId<WorkspaceId>("workspace-unattested");
    await provider.create(providerContext(), workspaceId, profile.key);
    await expect(provider.checkpoint(workspaceId, profile.key)).rejects.toThrow(
      "no attested content checkpoint digest",
    );
  });

  it("resolves Dev Container declarations before Docker and language manifests", () => {
    const resolved = resolveExecutionProfile(
      {
        files: {
          ".devcontainer/devcontainer.json":
            '{ // comment\n "image": "mcr.microsoft.com/devcontainers/typescript-node:22", }',
          Dockerfile: "FROM node:22",
          "package.json": "{}",
        },
      },
      profile,
    );
    expect(resolved.source).toBe("devcontainer");
    expect(resolved.sourcePath).toBe(".devcontainer/devcontainer.json");
    expect(resolved.profile.image).toBe("mcr.microsoft.com/devcontainers/typescript-node:22");
    expect(resolved.requiresImageBuild).toBe(false);
  });

  it("does not silently claim a fallback image is a devcontainer build result", () => {
    const resolved = resolveExecutionProfile(
      { files: { ".devcontainer/devcontainer.json": '{"build":{"dockerfile":"Dockerfile"}}' } },
      profile,
    );
    expect(resolved.source).toBe("devcontainer");
    expect(resolved.requiresImageBuild).toBe(true);
  });
});

function resourceKey(
  apiVersion: string,
  kind: string,
  namespace: string | undefined,
  name: string,
) {
  return `${apiVersion}:${kind}:${namespace ?? "_"}:${name}`;
}
