import { useCallback, useEffect, useState } from "react";
import { useAuthToken } from "./use-root-data";
import { getApiBaseUrl } from "~/utils/api-config";
import { toast } from "./use-toast";

/**
 * Web Push subscription management.
 *
 * Flow: ask permission -> fetch VAPID public key -> subscribe via the service
 * worker's PushManager -> POST the subscription to the backend. Disabling
 * unsubscribes locally and tells the backend to drop the row.
 *
 * Every awaited step is guarded by a timeout — several of them (notably
 * `pushManager.subscribe` and `serviceWorker.ready` on iOS Safari when the
 * PWA isn't home-screen-installed) can return a Promise that never resolves.
 * Without timeouts the toggle wedges as "..." forever.
 */

type PermissionState = NotificationPermission | "unsupported";

const STEP_TIMEOUT_MS = 15000;

function withTimeout<T>(p: Promise<T>, ms: number, label: string): Promise<T> {
  return new Promise<T>((resolve, reject) => {
    const t = setTimeout(() => reject(new Error(`timeout: ${label}`)), ms);
    p.then(
      (v) => { clearTimeout(t); resolve(v); },
      (e) => { clearTimeout(t); reject(e); },
    );
  });
}

function isIosSafari(): boolean {
  if (typeof navigator === "undefined") return false;
  const ua = navigator.userAgent || "";
  const isIos = /iPad|iPhone|iPod/.test(ua) && !("MSStream" in window);
  return isIos;
}

function isStandalonePwa(): boolean {
  if (typeof window === "undefined") return false;
  const mm = window.matchMedia?.("(display-mode: standalone)").matches;
  // iOS-specific standalone flag on the Navigator object.
  const nav = navigator as Navigator & { standalone?: boolean };
  return !!(mm || nav.standalone);
}

function urlBase64ToUint8Array(base64String: string): Uint8Array<ArrayBuffer> {
  const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
  const rawData = atob(base64);
  // Back the array with a concrete ArrayBuffer so it satisfies BufferSource.
  const outputArray = new Uint8Array(new ArrayBuffer(rawData.length));
  for (let i = 0; i < rawData.length; i++) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
}

function isPushSupported(): boolean {
  return (
    typeof window !== "undefined" &&
    "serviceWorker" in navigator &&
    "PushManager" in window &&
    "Notification" in window
  );
}

export function usePushNotifications() {
  const token = useAuthToken();
  const apiBase = getApiBaseUrl();

  const [permission, setPermission] = useState<PermissionState>("default");
  const [isSubscribed, setIsSubscribed] = useState(false);
  const [busy, setBusy] = useState(false);

  const authHeaders = useCallback(() => {
    const headers: Record<string, string> = { "Content-Type": "application/json" };
    if (token) headers["Authorization"] = `Bearer ${token}`;
    return headers;
  }, [token]);

  // Reflect current permission + subscription state on mount.
  //
  // We *also* re-POST the sub to the backend on every mount. Reasons:
  //   - iOS Safari silently expires push subs after a few days of inactivity;
  //     the local `getSubscription()` still returns the token but the backend
  //     row may have been 410'd and deleted. Re-POSTing recreates it.
  //   - Multi-device: a sub registered on device A may not exist server-side
  //     for device B. Idempotent upsert (`update_or_create` by endpoint) makes
  //     this safe to do on every visit.
  useEffect(() => {
    if (!isPushSupported()) {
      setPermission("unsupported");
      return;
    }
    setPermission(Notification.permission);
    withTimeout(navigator.serviceWorker.ready, STEP_TIMEOUT_MS, "serviceWorker.ready")
      .then((reg) => withTimeout(reg.pushManager.getSubscription(), STEP_TIMEOUT_MS, "getSubscription"))
      .then((sub) => {
        setIsSubscribed(!!sub);
        if (sub && token) {
          // Fire-and-forget refresh; failure is non-fatal.
          fetch(`${apiBase}/api/notif/v1/push/subscribe/`, {
            method: "POST",
            headers: authHeaders(),
            body: JSON.stringify(sub.toJSON()),
          }).catch(() => {});
        }
      })
      .catch(() => setIsSubscribed(false));
  }, [apiBase, token, authHeaders]);

  const subscribe = useCallback(async (): Promise<boolean> => {
    if (!isPushSupported() || !token) return false;
    // iOS Safari only allows Web Push from a home-screen-installed PWA. In a
    // regular Safari tab, `pushManager.subscribe` returns a Promise that
    // never resolves — indistinguishable from a hang. Bail out early with a
    // clear message rather than wedging the UI.
    if (isIosSafari() && !isStandalonePwa()) {
      toast({
        title: "برای فعال‌سازی، ابتدا سایت را به صفحه اصلی اضافه کنید",
        description:
          "در Safari دکمه اشتراک‌گذاری را بزنید و «Add to Home Screen» را انتخاب کنید، سپس از آیکن روی صفحه اصلی وارد شوید و دوباره تلاش کنید.",
      });
      return false;
    }

    setBusy(true);
    try {
      const perm = await withTimeout(
        Notification.requestPermission(),
        STEP_TIMEOUT_MS,
        "requestPermission",
      );
      setPermission(perm);
      if (perm !== "granted") {
        if (perm === "denied") {
          toast({
            title: "اعلان‌ها در مرورگر مسدود شده‌اند",
            description:
              "از تنظیمات مرورگر یا PWA اعلان‌ها را برای این سایت فعال کنید و صفحه را رفرش کنید.",
            variant: "destructive",
          });
        }
        return false;
      }

      // Get the VAPID public key from the backend. The backend's view returns
      // {"public_key": ...} but its global CamelCaseJSONRenderer rewrites that
      // to {"publicKey": ...} on the wire — so we read camelCase here.
      const keyRes = await withTimeout(
        fetch(`${apiBase}/api/notif/v1/push/vapid-public-key/`),
        STEP_TIMEOUT_MS,
        "vapid-key fetch",
      );
      const { publicKey } = await keyRes.json();
      if (!publicKey) return false;

      const reg = await withTimeout(
        navigator.serviceWorker.ready,
        STEP_TIMEOUT_MS,
        "serviceWorker.ready",
      );
      let sub = await withTimeout(
        reg.pushManager.getSubscription(),
        STEP_TIMEOUT_MS,
        "getSubscription",
      );
      if (!sub) {
        sub = await withTimeout(
          reg.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: urlBase64ToUint8Array(publicKey),
          }),
          STEP_TIMEOUT_MS,
          "pushManager.subscribe",
        );
      }

      const res = await withTimeout(
        fetch(`${apiBase}/api/notif/v1/push/subscribe/`, {
          method: "POST",
          headers: authHeaders(),
          body: JSON.stringify(sub.toJSON()),
        }),
        STEP_TIMEOUT_MS,
        "backend subscribe",
      );
      const ok = res.ok;
      setIsSubscribed(ok);
      if (ok) {
        toast({
          title: "اعلان‌ها فعال شد",
          description: "از این پس اعلان‌ها را دریافت می‌کنید.",
        });
      }
      return ok;
    } catch (e) {
      console.error("Push subscribe failed:", e);
      // Include the step name / error text in the toast so field debugging
      // doesn't need browser console access — sellers can read out what
      // failed to whoever's helping them.
      const detail = e instanceof Error ? e.message : String(e);
      toast({
        title: "فعال‌سازی اعلان با خطا مواجه شد",
        description: `${detail || "خطای ناشناخته"}. لطفاً چند لحظه بعد دوباره تلاش کنید.`,
        variant: "destructive",
      });
      return false;
    } finally {
      setBusy(false);
    }
  }, [apiBase, token, authHeaders]);

  const disable = useCallback(async (): Promise<void> => {
    if (!isPushSupported()) return;
    setBusy(true);
    try {
      const reg = await withTimeout(
        navigator.serviceWorker.ready,
        STEP_TIMEOUT_MS,
        "serviceWorker.ready",
      );
      const sub = await withTimeout(
        reg.pushManager.getSubscription(),
        STEP_TIMEOUT_MS,
        "getSubscription",
      );
      if (sub) {
        await fetch(`${apiBase}/api/notif/v1/push/unsubscribe/`, {
          method: "POST",
          headers: authHeaders(),
          body: JSON.stringify({ endpoint: sub.endpoint }),
        }).catch(() => {});
        await sub.unsubscribe().catch(() => {});
      }
      setIsSubscribed(false);
    } catch (e) {
      console.error("Push disable failed:", e);
    } finally {
      setBusy(false);
    }
  }, [apiBase, authHeaders]);

  return {
    supported: permission !== "unsupported",
    permission,
    isSubscribed,
    busy,
    subscribe,
    disable,
  };
}
