const PAIRING_PREFIX = "crm-pair:v1:";
const BASE64URL_VALUE = /^[A-Za-z0-9_-]+$/;

export function createDevicePairingPayload({ origin, attemptId, secret, expiresAt }) {
  const value = validatePairingData({ origin, attemptId, secret, expiresAt });
  return `${PAIRING_PREFIX}${encodeBase64Url(JSON.stringify(value))}`;
}

export function parseDevicePairingPayload(payload) {
  if (typeof payload !== "string" || payload.length > 2048 || !payload.startsWith(PAIRING_PREFIX)) {
    throw new Error("invalid_pairing_payload");
  }

  try {
    const encoded = payload.slice(PAIRING_PREFIX.length);
    if (!encoded || !BASE64URL_VALUE.test(encoded)) throw new Error("invalid_pairing_payload");
    return validatePairingData(JSON.parse(decodeBase64Url(encoded)));
  } catch {
    throw new Error("invalid_pairing_payload");
  }
}

function validatePairingData(value) {
  const origin = normalizeOrigin(value?.origin);
  if (!validOpaqueValue(value?.attemptId, 16, 96) || !validOpaqueValue(value?.secret, 32, 128)) {
    throw new Error("invalid_pairing_payload");
  }
  const expiresAt = new Date(value?.expiresAt);
  if (!Number.isFinite(expiresAt.getTime())) throw new Error("invalid_pairing_payload");
  return {
    origin,
    attemptId: value.attemptId,
    secret: value.secret,
    expiresAt: expiresAt.toISOString()
  };
}

function normalizeOrigin(value) {
  if (typeof value !== "string" || value.length > 512) throw new Error("invalid_pairing_payload");
  const url = new URL(value);
  const loopbackHttp = url.protocol === "http:" && ["127.0.0.1", "localhost"].includes(url.hostname);
  if (url.username || url.password || (url.protocol !== "https:" && !loopbackHttp) || url.origin !== value.replace(/\/$/, "")) {
    throw new Error("invalid_pairing_payload");
  }
  return url.origin;
}

function validOpaqueValue(value, minimum, maximum) {
  return typeof value === "string"
    && value.length >= minimum
    && value.length <= maximum
    && BASE64URL_VALUE.test(value);
}

function encodeBase64Url(value) {
  const bytes = new TextEncoder().encode(value);
  let binary = "";
  for (const byte of bytes) binary += String.fromCharCode(byte);
  return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}

function decodeBase64Url(value) {
  const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
  const binary = atob(padded);
  return new TextDecoder().decode(Uint8Array.from(binary, (character) => character.charCodeAt(0)));
}
