const ACTIVATION_BODY_LIMIT = 16 * 1024;
const CHALLENGE_BODY_LIMIT = 1024;
const PUBLIC_STATUS_FIELDS = [
  "tier",
  "state",
  "maxAccounts",
  "configuredAccounts",
  "expiresAt",
  "graceEndsAt",
];
const PUBLIC_FAILURES = new Map([
  ["entitlement_challenge_expired", 409],
  ["entitlement_challenge_invalid", 400],
  ["entitlement_challenge_used", 409],
  ["invalid_entitlement_certificate", 400],
]);

export async function resolveEntitlementRoute(req, url, context) {
  if (req.method === "GET" && url.pathname === "/entitlement") {
    return { status: 200, body: publicStatus(await callStore(() => context.store.getStatus())) };
  }

  if (req.method === "POST" && url.pathname === "/entitlement/challenges") {
    const input = await context.readJson(req, CHALLENGE_BODY_LIMIT);
    if (!isExactObject(input, [])) throw routeError("invalid_entitlement_request", 400);
    const challenge = await callStore(() => context.store.issueChallenge());
    return {
      status: 201,
      body: {
        challenge: challenge.challenge,
        backendKeyThumbprint: challenge.backendKeyThumbprint,
      },
    };
  }

  if (req.method === "POST" && url.pathname === "/entitlement/activate") {
    const input = await context.readJson(req, ACTIVATION_BODY_LIMIT);
    if (
      !isExactObject(input, ["certificate", "challenge"])
      || typeof input.challenge !== "string"
      || input.challenge.length === 0
      || input.challenge.length > 512
      || typeof input.certificate !== "string"
      || input.certificate.length === 0
    ) throw routeError("invalid_entitlement_request", 400);
    const status = await callStore(() => context.store.activate({
      challenge: input.challenge,
      certificate: input.certificate,
    }));
    return { status: 200, body: publicStatus(status) };
  }

  return null;
}

function publicStatus(status) {
  return Object.fromEntries(PUBLIC_STATUS_FIELDS.map((field) => [field, status?.[field] ?? null]));
}

function isExactObject(value, fields) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
  const keys = Object.keys(value).sort();
  return keys.length === fields.length && fields.every((field, index) => keys[index] === field);
}

async function callStore(operation) {
  try {
    return await operation();
  } catch (error) {
    const status = PUBLIC_FAILURES.get(error?.code);
    if (status) throw routeError(error.code, status);
    throw routeError("entitlement_unavailable", 500);
  }
}

function routeError(code, status) {
  const error = new Error(code);
  error.status = status;
  error.safeLogCode = code;
  return error;
}
