import {
  base64ToBytes,
  base64Url,
  bufferSource,
  signerFromPrivateJwk,
  utf8,
} from "./crypto";
import type { RoleSigners } from "./runtime";
import { HttpError, type Env, type SignerMaterial } from "./types";

const ROLE_SECRETS = {
  authorization: "AUTHORIZATION_SIGNING_JWK",
  boundary: "RECEIVER_SIGNING_JWK",
  target: "DENOMINATOR_SIGNING_JWK",
  incidentCommander: "INCIDENT_COMMANDER_SIGNING_JWK",
  witness: "WITNESS_SIGNING_JWK",
  publication: "PUBLICATION_SIGNING_JWK",
} as const;

function parseSigner(value: string | undefined, name: string): SignerMaterial {
  if (value === undefined) {
    throw new HttpError(503, "KEY_PROVIDER_UNAVAILABLE", `${name} is not provisioned`);
  }
  try {
    return signerFromPrivateJwk(JSON.parse(value) as JsonWebKey);
  } catch {
    throw new HttpError(503, "KEY_PROVIDER_UNAVAILABLE", `${name} is not a valid Ed25519 JWK`);
  }
}

export function loadRoleSigners(env: Env): RoleSigners {
  if (env.KEY_PROVIDER_MODE !== "PROVISIONED_JWK") {
    throw new HttpError(
      503,
      "KEY_PROVIDER_BLOCKED",
      "write operations require the externally provisioned JWK key provider",
    );
  }
  const signers = Object.fromEntries(
    Object.entries(ROLE_SECRETS).map(([role, environmentName]) => [
      role,
      parseSigner(env[environmentName], environmentName),
    ]),
  ) as unknown as RoleSigners;
  const issuers = Object.values(signers).map((signer) => signer.issuer);
  if (new Set(issuers).size !== issuers.length) {
    throw new HttpError(503, "ROLE_KEY_COLLISION", "every alpha role requires a distinct issuer");
  }
  return signers;
}

function capabilitySecretBytes(value: string | undefined): Uint8Array {
  if (value === undefined || !/^[A-Za-z0-9_-]{43}$/u.test(value)) {
    throw new HttpError(
      503,
      "CAPABILITY_PROVIDER_UNAVAILABLE",
      "CAPABILITY_TOKEN_SECRET must be canonical unpadded base64url for 32 bytes",
    );
  }
  let bytes: Uint8Array;
  try {
    const padded = `${value.replaceAll("-", "+").replaceAll("_", "/")}=`;
    bytes = base64ToBytes(padded);
  } catch {
    throw new HttpError(
      503,
      "CAPABILITY_PROVIDER_UNAVAILABLE",
      "CAPABILITY_TOKEN_SECRET must be canonical unpadded base64url for 32 bytes",
    );
  }
  if (
    bytes.length !== 32 ||
    base64Url(bytes) !== value ||
    new Set(bytes).size < 16
  ) {
    throw new HttpError(
      503,
      "CAPABILITY_PROVIDER_UNAVAILABLE",
      "CAPABILITY_TOKEN_SECRET must be canonical unpadded base64url for 32 high-diversity bytes",
    );
  }
  return bytes;
}

export async function deriveCapabilityToken(env: Env, idempotencyDigest: string): Promise<string> {
  const secret = capabilitySecretBytes(env.CAPABILITY_TOKEN_SECRET);
  const key = await crypto.subtle.importKey(
    "raw",
    bufferSource(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"],
  );
  const signature = await crypto.subtle.sign(
    "HMAC",
    key,
    bufferSource(utf8(`bulla-control-plane-alpha/run/${idempotencyDigest}`)),
  );
  return base64Url(new Uint8Array(signature));
}
