/**
 * Minimal AuthzProvider over SpiceDB's HTTP API.
 *
 * The `mesh start` local platform exposes SpiceDB HTTP on :8443 (preshared
 * key `local-dev-key`); deployed SpiceDB exposes the same API. When the
 * platform ships a first-party gRPC provider in @mesh-tech/authz, swap this
 * file for it — the
 * AuthzProvider seam is the whole point.
 */

import type { AuthzProvider } from "@mesh-tech/authz/runtime";

export interface SpiceDbHttpConfig {
  /** e.g. http://localhost:8443 */
  endpoint: string;
  presharedKey: string;
}

async function call(cfg: SpiceDbHttpConfig, path: string, body: unknown): Promise<any> {
  const res = await fetch(`${cfg.endpoint}${path}`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${cfg.presharedKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
  });
  const data: any = await res.json().catch(() => ({}));
  if (!res.ok) {
    throw new Error(`SpiceDB ${path} → ${res.status}: ${data?.message ?? "error"}`);
  }
  return data;
}

export function spiceDbHttpProvider(cfg: SpiceDbHttpConfig): AuthzProvider {
  // compileSpiceDBSchema emits unprefixed definitions (`definition report`),
  // so object types pass through as-is.
  const objectType = (type: string) => type;
  return {
    name: "spicedb-http",

    async checkPermission({ subject, action, resource, consistency }) {
      // An id-less m.require() would silently check the wildcard resource —
      // fail-closed in practice, but it hides the caller's bug. Fail loud.
      if (!resource.id) {
        throw new Error(
          `checkPermission: resource.id is required (got type '${resource.type}' with no id) — pass the object id being authorized.`,
        );
      }
      // Default to fully-consistent: the hello-world flow grants then checks
      // immediately, and SpiceDB's minimize-latency default can serve a stale
      // denial. Tune per-call via the runtime's consistency hint in real apps.
      const data = await call(cfg, "/v1/permissions/check", {
        consistency:
          consistency === "minimize-latency" ? { minimizeLatency: true } : { fullyConsistent: true },
        resource: { objectType: objectType(resource.type), objectId: resource.id },
        permission: action,
        subject: { object: { objectType: objectType(subject.type), objectId: subject.id } },
      });
      return { allowed: data.permissionship === "PERMISSIONSHIP_HAS_PERMISSION" };
    },

    async writeRelationship({ subject, relation, resource, op }) {
      const operation =
        op === "delete" ? "OPERATION_DELETE" : op === "create" ? "OPERATION_CREATE" : "OPERATION_TOUCH";
      await call(cfg, "/v1/relationships/write", {
        updates: [
          {
            operation,
            relationship: {
              resource: { objectType: objectType(resource.type), objectId: resource.id },
              relation,
              subject: { object: { objectType: objectType(subject.type), objectId: subject.id } },
            },
          },
        ],
      });
      return {};
    },

    async lookupResources() {
      throw new Error("lookupResources not implemented by the HTTP starter provider");
    },

    async lookupSubjects() {
      throw new Error("lookupSubjects not implemented by the HTTP starter provider");
    },
  };
}
