import { createHash } from "node:crypto";
import type { ProviderObservation, ReconcileRequest, WorkspaceProvider } from "@awp/application";
import {
  unsafeOpaqueId,
  type ProviderDescriptor,
  type ProviderId,
  type ProviderOperationContext,
  type ProviderReference,
  type ProviderResult,
  type WorkspaceId,
} from "@awp/contracts";
import type { KubernetesObject, KubernetesTransport, WorkspaceResourceSet } from "./kubernetes.js";
import type { WorkspaceExecutionProfile, WorkspaceProfileRegistry } from "./profile.js";
import { buildWorkspaceResources, resourceBaseName } from "./resources.js";

const PROVIDER_ID = unsafeOpaqueId<ProviderId>("kubernetes-workspace");

export interface WorkspaceCheckpoint {
  readonly workspaceId: WorkspaceId;
  readonly pvcName: string;
  readonly pvcUid: string;
  readonly resourceVersion: string;
  readonly digest: string;
  readonly observedAt: string;
}

export class KubernetesWorkspaceProvider implements WorkspaceProvider {
  constructor(
    private readonly kubernetes: KubernetesTransport,
    private readonly profiles: WorkspaceProfileRegistry,
    private readonly now: () => string = () => new Date().toISOString(),
  ) {}

  async describe(): Promise<ProviderDescriptor> {
    return {
      providerId: PROVIDER_ID,
      kind: "workspace",
      adapterVersion: "i0-v1",
      capabilities: [
        "workspace.pod",
        "workspace.pvc",
        "workspace.service-account",
        "workspace.network-policy",
        "workspace.runtime-class",
        "workspace.checkpoint-retention",
        "workspace.reconcile",
      ],
      authModes: ["kubernetes-service-account"],
      resourceTypes: ["Pod", "PersistentVolumeClaim", "ServiceAccount", "NetworkPolicy", "Service"],
      healthFeatures: ["pod-readback", "pvc-readback", "checkpoint-digest"],
    };
  }

  async create(
    _context: ProviderOperationContext,
    workspaceId: WorkspaceId,
    profileKey: string,
  ): Promise<ProviderResult<ProviderObservation>> {
    const profile = this.profiles.get(profileKey);
    const resources = buildWorkspaceResources(workspaceId, profile);

    await this.kubernetes.apply(resources.serviceAccount);
    await this.kubernetes.apply(resources.networkPolicy);
    if (resources.persistentVolumeClaim)
      await this.kubernetes.apply(resources.persistentVolumeClaim);
    if (resources.service) await this.kubernetes.apply(resources.service);
    const pod = await this.kubernetes.apply(resources.pod);

    return this.workspaceResult(workspaceId, profile, resources, pod, "preparing");
  }

  async destroy(
    _context: ProviderOperationContext,
    workspaceId: WorkspaceId,
  ): Promise<ProviderResult<ProviderObservation>> {
    const match = await this.findWorkspace(workspaceId);
    if (!match) return this.absentResult(workspaceId, "cleaned");

    const { profile, name } = match;
    await this.deleteIfPresent("v1", "Pod", profile.namespace, name);
    await this.deleteIfPresent("v1", "Service", profile.namespace, name);
    await this.deleteIfPresent("networking.k8s.io/v1", "NetworkPolicy", profile.namespace, name);
    await this.deleteIfPresent("v1", "ServiceAccount", profile.namespace, name);

    // Deliberately retain the PVC. Logical run completion must not destroy the
    // only recoverable WIP copy. Cleanup requires an explicit digest handshake.
    const pvc = await this.kubernetes.get("v1", "PersistentVolumeClaim", profile.namespace, name);
    const observedAt = this.now();
    const reference = workspaceReference(workspaceId, profile.namespace, name, observedAt);
    return {
      value: {
        state: "released",
        details: {
          workspaceId,
          computeDeleted: true,
          checkpointRetained: Boolean(pvc),
          ...(pvc ? { checkpointDigest: checkpointDigest(workspaceId, pvc) } : {}),
        },
      },
      references: [reference],
      observedAt,
      reconciliationToken: `${name}:released`,
    };
  }

  async reconcile(request: ReconcileRequest): Promise<ProviderResult<ProviderObservation>> {
    validateReference(request.reference);
    const parsed = parseWorkspaceReference(request.reference);
    const profile = this.profileForNamespace(parsed.namespace);
    if (!profile) return this.absentResult(parsed.workspaceId, "missing");

    const pod = await this.kubernetes.get("v1", "Pod", parsed.namespace, parsed.name);
    const pvc = await this.kubernetes.get(
      "v1",
      "PersistentVolumeClaim",
      parsed.namespace,
      parsed.name,
    );
    const observedAt = this.now();
    return {
      value: {
        state: normalizePodState(pod),
        details: {
          workspaceId: parsed.workspaceId,
          podPresent: Boolean(pod),
          checkpointRetained: Boolean(pvc),
          ...(pvc ? { checkpointDigest: checkpointDigest(parsed.workspaceId, pvc) } : {}),
          runtimeClassName: profile.runtimeClassName ?? "native",
        },
      },
      references: [request.reference],
      observedAt,
      reconciliationToken: `${parsed.name}:${pod?.metadata.resourceVersion ?? "no-pod"}:${pvc?.metadata.resourceVersion ?? "no-pvc"}`,
    };
  }

  async replaceCompute(
    workspaceId: WorkspaceId,
    profileKey: string,
  ): Promise<ProviderResult<ProviderObservation>> {
    const profile = this.profiles.get(profileKey);
    const resources = buildWorkspaceResources(workspaceId, profile);
    const name = resourceBaseName(workspaceId);
    await this.deleteIfPresent("v1", "Pod", profile.namespace, name);
    const pod = await this.kubernetes.apply(resources.pod);
    return this.workspaceResult(workspaceId, profile, resources, pod, "preparing");
  }

  async checkpoint(workspaceId: WorkspaceId, profileKey: string): Promise<WorkspaceCheckpoint> {
    const profile = this.profiles.get(profileKey);
    const name = resourceBaseName(workspaceId);
    const pvc = await this.kubernetes.get("v1", "PersistentVolumeClaim", profile.namespace, name);
    if (!pvc?.metadata.uid || !pvc.metadata.resourceVersion) {
      throw new Error(`Workspace ${workspaceId} has no durable PVC checkpoint identity`);
    }
    return {
      workspaceId,
      pvcName: name,
      pvcUid: pvc.metadata.uid,
      resourceVersion: pvc.metadata.resourceVersion,
      digest: checkpointDigest(workspaceId, pvc),
      observedAt: this.now(),
    };
  }

  async cleanupCheckpoint(
    workspaceId: WorkspaceId,
    profileKey: string,
    expectedDigest: string,
    collected: boolean,
  ): Promise<void> {
    if (!collected) throw new Error("Checkpoint cleanup requires collected=true");
    const profile = this.profiles.get(profileKey);
    const checkpoint = await this.checkpoint(workspaceId, profileKey);
    if (checkpoint.digest !== expectedDigest) {
      throw new Error("Checkpoint digest changed; refusing destructive cleanup");
    }
    await this.deleteIfPresent(
      "v1",
      "PersistentVolumeClaim",
      profile.namespace,
      checkpoint.pvcName,
    );
  }

  private async workspaceResult(
    workspaceId: WorkspaceId,
    profile: WorkspaceExecutionProfile,
    resources: WorkspaceResourceSet,
    pod: KubernetesObject,
    state: string,
  ): Promise<ProviderResult<ProviderObservation>> {
    const observedAt = this.now();
    const reference = workspaceReference(
      workspaceId,
      profile.namespace,
      resources.pod.metadata.name,
      observedAt,
      pod.metadata.resourceVersion,
    );
    return {
      value: {
        state,
        details: {
          workspaceId,
          namespace: profile.namespace,
          pod: resources.pod.metadata.name,
          persistent: profile.persistent,
          runtimeClassName: profile.runtimeClassName ?? "native",
          serviceAccountTokenMounted: false,
          reusablePublicationCredentialMounted: false,
        },
      },
      references: [reference],
      observedAt,
      reconciliationToken: `${resources.pod.metadata.name}:${pod.metadata.resourceVersion ?? "applied"}`,
    };
  }

  private async findWorkspace(
    workspaceId: WorkspaceId,
  ): Promise<{ profile: WorkspaceExecutionProfile; name: string } | undefined> {
    const name = resourceBaseName(workspaceId);
    for (const profile of this.allProfiles()) {
      const pod = await this.kubernetes.get("v1", "Pod", profile.namespace, name);
      const pvc = await this.kubernetes.get("v1", "PersistentVolumeClaim", profile.namespace, name);
      if (pod || pvc) return { profile, name };
    }
    return undefined;
  }

  private allProfiles(): readonly WorkspaceExecutionProfile[] {
    return this.profiles.list();
  }

  private profileForNamespace(namespace: string): WorkspaceExecutionProfile | undefined {
    return this.allProfiles().find((profile) => profile.namespace === namespace);
  }

  private async deleteIfPresent(
    apiVersion: string,
    kind: string,
    namespace: string,
    name: string,
  ): Promise<void> {
    const existing = await this.kubernetes.get(apiVersion, kind, namespace, name);
    if (existing) await this.kubernetes.delete(apiVersion, kind, namespace, name);
  }

  private absentResult(
    workspaceId: WorkspaceId,
    state: string,
  ): ProviderResult<ProviderObservation> {
    const observedAt = this.now();
    return {
      value: { state, details: { workspaceId } },
      references: [],
      observedAt,
      reconciliationToken: `${workspaceId}:${state}`,
    };
  }
}

function workspaceReference(
  workspaceId: WorkspaceId,
  namespace: string,
  name: string,
  observedAt: string,
  revision?: string,
): ProviderReference {
  return {
    providerId: PROVIDER_ID,
    resourceType: "kubernetes-workspace",
    nativeId: `${namespace}/${name}`,
    ...(revision === undefined ? {} : { nativeRevision: revision }),
    url: `k8s://${namespace}/pods/${name}?awpWorkspaceId=${encodeURIComponent(workspaceId)}`,
    observedAt,
  };
}

function validateReference(reference: ProviderReference): void {
  if (reference.providerId !== PROVIDER_ID || reference.resourceType !== "kubernetes-workspace") {
    throw new Error("Workspace reconcile received a foreign provider reference");
  }
}

function parseWorkspaceReference(reference: ProviderReference): {
  namespace: string;
  name: string;
  workspaceId: WorkspaceId;
} {
  const [namespace, name] = reference.nativeId.split("/", 2);
  if (!namespace || !name) throw new Error("Invalid Kubernetes workspace reference");
  const workspaceId = new URL(reference.url ?? "k8s://invalid").searchParams.get("awpWorkspaceId");
  if (!workspaceId) throw new Error("Workspace reference is missing AWP workspace identity");
  return { namespace, name, workspaceId: workspaceId as WorkspaceId };
}

function normalizePodState(pod: KubernetesObject | undefined): string {
  if (!pod) return "interrupted";
  const phase = typeof pod.status?.phase === "string" ? pod.status.phase : undefined;
  switch (phase) {
    case "Running":
      return "ready";
    case "Succeeded":
      return "completed";
    case "Failed":
      return "execution-interrupted";
    case "Pending":
    default:
      return "preparing";
  }
}

function checkpointDigest(workspaceId: WorkspaceId, pvc: KubernetesObject): string {
  return createHash("sha256")
    .update(workspaceId)
    .update("\0")
    .update(pvc.metadata.uid ?? "")
    .update("\0")
    .update(pvc.metadata.resourceVersion ?? "")
    .digest("hex");
}
