const PUSH_CONTEXT_CACHE = "push-context-v1";
const PUSH_CONTEXT_URL = "/__push-context__";

self.addEventListener("install", () => {
  self.skipWaiting();
});

self.addEventListener("message", (event) => {
  if (event.data?.type !== "push-context-sync") {
    return;
  }

  event.waitUntil(savePushContext(event.data.payload));
});

self.addEventListener("activate", (event) => {
  event.waitUntil(self.clients.claim());
});

self.addEventListener("push", (event) => {
  const data = event.data ? event.data.json() : {};
  const title = data.title || "Новый сигнал";
  const body = data.body || "Появился новый матч в списке сигналов";
  const url = data?.data?.url || data.url || "/";
  const tag = typeof data.tag === "string" && data.tag.trim().length > 0 ? data.tag : undefined;
  const shouldRenotify = Boolean(tag && data.renotify);

  const notificationOptions = {
    body,
    requireInteraction: false,
    icon: "/icons/app-icon-192.png",
    badge: "/icons/app-badge.png",
    data: {
      url,
      ...(data?.data || {})
    },
    vibrate: [200, 100, 200],
    timestamp: Date.now(),
    ...(tag ? { tag } : {}),
    ...(tag ? { renotify: shouldRenotify } : {})
  };

  event.waitUntil(
    self.registration.showNotification(title, notificationOptions)
  );
});

self.addEventListener("notificationclick", (event) => {
  event.notification.close();
  const url = event.notification.data?.url || "/";

  event.waitUntil(
    clients.matchAll({ type: "window", includeUncontrolled: true }).then((windowClients) => {
      const sameOriginClients = windowClients.filter((client) => {
        try {
          return new URL(client.url).origin === self.location.origin;
        } catch {
          return false;
        }
      });

      const preferredClient =
        sameOriginClients.find((client) => client.visibilityState === "visible" || client.focused) ||
        sameOriginClients[0];

      if (preferredClient) {
        preferredClient.postMessage({
          type: "push-notification-click",
          url
        });

        return preferredClient.focus();
      }

      for (const client of windowClients) {
        if ("focus" in client) {
          client.navigate(url);
          return client.focus();
        }
      }

      return clients.openWindow(url);
    })
  );
});

self.addEventListener("pushsubscriptionchange", (event) => {
  event.waitUntil(
    resubscribeAfterChange(event)
  );
});

async function savePushContext(nextContext) {
  const cache = await caches.open(PUSH_CONTEXT_CACHE);
  const currentContext = await readPushContext();
  const payload = {
    ...(currentContext || {}),
    ...(nextContext || {})
  };

  await cache.put(
    PUSH_CONTEXT_URL,
    new Response(JSON.stringify(payload), {
      headers: {
        "Content-Type": "application/json"
      }
    })
  );
}

async function readPushContext() {
  const cache = await caches.open(PUSH_CONTEXT_CACHE);
  const response = await cache.match(PUSH_CONTEXT_URL);
  if (!response) {
    return null;
  }

  try {
    return await response.json();
  } catch {
    return null;
  }
}

function urlBase64ToUint8Array(base64String) {
  const normalizedInput = typeof base64String === "string" ? base64String.trim() : "";
  if (!normalizedInput) {
    throw new Error("Missing VAPID public key");
  }

  const padding = "=".repeat((4 - (normalizedInput.length % 4)) % 4);
  const base64 = (normalizedInput + padding).replace(/-/g, "+").replace(/_/g, "/");
  const rawData = atob(base64);
  return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
}

async function fetchApplicationServerKey(apiBase) {
  if (!apiBase) {
    throw new Error("Push API base is not configured in service worker context");
  }

  const response = await fetch(`${apiBase}/vapid-public-key`, {
    method: "GET",
    credentials: "include"
  });

  if (!response.ok) {
    throw new Error(`Failed to load VAPID public key: ${response.status}`);
  }

  const payload = await response.json();
  return urlBase64ToUint8Array(payload.publicKey);
}

async function syncSubscriptionWithServer(context, subscription, previousEndpoint) {
  if (!context?.apiBase) {
    throw new Error("Push API base is not configured in service worker context");
  }

  const serializedSubscription = subscription.toJSON();
  if (!serializedSubscription.endpoint || !serializedSubscription.keys?.p256dh || !serializedSubscription.keys?.auth) {
    throw new Error("Browser returned an incomplete push subscription");
  }

  const isAuthenticated = Boolean(context.isAuthenticated);
  const endpoint = isAuthenticated ? "/me/push-subscriptions" : "/public/push-subscriptions";

  if (!isAuthenticated && !context.anonymousClientId) {
    throw new Error("Anonymous push client id is missing in service worker context");
  }

  const headers = new Headers({
    "Content-Type": "application/json"
  });

  const response = await fetch(`${context.apiBase}${endpoint}`, {
    method: "POST",
    credentials: "include",
    headers,
    body: JSON.stringify({
      ...(isAuthenticated ? {} : { clientId: context.anonymousClientId }),
      ...serializedSubscription,
      ...(previousEndpoint && previousEndpoint !== serializedSubscription.endpoint ? { previousEndpoint } : {})
    })
  });

  if (!response.ok) {
    throw new Error(`Failed to sync rotated subscription: ${response.status}`);
  }
}

async function resubscribeAfterChange(event) {
  const context = await readPushContext();
  if (!context) {
    return;
  }

  const previousEndpoint = event.oldSubscription?.endpoint;
  const subscription =
    event.newSubscription ||
    (await self.registration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: await fetchApplicationServerKey(context.apiBase)
    }));

  await syncSubscriptionWithServer(context, subscription, previousEndpoint);
}
