import React from 'react';
import { getPushConfig } from '../lib/runtime-config';
import { registerPushSubscription, unregisterPushSubscription } from '../services/pushApi';

type PushSubscriptionState = {
  supported: boolean;
  permission: NotificationPermission;
  subscribed: boolean;
  loading: boolean;
  error: string | null;
  enable: () => Promise<void>;
  disable: () => Promise<void>;
  refresh: () => Promise<void>;
};

export function usePushSubscription(eventToken?: string): PushSubscriptionState {
  const pushConfig = React.useMemo(() => getPushConfig(), []);
  const supported = React.useMemo(() => {
    return typeof window !== 'undefined'
      && typeof navigator !== 'undefined'
      && typeof Notification !== 'undefined'
      && 'serviceWorker' in navigator
      && 'PushManager' in window
      && pushConfig.enabled;
  }, [pushConfig.enabled]);

  const [permission, setPermission] = React.useState<NotificationPermission>(() => {
    if (typeof Notification === 'undefined') {
      return 'default';
    }

    return Notification.permission;
  });
  const [subscription, setSubscription] = React.useState<PushSubscription | null>(null);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState<string | null>(null);

  const refresh = React.useCallback(async () => {
    if (!supported || !eventToken) {
      return;
    }

    try {
      const registration = await navigator.serviceWorker.ready;
      const current = await registration.pushManager.getSubscription();
      setSubscription(current);
    } catch (err) {
      console.warn('Unable to refresh push subscription', err);
      setSubscription(null);
    }
  }, [eventToken, supported]);

  React.useEffect(() => {
    if (!supported) {
      return;
    }

    void refresh();

    const handleMessage = (event: MessageEvent) => {
      if (event.data?.type === 'push-subscription-change') {
        void refresh();
      }
    };

    navigator.serviceWorker?.addEventListener('message', handleMessage);

    return () => {
      navigator.serviceWorker?.removeEventListener('message', handleMessage);
    };
  }, [refresh, supported]);

  const enable = React.useCallback(async () => {
    if (!supported || !eventToken) {
      setError('Push-Benachrichtigungen werden auf diesem Gerät nicht unterstützt.');

      return;
    }

    setLoading(true);
    setError(null);

    try {
      const permissionResult = await Notification.requestPermission();
      setPermission(permissionResult);

      if (permissionResult !== 'granted') {
        throw new Error('Bitte erlaube Benachrichtigungen, um Push zu aktivieren.');
      }

      const registration = await navigator.serviceWorker.ready;
      const existing = await registration.pushManager.getSubscription();

      if (existing) {
        await registerPushSubscription(eventToken, existing);
        setSubscription(existing);

        return;
      }

      if (!pushConfig.vapidPublicKey) {
        throw new Error('Push-Konfiguration ist nicht vollständig.');
      }

      const newSubscription = await registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(pushConfig.vapidPublicKey).buffer as ArrayBuffer,
      });

      await registerPushSubscription(eventToken, newSubscription);
      setSubscription(newSubscription);
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Push konnte nicht aktiviert werden.';
      setError(message);
      console.error(err);
      await refresh();
    } finally {
      setLoading(false);
    }
  }, [eventToken, pushConfig.vapidPublicKey, refresh, supported]);

  const disable = React.useCallback(async () => {
    if (!supported || !eventToken || !subscription) {
      return;
    }

    setLoading(true);
    setError(null);

    try {
      await unregisterPushSubscription(eventToken, subscription.endpoint);
      await subscription.unsubscribe();
      setSubscription(null);
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Push konnte nicht deaktiviert werden.';
      setError(message);
      console.error(err);
    } finally {
      setLoading(false);
    }
  }, [eventToken, subscription, supported]);

  return {
    supported,
    permission,
    subscribed: Boolean(subscription),
    loading,
    error,
    enable,
    disable,
    refresh,
  };
}

function urlBase64ToUint8Array(base64String: string): Uint8Array {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
  const rawData = typeof window !== 'undefined'
    ? window.atob(base64)
    : Buffer.from(base64, 'base64').toString('binary');
  const outputArray = new Uint8Array(rawData.length);

  for (let i = 0; i < rawData.length; i += 1) {
    outputArray[i] = rawData.charCodeAt(i);
  }

  return outputArray;
}
