/**
 * The App Attest key record: one Durable Object per keyId holding the
 * registered public key and its assertion counter, plus the injectable store
 * the verifiers talk to.
 *
 * The key and the counter belong together: registration writes the public key
 * and starts the counter at zero, and every verified assertion advances the
 * counter through the same single-threaded instance. Durable Objects give
 * each keyId its own strongly consistent instance with great distributed
 * performance, so "strictly increasing" holds everywhere at once.
 */
import type { DurableObjectNamespaceLike, Env } from "./env.ts";

/** Injectable seam: registered keys and their assertion counters. */
export interface AttestKeyStore {
  getPublicKey(keyId: string): Promise<string | null>;
  putPublicKey(keyId: string, spkiBase64: string): Promise<void>;
  /** True only when `counter` is strictly greater than the stored value. */
  bump(keyId: string, counter: number): Promise<boolean>;
}

interface StorageLike {
  get(key: string): Promise<unknown>;
  put(key: string, value: unknown): Promise<void>;
}

interface StateLike {
  storage: StorageLike;
}

type AttestKeyRequest =
  | { action: "get" }
  | { action: "put"; spkiBase64: string }
  | { action: "bump"; counter: number };

/** One instance per keyId (idFromName). Classic fetch-based Durable Object. */
export class AttestKey {
  storage: StorageLike;

  constructor(state: StateLike) {
    this.storage = state.storage;
  }

  async fetch(request: Request): Promise<Response> {
    const body = (await request.json()) as AttestKeyRequest;
    if (body.action === "put") {
      await this.storage.put("spki", body.spkiBase64);
      await this.storage.put("counter", 0);
      return Response.json({ ok: true });
    }
    if (body.action === "get") {
      const spki = (await this.storage.get("spki")) ?? null;
      return Response.json({ spki });
    }
    const last = ((await this.storage.get("counter")) ?? 0) as number;
    if (!Number.isInteger(body.counter) || body.counter <= last) {
      return Response.json({ ok: false });
    }
    await this.storage.put("counter", body.counter);
    return Response.json({ ok: true });
  }
}

/** Wrap the ATTEST_KEYS binding, or null when it is not configured. */
export function attestKeyStoreFromEnv(env: Env): AttestKeyStore | null {
  const ns: DurableObjectNamespaceLike | undefined = env.ATTEST_KEYS;
  if (!ns) return null;
  const call = async (keyId: string, body: AttestKeyRequest) => {
    const stub = ns.get(ns.idFromName(keyId));
    const res = await stub.fetch("https://attest-key/", {
      method: "POST",
      body: JSON.stringify(body),
    });
    return res.json();
  };
  return {
    async getPublicKey(keyId) {
      const { spki } = (await call(keyId, { action: "get" })) as {
        spki: string | null;
      };
      return spki;
    },
    async putPublicKey(keyId, spkiBase64) {
      await call(keyId, { action: "put", spkiBase64 });
    },
    async bump(keyId, counter) {
      const { ok } = (await call(keyId, { action: "bump", counter })) as {
        ok: boolean;
      };
      return ok;
    },
  };
}
