/**
 * PhoneCall-Service — pausiert ARIA bei Telefonaten:
 *
 * 1. Klassischer Mobilfunk-Anruf via TelephonyManager (PhoneCallModule.kt)
 *    Status: idle / ringing / offhook
 *
 * 2. VoIP-Anrufe (WhatsApp, Signal, Discord, Telegram, Teams, ...) via
 *    AudioFocus-Loss-Event (AudioFocusModule.kt). Diese Apps requestn
 *    AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE wenn ein Anruf reinkommt — wir
 *    bekommen ein "loss" Event und reagieren genauso wie auf RINGING.
 *
 * In beiden Faellen wird audioService.haltAllPlayback() + wakeWordService.
 * pauseForCall() gerufen. Bei call-end (idle / focus-gain) → resumeFromCall.
 *
 * Permission READ_PHONE_STATE ist nur fuer Pfad 1 noetig — Pfad 2 braucht
 * keine extra Berechtigung weil unser eigener AudioFocus-Listener feuert.
 */

import {
  NativeEventEmitter,
  NativeModules,
  PermissionsAndroid,
  Platform,
  ToastAndroid,
} from 'react-native';
import audioService from './audio';
import wakeWordService from './wakeword';

interface PhoneCallNative {
  start(): Promise<boolean>;
  stop(): Promise<boolean>;
}

const { PhoneCall } = NativeModules as { PhoneCall?: PhoneCallNative };

type PhoneState = 'idle' | 'ringing' | 'offhook';

class PhoneCallService {
  private started: boolean = false;
  private subscription: { remove: () => void } | null = null;
  private focusSubscription: { remove: () => void } | null = null;
  private lastState: PhoneState = 'idle';
  /** Damit Resume nach VoIP-Loss nicht doppelt feuert wenn auch
   *  TelephonyManager-IDLE-Event kommt. */
  private interruptedByFocus: boolean = false;
  /** True wenn der TelephonyManager-Listener (Pfad 1) wirklich registriert
   *  ist. False wenn READ_PHONE_STATE abgelehnt wurde oder Native nicht ging. */
  private telephonyAttached: boolean = false;

  /** Status fuer Diagnose: laeuft die Anruf-Erkennung tatsaechlich? */
  status(): { focusAttached: boolean; telephonyAttached: boolean } {
    return {
      focusAttached: this.focusSubscription !== null,
      telephonyAttached: this.telephonyAttached,
    };
  }

  /** Nach App-Resume: pruefen ob die Listener noch leben. Wenn der
   *  TelephonyManager-Listener verloren ging (kann passieren wenn der
   *  React-Bridge-Context recreated wurde), neu attachen. */
  async refresh(): Promise<void> {
    if (!this.started) return;
    if (this.telephonyAttached) return; // alles ok
    if (!PhoneCall) return;
    try {
      const ok = await PhoneCall.start();
      if (ok) {
        if (!this.subscription) {
          const emitter = new NativeEventEmitter(NativeModules.PhoneCall as any);
          this.subscription = emitter.addListener(
            'PhoneCallStateChanged',
            (e: { state: PhoneState }) => this._onStateChanged(e.state),
          );
        }
        this.telephonyAttached = true;
        console.log('[PhoneCall] refresh: TelephonyManager-Listener re-attached');
      }
    } catch (err: any) {
      console.warn('[PhoneCall] refresh fehlgeschlagen:', err?.message || err);
    }
  }

  async start(): Promise<boolean> {
    if (this.started || Platform.OS !== 'android') return false;

    // 1. AudioFocus-Listener IMMER registrieren — fangs VoIP-Calls (WhatsApp,
    //    Signal, Discord etc.) abdecken, brauchen keine Permission.
    try {
      const focusEmitter = new NativeEventEmitter(NativeModules.AudioFocus as any);
      this.focusSubscription = focusEmitter.addListener(
        'AudioFocusChanged',
        (e: { type: 'loss' | 'loss_transient' | 'gain' }) => this._onFocusChanged(e.type),
      );
      console.log('[PhoneCall] AudioFocus-Listener aktiv (fuer VoIP-Calls)');
    } catch (err: any) {
      console.warn('[PhoneCall] AudioFocus-Subscription gescheitert', err?.message || err);
    }

    // 2. TelephonyManager-Listener — fuer klassische Mobilfunk-Anrufe
    if (PhoneCall) {
      try {
        const granted = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.READ_PHONE_STATE,
          {
            title: 'ARIA Cockpit — Anruf-Erkennung',
            message: 'Damit ARIA bei einem eingehenden Anruf nicht weiterredet, '
              + 'darf die App den Anruf-Status sehen (Klingeln/Aktiv/Aufgelegt). '
              + 'Es werden keine Anrufdaten gelesen oder gespeichert.',
            buttonPositive: 'Erlauben',
            buttonNegative: 'Spaeter',
          },
        );
        if (granted === PermissionsAndroid.RESULTS.GRANTED) {
          const ok = await PhoneCall.start();
          if (ok) {
            const emitter = new NativeEventEmitter(NativeModules.PhoneCall as any);
            this.subscription = emitter.addListener(
              'PhoneCallStateChanged',
              (e: { state: PhoneState }) => this._onStateChanged(e.state),
            );
            this.telephonyAttached = true;
            console.log('[PhoneCall] TelephonyManager-Listener aktiv');
          } else {
            console.warn('[PhoneCall] PhoneCall.start() lieferte false — Native-Listener nicht aktiv');
          }
        } else {
          console.warn('[PhoneCall] READ_PHONE_STATE abgelehnt — VoIP-Calls werden trotzdem ueber AudioFocus erkannt');
        }
      } catch (err: any) {
        console.warn('[PhoneCall] TelephonyManager-Setup gescheitert:', err?.message || err);
      }
    }

    this.started = true;
    return true;
  }

  async stop(): Promise<void> {
    if (!this.started) return;
    try { this.subscription?.remove(); } catch {}
    try { this.focusSubscription?.remove(); } catch {}
    this.subscription = null;
    this.focusSubscription = null;
    if (PhoneCall) {
      try { await PhoneCall.stop(); } catch {}
    }
    this.started = false;
    this.lastState = 'idle';
    this.interruptedByFocus = false;
    this.telephonyAttached = false;
  }

  private _onStateChanged(state: PhoneState): void {
    if (state === this.lastState) return;
    const prev = this.lastState;
    console.log('[PhoneCall] State: %s → %s', prev, state);
    this.lastState = state;
    if (state === 'ringing' || state === 'offhook') {
      this._haltForCall(state === 'ringing' ? 'Anruf — ARIA pausiert' : 'Im Gespraech — ARIA pausiert');
    } else if (state === 'idle' && prev !== 'idle') {
      // Wenn schon durch AudioFocus-Loss pausiert wurde, NICHT doppelt resumen.
      // Der Focus-Gain-Event triggert das Resume.
      if (!this.interruptedByFocus) {
        this._resumeAfterCall('Anruf beendet — ARIA wieder aktiv');
      }
    }
  }

  /** AudioFocus-Loss = irgendeine andere App hat den Focus uebernommen.
   *  Das passiert bei VoIP-Anrufen (was wir wollen) ABER auch bei normalen
   *  Audio-Playern (anderer Player startet, Notification-Sound, sogar
   *  unsere eigenen Sound-Calls beim Play-Button). Daher checken wir den
   *  AudioMode — nur IN_CALL (2) oder IN_COMMUNICATION (3) zaehlt als Anruf. */
  private async _onFocusChanged(type: 'loss' | 'loss_transient' | 'gain'): Promise<void> {
    if (type === 'loss' || type === 'loss_transient') {
      // Schon durch klassischen TelephonyManager pausiert? Dann nichts doppeln.
      if (this.lastState === 'ringing' || this.lastState === 'offhook') return;
      // Mode pruefen — nur echte Anrufe behandeln.
      let mode = -1;
      try { mode = await (NativeModules.AudioFocus as any)?.getMode?.(); } catch {}
      if (mode !== 2 && mode !== 3) {
        // NORMAL-Mode → kein Anruf (Stefan hat z.B. Play-Button gedrueckt
        // oder Spotify hat sich neu reingedraengelt). Keine Toasts.
        console.log('[PhoneCall] FOCUS_LOSS ignoriert (AudioMode=%d, kein Call)', mode);
        return;
      }
      this.interruptedByFocus = true;
      this._haltForCall('Anruf erkannt (VoIP) — ARIA pausiert');
      // Pollen, weil GAIN nicht zuverlaessig kommt (wir releasen den Focus
      // selbst beim halt → kein automatischer GAIN). AudioMode != IN_COMMUNICATION
      // = Call vorbei.
      this._startVoipResumePoll();
    } else if (type === 'gain') {
      if (this.interruptedByFocus) {
        this.interruptedByFocus = false;
        this._stopVoipResumePoll();
        this._resumeAfterCall('Audio frei — ARIA wieder aktiv');
      }
    }
  }

  /** Polling-Fallback: alle 3s checken ob AudioMode wieder NORMAL ist. */
  private voipPollTimer: ReturnType<typeof setInterval> | null = null;
  private _startVoipResumePoll(): void {
    if (this.voipPollTimer) return;
    this.voipPollTimer = setInterval(async () => {
      if (!this.interruptedByFocus) {
        this._stopVoipResumePoll();
        return;
      }
      try {
        const mode = await (NativeModules.AudioFocus as any)?.getMode?.();
        // 0 = MODE_NORMAL — Call ist vorbei
        if (typeof mode === 'number' && mode === 0) {
          this.interruptedByFocus = false;
          this._stopVoipResumePoll();
          this._resumeAfterCall('Anruf beendet — ARIA wieder aktiv');
        }
      } catch {}
    }, 3000);
  }
  private _stopVoipResumePoll(): void {
    if (this.voipPollTimer) {
      clearInterval(this.voipPollTimer);
      this.voipPollTimer = null;
    }
  }

  private _haltForCall(toast: string): void {
    // Position merken bevor wir den Stream killen — fuer Auto-Resume.
    audioService.captureInterruption();
    // pauseForCall (statt haltAllPlayback): pcmBuffer + messageId bleiben,
    // weitere Chunks werden weiter gesammelt damit isFinal die WAV schreibt.
    audioService.pauseForCall(toast);
    wakeWordService.pauseForCall().catch(() => {});
    ToastAndroid.show(toast, ToastAndroid.SHORT);
  }

  private _resumeAfterCall(toast: string): void {
    // Anruf-Pause aufheben — neue Chunks duerfen wieder direkt abgespielt
    // werden (falls die Bridge mid-Anruf isFinal noch nicht geschickt hat).
    audioService.endCallPause();
    wakeWordService.resumeFromCall().catch(() => {});
    ToastAndroid.show(toast, ToastAndroid.SHORT);
    // 800ms warten bevor Auto-Resume — sonst kollidiert ARIA's neuer Focus-
    // Request mit Spotify's Auto-Resume nach Anruf-Ende. System haengt nach
    // dem Auflegen noch im IN_CALL-Mode-Uebergang, Spotify schaut auf Focus-
    // Gain und wuerde sofort wieder LOSS sehen → bleibt pausiert.
    // Mit Delay: Spotify resumed kurz, dann pausiert ARIA wieder ordnungs-
    // gemaess. Wenn ARIA nichts pending hat, bleibt Spotify einfach an.
    setTimeout(() => {
      audioService.resumeFromInterruption(30000).then(ok => {
        if (ok) {
          console.log('[PhoneCall] Auto-Resume von gemerkter Position gestartet');
        }
      }).catch(() => {});
    }, 800);
  }
}

const phoneCallService = new PhoneCallService();
export default phoneCallService;
