const SERVICE_WORKER_URL = "/termi-web-push-sw.js";

export type BrowserPushSubscriptionPayload = {
  endpoint: string;
  expirationTime?: number | null;
  keys: {
    auth: string;
    p256dh: string;
  };
};

export type BrowserPushSubscriptionState = {
  subscription: PushSubscription | null;
  stale: boolean;
};

function ensureBrowserSupport() {
  if (
    typeof window === "undefined" ||
    !("Notification" in window) ||
    !("serviceWorker" in navigator) ||
    !("PushManager" in window)
  ) {
    throw new Error("当前浏览器不支持 Web Push。");
  }
}

function urlBase64ToUint8Array(base64String: string) {
  const normalized = base64String.trim();
  const padding = "=".repeat((4 - (normalized.length % 4)) % 4);
  const base64 = (normalized + padding).replace(/-/g, "+").replace(/_/g, "/");
  const binary = window.atob(base64);
  const output = new Uint8Array(binary.length);

  for (let index = 0; index < binary.length; index += 1) {
    output[index] = binary.charCodeAt(index);
  }

  return output;
}

function toUint8Array(value: BufferSource | null | undefined) {
  if (!value) {
    return null;
  }

  if (value instanceof Uint8Array) {
    return value;
  }

  if (value instanceof ArrayBuffer) {
    return new Uint8Array(value);
  }

  return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
}

function uint8ArraysEqual(left: Uint8Array | null, right: Uint8Array | null) {
  if (!left || !right || left.byteLength !== right.byteLength) {
    return false;
  }

  for (let index = 0; index < left.byteLength; index += 1) {
    if (left[index] !== right[index]) {
      return false;
    }
  }

  return true;
}

function subscriptionMatchesPublicKey(
  subscription: PushSubscription,
  publicKeyBytes: Uint8Array | null,
) {
  if (!publicKeyBytes) {
    return true;
  }

  const currentKey = toUint8Array(
    subscription.options?.applicationServerKey ?? null,
  );
  return uint8ArraysEqual(currentKey, publicKeyBytes);
}

async function getRegistration() {
  ensureBrowserSupport();
  await navigator.serviceWorker.register(SERVICE_WORKER_URL, { scope: "/" });
  return navigator.serviceWorker.ready;
}

function normalizeSubscription(
  subscription: PushSubscription | PushSubscriptionJSON,
): BrowserPushSubscriptionPayload {
  const json = "toJSON" in subscription ? subscription.toJSON() : subscription;
  const endpoint = json.endpoint?.trim() || "";
  const auth = json.keys?.auth?.trim() || "";
  const p256dh = json.keys?.p256dh?.trim() || "";

  if (!endpoint || !auth || !p256dh) {
    throw new Error("浏览器返回的 PushSubscription 不完整。");
  }

  return {
    endpoint,
    expirationTime: json.expirationTime ?? null,
    keys: {
      auth,
      p256dh,
    },
  };
}

export function supportsBrowserPush() {
  return (
    typeof window !== "undefined" &&
    "Notification" in window &&
    "serviceWorker" in navigator &&
    "PushManager" in window
  );
}

export async function getBrowserPushSubscriptionState(
  publicKey?: string,
): Promise<BrowserPushSubscriptionState> {
  if (!supportsBrowserPush()) {
    return {
      subscription: null,
      stale: false,
    };
  }

  const registration = await getRegistration();
  const subscription = await registration.pushManager.getSubscription();
  const normalizedPublicKey = publicKey?.trim() || "";
  const publicKeyBytes = normalizedPublicKey
    ? urlBase64ToUint8Array(normalizedPublicKey)
    : null;

  if (!subscription) {
    return {
      subscription: null,
      stale: false,
    };
  }

  if (!subscriptionMatchesPublicKey(subscription, publicKeyBytes)) {
    return {
      subscription: null,
      stale: true,
    };
  }

  return {
    subscription,
    stale: false,
  };
}

export async function getBrowserPushSubscription(publicKey?: string) {
  const state = await getBrowserPushSubscriptionState(publicKey);
  return state.subscription;
}

export async function ensureBrowserPushSubscription(
  publicKey: string,
): Promise<BrowserPushSubscriptionPayload> {
  ensureBrowserSupport();

  if (!publicKey.trim()) {
    throw new Error("Web Push 公钥未配置。");
  }

  const permission =
    Notification.permission === "granted"
      ? "granted"
      : await Notification.requestPermission();

  if (permission !== "granted") {
    throw new Error("浏览器通知权限未开启。");
  }

  const registration = await getRegistration();
  const publicKeyBytes = urlBase64ToUint8Array(publicKey);
  let subscription = await registration.pushManager.getSubscription();

  if (
    subscription &&
    !subscriptionMatchesPublicKey(subscription, publicKeyBytes)
  ) {
    await subscription.unsubscribe().catch(() => undefined);
    subscription = null;
  }

  if (!subscription) {
    subscription = await registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: publicKeyBytes,
    });
  }

  return normalizeSubscription(subscription);
}
