import { useCallback, useState } from 'react';

type PermissionStatus = 'granted' | 'denied' | 'prompt' | 'unknown';

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

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

    return outputArray;
}

function getInitialPermission(): PermissionStatus {
    if (!('Notification' in window)) {
        return 'denied';
    }

    return Notification.permission as PermissionStatus;
}

function getIsSupported(): boolean {
    return (
        'Notification' in window &&
        'serviceWorker' in navigator &&
        'PushManager' in window
    );
}

export function usePushNotification() {
    const [permission, setPermission] =
        useState<PermissionStatus>(getInitialPermission);
    const [isSupported] = useState<boolean>(getIsSupported);

    const requestPermission =
        useCallback(async (): Promise<PermissionStatus> => {
            if (!('Notification' in window)) {
                return 'denied';
            }

            const result = await Notification.requestPermission();
            setPermission(result as PermissionStatus);

            return result as PermissionStatus;
        }, []);

    const subscribe = useCallback(
        async (vapidPublicKey: string): Promise<boolean> => {
            try {
                const registration = await navigator.serviceWorker.ready;
                const subscription = await registration.pushManager.subscribe({
                    userVisibleOnly: true,
                    applicationServerKey: urlBase64ToUint8Array(
                        vapidPublicKey,
                    ) as BufferSource,
                });

                const { endpoint } = subscription;
                const key = subscription.getKey('p256dh');
                const auth = subscription.getKey('auth');

                const response = await fetch('/api/push/subscribe', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'X-Requested-With': 'XMLHttpRequest',
                        'X-XSRF-TOKEN': decodeURIComponent(
                            document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1] ??
                                '',
                        ),
                    },
                    body: JSON.stringify({
                        endpoint,
                        public_key: key
                            ? btoa(String.fromCharCode(...new Uint8Array(key)))
                            : null,
                        auth_token: auth
                            ? btoa(String.fromCharCode(...new Uint8Array(auth)))
                            : null,
                        content_encoding: 'aes128gcm',
                    }),
                });

                return response.ok;
            } catch (error) {
                console.error(
                    'Failed to subscribe to push notifications:',
                    error,
                );

                return false;
            }
        },
        [],
    );

    const unsubscribe = useCallback(async (): Promise<boolean> => {
        try {
            const registration = await navigator.serviceWorker.ready;
            const subscription =
                await registration.pushManager.getSubscription();

            if (!subscription) {
                return true;
            }

            const response = await fetch('/api/push/unsubscribe', {
                method: 'DELETE',
                headers: {
                    'Content-Type': 'application/json',
                    'X-Requested-With': 'XMLHttpRequest',
                    'X-XSRF-TOKEN': decodeURIComponent(
                        document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1] ?? '',
                    ),
                },
                body: JSON.stringify({
                    endpoint: subscription.endpoint,
                }),
            });

            if (response.ok) {
                await subscription.unsubscribe();
            }

            return response.ok;
        } catch (error) {
            console.error(
                'Failed to unsubscribe from push notifications:',
                error,
            );

            return false;
        }
    }, []);

    return {
        permission,
        isSupported,
        requestPermission,
        subscribe,
        unsubscribe,
    };
}
