/**
 * Push-Token-Registration mit Expo.
 *
 * Flow:
 *   1. Permission anfragen (falls noch nicht entschieden)
 *   2. ExponentPushToken[xxx] via getExpoPushTokenAsync() holen
 *   3. Token an Backend POST /api/users/me/push-token senden
 *   4. Bei Logout: DELETE /api/users/me/push-token?token=…
 *
 * Wird einmal pro App-Start nach Login aufgerufen — idempotent durch
 * upsert im Backend.
 */
import { useEffect, useRef } from 'react';
import { Platform } from 'react-native';
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import Constants from 'expo-constants';
import { apiFetch } from '../lib/api';
import { getDeviceId } from '../lib/deviceId';

// VoIP-PushKit (iOS only) — separater Push-Token für CallKit-Wake-from-killed.
// Lazy require: react-native-voip-push-notification ist iOS-only, Android-Bundle
// soll nicht crashen.
let RNVoipPushNotification: any = null;
if (Platform.OS === 'ios') {
  try {
    RNVoipPushNotification = require('react-native-voip-push-notification').default;
  } catch {
    // Modul (noch) nicht gelinkt — z.B. im Expo-Go-Fallback. Silent skip.
  }
}

const lastRegisteredToken: { current: string | null } = { current: null };
const lastRegisteredVoipToken: { current: string | null } = { current: null };

/** Holt iOS-VoIP-Push-Token via PushKit. Resolve mit `null` wenn nicht verfügbar. */
async function fetchVoipToken(): Promise<string | null> {
  if (Platform.OS !== 'ios') {
    if (__DEV__) console.log('[voip] skip (not iOS)');
    return null;
  }
  if (!RNVoipPushNotification) {
    console.warn('[voip] react-native-voip-push-notification NOT linked — PushKit token cannot be fetched. Did the with-voip-pushkit-ios.js plugin run + prebuild?');
    return null;
  }
  return new Promise((resolve) => {
    let resolved = false;
    const onToken = (token: string) => {
      if (resolved) return;
      resolved = true;
      if (__DEV__) console.log('[voip] register event fired, token:', token ? token.slice(0, 20) + '…' : '(empty)');
      resolve(token || null);
    };
    try {
      // Listener registrieren BEVOR registerVoipToken — sonst race.
      RNVoipPushNotification.addEventListener('register', onToken);
      RNVoipPushNotification.registerVoipToken();
      if (__DEV__) console.log('[voip] registerVoipToken() called, waiting for callback…');
      // Safety-Timeout: nach 4s aufgeben (Cert/Provisioning fehlt etc.).
      setTimeout(() => {
        if (resolved) return;
        resolved = true;
        console.warn('[voip] timeout after 4s — NO PushKit token received. Check: Push-Notifications-Cap + Background-Modes(voip) in Xcode entitlements, physical device, VoIP-services-certificate in Apple Portal.');
        resolve(null);
      }, 4000);
    } catch (err) {
      console.warn('[voip] register failed:', err);
      resolve(null);
    }
  });
}

export async function registerPushTokenWithBackend(): Promise<string | null> {
  // Simulator/Emulator support: Expo Push funktioniert auf physical devices only
  // — Simulator gibt Permission denied. Wir loggen aber crashen nicht.
  if (!Device.isDevice) {
    if (__DEV__) console.log('[push] skipped (simulator)');
    return null;
  }

  try {
    // 1) Permission
    const { status: existing } = await Notifications.getPermissionsAsync();
    let status = existing;
    if (existing !== 'granted') {
      const { status: requested } = await Notifications.requestPermissionsAsync();
      status = requested;
    }
    if (status !== 'granted') {
      if (__DEV__) console.log('[push] permission denied');
      return null;
    }

    // 2) Android-Channels (muss vor getExpoPushTokenAsync existieren)
    if (Platform.OS === 'android') {
      await Notifications.setNotificationChannelAsync('chat', {
        name: 'Chat-Nachrichten',
        importance: Notifications.AndroidImportance.HIGH,
        vibrationPattern: [0, 250, 250, 250],
        lightColor: '#007AFF',
        sound: 'default',
      });
      // Dedizierter Channel für eingehende Anrufe — MAX importance, längere
      // Vibration, bypasst DND nicht (das bräuchte Critical-Alert-Permission).
      await Notifications.setNotificationChannelAsync('calls', {
        name: 'Anrufe',
        importance: Notifications.AndroidImportance.MAX,
        vibrationPattern: [0, 500, 200, 500, 200, 500],
        lightColor: '#007AFF',
        sound: 'default',
        lockscreenVisibility: Notifications.AndroidNotificationVisibility.PUBLIC,
      });
      // Account-Security: „Neues Gerät verbunden" — eigener Channel, damit der
      // User Geräte-Alerts nicht zusammen mit Chat stummschaltet.
      await Notifications.setNotificationChannelAsync('devices', {
        name: 'Geräte & Sicherheit',
        importance: Notifications.AndroidImportance.HIGH,
        vibrationPattern: [0, 250, 250, 250],
        lightColor: '#007AFF',
        sound: 'default',
        lockscreenVisibility: Notifications.AndroidNotificationVisibility.PUBLIC,
      });
    }

    // 3) Token holen
    const projectId =
      Constants.expoConfig?.extra?.eas?.projectId ??
      Constants.easConfig?.projectId;
    if (!projectId) {
      console.warn('[push] EAS projectId missing — token cannot be issued');
      return null;
    }
    const tokenData = await Notifications.getExpoPushTokenAsync({ projectId });
    const token = tokenData.data;
    if (!token) return null;

    // 4) VoIP-Token parallel holen (iOS-only, no-op auf Android).
    const voipToken = await fetchVoipToken();

    // 5) Idempotenz-Skip: wenn beide Tokens unverändert, kein Re-POST.
    if (
      lastRegisteredToken.current === token &&
      lastRegisteredVoipToken.current === voipToken
    ) {
      return token;
    }

    // 6) Senden an Backend
    const deviceId = await getDeviceId();
    await apiFetch('/api/users/me/push-token', {
      method: 'POST',
      body: {
        token,
        platform: Platform.OS as 'ios' | 'android',
        deviceId,
        ...(voipToken ? { voipToken } : {}),
      },
    });

    lastRegisteredToken.current = token;
    lastRegisteredVoipToken.current = voipToken;
    if (__DEV__) {
      console.log('[push] token registered:', token.slice(0, 30) + '…');
      if (voipToken) {
        console.log('[voip] token registered:', voipToken.slice(0, 30) + '…');
      } else if (Platform.OS === 'ios') {
        console.warn('[voip] iOS without VoIP-token — incoming calls in background/killed will NOT wake the app. Backend will fall back to silent Expo-push.');
      }
    }
    return token;
  } catch (err) {
    console.warn('[push] registration failed:', err);
    return null;
  }
}

export async function unregisterPushTokenFromBackend(): Promise<void> {
  const token = lastRegisteredToken.current;
  if (!token) return;
  try {
    await apiFetch(
      `/api/users/me/push-token?token=${encodeURIComponent(token)}`,
      { method: 'DELETE' },
    );
    lastRegisteredToken.current = null;
  } catch (err) {
    console.warn('[push] unregister failed:', err);
  }
}

/**
 * Hook: registriert Push-Token sobald User authenticated ist.
 * Verwendet in app/_layout.tsx nach Auth-Init.
 */
export function usePushTokenRegistration(userId: string | null | undefined) {
  const lastUserId = useRef<string | null>(null);
  useEffect(() => {
    if (!userId || userId === lastUserId.current) return;
    lastUserId.current = userId;
    void registerPushTokenWithBackend();
  }, [userId]);
}
