'use client';

import { useCallback, useEffect, useState } from 'react';
import axios from 'axios';

export type PushPermissionState = NotificationPermission | 'unsupported';

export interface PushSubscriptionInfo {
  endpoint: string;
  p256dh: string;
  auth: string;
}

interface UsePushNotificationReturn {
  permission: PushPermissionState;
  isSubscribed: boolean;
  isLoading: boolean;
  error: string | null;
  subscribe: () => Promise<void>;
  unsubscribe: () => Promise<void>;
}

/**
 * Hook for managing Web Push notification subscriptions.
 * Handles Service Worker registration, push permission, and subscription lifecycle.
 */
export function usePushNotification(): UsePushNotificationReturn {
  const [permission, setPermission] = useState<PushPermissionState>('unsupported');
  const [isSubscribed, setIsSubscribed] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Check initial state on mount
  useEffect(() => {
    if (!('Notification' in self) || !('serviceWorker' in navigator) || !('PushManager' in self)) {
      setPermission('unsupported');
      return;
    }
    setPermission(Notification.permission);

    // Check if already subscribed
    navigator.serviceWorker.ready.then((registration) => {
      registration.pushManager.getSubscription().then((sub) => {
        setIsSubscribed(!!sub);
      }).catch(() => {
        setIsSubscribed(false);
      });
    }).catch(() => {
      setIsSubscribed(false);
    });
  }, []);

  const subscribe = useCallback(async () => {
    if (permission === 'unsupported') {
      setError('Push notifications are not supported in this browser.');
      return;
    }
    if (permission === 'denied') {
      setError('Push notifications are blocked. Please enable them in your browser settings.');
      return;
    }

    setIsLoading(true);
    setError(null);

    try {
      // 1. Register / get Service Worker
      const registration = await navigator.serviceWorker.register('/sw.js', { scope: '/' }).catch(() => {
        // If already registered, just get it
        return navigator.serviceWorker.ready;
      });

      // 2. Request permission if not granted
      if (Notification.permission !== 'granted') {
        const result = await Notification.requestPermission();
        if (result !== 'granted') {
          setPermission(result);
          setError('Permission denied. Cannot subscribe to push notifications.');
          setIsLoading(false);
          return;
        }
        setPermission('granted');
      }

      // 3. Get VAPID public key from server
      const vapidResponse = await axios.get<{ data?: { public_key?: string } }>(
        '/api/users/me/notifications/push/vapid-key'
      );
      const publicKey = vapidResponse.data?.data?.public_key;
      if (!publicKey) {
        throw new Error('VAPID public key not available from server.');
      }

      // 4. Subscribe to push
      const subscription = await registration.pushManager.subscribe({
        userVisibleOnly: true,
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        applicationServerKey: urlBase64ToUint8Array(publicKey) as any,
      });

      // 5. Extract subscription details
      const raw = subscription.toJSON();
      const pushSubscription: PushSubscriptionInfo = {
        endpoint: raw.endpoint ?? '',
        p256dh: raw.keys?.p256dh ?? '',
        auth: raw.keys?.auth ?? '',
      };

      // 6. Save subscription to server via the preferences endpoint
      //    The server stores these in user_notification.push_subscription_* columns
      await axios.patch('/api/users/me/notifications/preferences', {
        push_subscription_endpoint: pushSubscription.endpoint,
        push_subscription_keys_p256dh: pushSubscription.p256dh,
        push_subscription_keys_auth: pushSubscription.auth,
      });

      setIsSubscribed(true);
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Failed to subscribe to push notifications';
      setError(msg);
    } finally {
      setIsLoading(false);
    }
  }, [permission]);

  const unsubscribe = useCallback(async () => {
    setIsLoading(true);
    setError(null);

    try {
      // 1. Unsubscribe from push manager
      const registration = await navigator.serviceWorker.ready;
      const subscription = await registration.pushManager.getSubscription();
      if (subscription) {
        await subscription.unsubscribe();
      }

      // 2. Clear subscription on server
      await axios.delete('/api/users/me/notifications/push/subscription');

      setIsSubscribed(false);
    } catch (err) {
      const msg = err instanceof Error ? err.message : 'Failed to unsubscribe from push notifications';
      setError(msg);
    } finally {
      setIsLoading(false);
    }
  }, []);

  return { permission, isSubscribed, isLoading, error, subscribe, unsubscribe };
}

// ─── Utility ────────────────────────────────────────────────────────────────

/**
 * Convert a Base64url string to a Uint8Array (for applicationServerKey).
 * Matches the browser's built-in urlBase64ToUint8Array behavior.
 */
function urlBase64ToUint8Array(base64String: string): Uint8Array {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');

  const rawData = atob(base64);
  const outputArray = new Uint8Array(rawData.length);

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