/**
 * CallKit (iOS) / ConnectionService (Android) Wrapper.
 *
 * Zentralisiert react-native-callkeep so dass stores/call.ts platform-agnostic
 * bleibt. Drei Verantwortungen:
 *   1. setup() einmal beim App-Start (Permission-Prompt auf Android)
 *   2. displayIncomingCall() wenn Push/Realtime ein Ring signalisiert
 *   3. startCall() wenn der User selbst einen Anruf initiiert
 *
 * Privacy für DiGA (sensible Userbasis, Art. 9 DSGVO):
 *   - includesCallsInRecents: false  → KEIN iCloud-Sync der Anrufliste
 *   - handle = userId (Email-Type)   → keine Telefonnummern-Style-Anzeige
 *   - appName "ReBreak-Audio"        → erscheint im Lockscreen-Banner
 */
import { Platform, PermissionsAndroid, NativeModules } from 'react-native';
import { useNotificationPrefsStore } from '../stores/notificationPrefs';

// react-native-callkeep ist ein natives Modul. In Builds OHNE das Modul (z.B. der
// Dev-Build) crasht bereits der IMPORT, weil RNCallKeep beim Laden
// `new NativeEventEmitter(nativeModule)` mit null aufruft (Invariant Violation →
// die ganze App startet nicht). Darum laden wir das JS-Wrapper-Modul nur, wenn das
// native Modul `RNCallKeep` wirklich vorhanden ist. Sonst sind alle Call-Funktionen
// hier no-ops (Calls in diesem Build deaktiviert, App läuft trotzdem).
export const CALLKEEP_AVAILABLE = !!(NativeModules as { RNCallKeep?: unknown }).RNCallKeep;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let RNCallKeep: any = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let CK_CONSTANTS: any = null;
if (CALLKEEP_AVAILABLE) {
  // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
  const mod = require('react-native-callkeep');
  RNCallKeep = mod.default ?? mod;
  CK_CONSTANTS = mod.CONSTANTS;
}

export function isCallKeepAvailable(): boolean {
  return CALLKEEP_AVAILABLE;
}

let didSetup = false;

export async function setupCallKeep(): Promise<void> {
  if (didSetup || !RNCallKeep) return;
  try {
    // Stelle sicher dass die User-Prefs aus AsyncStorage geladen sind, bevor
    // wir CXProviderConfiguration einfrieren. CallKit liest includesCallsInRecents
    // nur beim provider-init \u2014 nachtr\u00e4gliche \u00c4nderungen erfordern App-Restart.
    try {
      await useNotificationPrefsStore.getState().init();
    } catch {}
    const callsInRecents =
      useNotificationPrefsStore.getState().callsInRecents ?? false;

    await RNCallKeep.setup({
      ios: {
        appName: 'ReBreak-Audio',
        // KEIN imageName \u2192 Default-Avatar von CallKit; wir setzen den echten
        // Caller-Namen via displayIncomingCall(localizedCallerName)
        supportsVideo: false,
        maximumCallGroups: '1',
        maximumCallsPerCallGroup: '1',
        // Privacy-Default: KEINE Anrufliste in iOS-Recents (= kein iCloud-Sync).
        // F\u00fcr DiGA mit Suchterkrankungs-Zielgruppe Default. User kann
        // in Settings opt-in (\u2192 dann erscheint ReBreak in der nativen
        // \"Anrufe\"-App wie WhatsApp).
        includesCallsInRecents: callsInRecents,
      },
      android: {
        alertTitle: 'Anrufe in ReBreak zulassen',
        alertDescription:
          'ReBreak braucht Telefon-Konto-Berechtigung, um eingehende Sprach-Anrufe wie in Telefon-Apps anzuzeigen.',
        cancelButton: 'Nicht jetzt',
        okButton: 'Erlauben',
        // Foreground-Service für Android 11+ Mic-Background-Access
        foregroundService: {
          channelId: 'org.rebreak.app.calls',
          channelName: 'ReBreak-Anrufe',
          notificationTitle: 'ReBreak: Anruf aktiv',
        },
      },
    });
    if (Platform.OS === 'android') {
      RNCallKeep.setAvailable(true);
      // Android 14+: Full-Screen-Intent für Lockscreen-Call-UI braucht extra Permission
      try {
        await PermissionsAndroid.request(
          // @ts-ignore — RN-Types haben USE_FULL_SCREEN_INTENT evtl. noch nicht
          'android.permission.USE_FULL_SCREEN_INTENT' as any,
        );
      } catch {}
    }
    // HINWEIS: KEIN endAllCalls() hier. Bei einem VoIP-Cold-Start (App wird
    // DURCH den eingehenden Call gestartet) läuft setupCallKeep im selben Launch
    // wie der gerade reportete Incoming-Call → endAllCalls() würde genau diesen
    // legitimen Anruf killen (= Auto-Reject bei Force-Quit). Der Zombie-Cleanup
    // passiert stattdessen verzögert + bedingt in useCallKeepEvents (nur wenn der
    // Store nach einer Grace-Period keinen echten Call kennt).
    didSetup = true;
  } catch (e: any) {
    console.warn('[callkeep] setup failed', e?.message ?? e);
  }
}

/**
 * UUID-Generator — CallKit braucht lowercase UUID (Apple-Quirk).
 * Wir mappen 1:1 callId ↔ callUUID (deterministic) damit beide Apps dieselbe ID nutzen.
 */
export function callIdToUuid(callId: string): string {
  // CallId hat Format: "<timestamp>-<random8>", z.B. "1717491600000-abc12345"
  // CallKit/CXProvider braucht **valides Hex-UUID** (0-9a-f) — sonst native
  // crash beim startCall(). Wir mappen jedes Zeichen der callId auf einen
  // Hex-Digit via charCode-modulo. Deterministic.
  const hex: string[] = [];
  for (let i = 0; i < callId.length && hex.length < 32; i++) {
    const code = callId.charCodeAt(i);
    // 2 Hex-Digits pro Zeichen → genug Material für 32 Hex-Zeichen
    hex.push(((code >> 4) & 0xf).toString(16));
    if (hex.length < 32) hex.push((code & 0xf).toString(16));
  }
  while (hex.length < 32) hex.push('0');
  const clean = hex.join('').slice(0, 32);
  // UUID v4 Format: xxxxxxxx-xxxx-4xxx-Yxxx-xxxxxxxxxxxx (Y ∈ {8,9,a,b})
  return `${clean.slice(0, 8)}-${clean.slice(8, 12)}-4${clean.slice(13, 16)}-8${clean.slice(17, 20)}-${clean.slice(20, 32)}`;
}

export function displayIncomingCall(callId: string, callerName: string): void {
  try {
    const uuid = callIdToUuid(callId);
    RNCallKeep.displayIncomingCall(
      uuid,
      callerName, // handle — wird im iOS-Lockscreen als Untertitel angezeigt
      callerName, // localizedCallerName — als Titel
      'generic',  // handleType: kein Phone-Number-Format
      false,      // hasVideo
    );
  } catch (e: any) {
    console.warn('[callkeep] displayIncomingCall failed', e?.message ?? e);
  }
}

export function startOutgoingCall(callId: string, calleeName: string): void {
  // Android: NICHT aufrufen \u2014 `RNCallKeep.startCall` triggert
  // `telecomManager.placeCall` was die System-Telefon-Wahl-UI \u00f6ffnet. F\u00fcr
  // In-App-WebRTC-Calls v\u00f6llig falsch (User sieht "Telefon w\u00e4hlt\u2026" statt
  // unserem /call-Screen). iOS-CallKit macht das richtig (Audio-Session
  // setup, keine native UI weil supportsHolding=false und wir keine
  // CXEndCallAction triggern bis User auflegt).
  if (Platform.OS !== 'ios') return;
  try {
    const uuid = callIdToUuid(callId);
    RNCallKeep.startCall(uuid, calleeName, calleeName, 'generic', false);
  } catch (e: any) {
    console.warn('[callkeep] startCall failed', e?.message ?? e);
  }
}

export function reportConnected(callId: string): void {
  try {
    const uuid = callIdToUuid(callId);
    if (Platform.OS === 'android') {
      RNCallKeep.setCurrentCallActive(uuid);
    }
  } catch {}
}

export function endCall(callId: string): void {
  try {
    const uuid = callIdToUuid(callId);
    RNCallKeep.endCall(uuid);
  } catch {}
}

export function reportEnded(
  callId: string,
  reason: 'failed' | 'declined' | 'unanswered' | 'ended' = 'ended',
): void {
  try {
    const uuid = callIdToUuid(callId);
    const reasonCode =
      reason === 'failed'
        ? CK_CONSTANTS.END_CALL_REASONS.FAILED
        : reason === 'unanswered'
          ? CK_CONSTANTS.END_CALL_REASONS.UNANSWERED
          : reason === 'declined'
            ? CK_CONSTANTS.END_CALL_REASONS.MISSED
            : CK_CONSTANTS.END_CALL_REASONS.REMOTE_ENDED;
    RNCallKeep.reportEndCallWithUUID(uuid, reasonCode);
  } catch {}
}

export { RNCallKeep };
