import { hangupCall, onProxyEvent } from '../proxybridge.ts';
import type { FaxBoxManager } from '../faxbox.ts';
import type { FaxJobManager } from '../faxjobs.ts';
import type { VoiceboxManager } from '../voicebox.ts';
import type { StatusStore } from './status-store.ts';
import type { IProviderMediaInfo, WebRtcLinkManager } from './webrtc-linking.ts';

export interface IRegisterProxyEventHandlersOptions {
  log: (msg: string) => void;
  statusStore: StatusStore;
  faxBoxManager: FaxBoxManager;
  faxJobManager: FaxJobManager;
  voiceboxManager: VoiceboxManager;
  webRtcLinks: WebRtcLinkManager;
  getBrowserDeviceIds: () => string[];
  sendToBrowserDevice: (deviceId: string, data: unknown) => boolean;
  broadcast: (type: string, data: unknown) => void;
  onLinkWebRtcSession: (callId: string, sessionId: string, media: IProviderMediaInfo) => void;
  onCloseWebRtcSession: (sessionId: string) => void;
}

export function registerProxyEventHandlers(options: IRegisterProxyEventHandlersOptions): void {
  const {
    log,
    statusStore,
    faxBoxManager,
    faxJobManager,
    voiceboxManager,
    webRtcLinks,
    getBrowserDeviceIds,
    sendToBrowserDevice,
    broadcast,
    onLinkWebRtcSession,
    onCloseWebRtcSession,
  } = options;

  const legMediaDetails = (data: {
    codec?: string | null;
    mediaProtocol?: string | null;
    remoteMedia?: string | null;
    rtpPort?: number | null;
  }): string => {
    const parts: string[] = [];
    if (data.codec) {
      parts.push(`codec=${data.codec}`);
    }
    if (data.mediaProtocol) {
      parts.push(`media=${data.mediaProtocol}`);
    }
    if (data.remoteMedia) {
      parts.push(`remote=${data.remoteMedia}`);
    }
    if (data.rtpPort !== undefined && data.rtpPort !== null) {
      parts.push(`rtp=${data.rtpPort}`);
    }
    return parts.length ? ` ${parts.join(' ')}` : '';
  };

  onProxyEvent('provider_registered', (data) => {
    const previous = statusStore.noteProviderRegistered(data);
    if (previous) {
      if (data.registered && !previous.wasRegistered) {
        log(`[provider:${data.provider_id}] registered (publicIp=${data.public_ip})`);
      } else if (!data.registered && previous.wasRegistered) {
        log(`[provider:${data.provider_id}] registration lost`);
      }
    }
    broadcast('registration', { providerId: data.provider_id, registered: data.registered });
  });

  onProxyEvent('device_registered', (data) => {
    if (statusStore.noteDeviceRegistered(data)) {
      log(`[registrar] ${data.display_name} registered from ${data.address}:${data.port}`);
    }
  });

  onProxyEvent('incoming_call', (data) => {
    log(`[call] incoming: ${data.from_uri} -> ${data.to_number} via ${data.provider_id} (${data.call_id})`);
    statusStore.noteIncomingCall(data);

    if (data.ring_browsers === false) {
      return;
    }

    for (const deviceId of getBrowserDeviceIds()) {
      sendToBrowserDevice(deviceId, {
        type: 'webrtc-incoming',
        callId: data.call_id,
        from: data.from_uri,
        deviceId,
      });
    }
  });

  onProxyEvent('outbound_device_call', (data) => {
    log(`[call] outbound: device ${data.from_device} -> ${data.to_number} (${data.call_id})`);
    statusStore.noteOutboundDeviceCall(data);
  });

  onProxyEvent('outbound_call_started', (data) => {
    log(`[call] outbound started: ${data.call_id} -> ${data.number} via ${data.provider_id}`);
    statusStore.noteOutboundCallStarted(data);

    if (data.ring_browsers === false) {
      void faxJobManager.noteDialing(data.call_id, data.number, data.provider_id)
        .catch((error) => log(`[fax] persist dialing failed: ${error instanceof Error ? error.message : String(error)}`));
    }

    if (data.ring_browsers === false) {
      return;
    }

    for (const deviceId of getBrowserDeviceIds()) {
      sendToBrowserDevice(deviceId, {
        type: 'webrtc-incoming',
        callId: data.call_id,
        from: data.number,
        deviceId,
      });
    }
  });

  onProxyEvent('call_ringing', (data) => {
    statusStore.noteCallRinging(data);
  });

  onProxyEvent('call_answered', (data) => {
    if (statusStore.noteCallAnswered(data)) {
      log(`[call] ${data.call_id} connected`);
    }

    if (data.media_protocol && data.media_protocol !== 'rtp') {
      return;
    }

    if (!data.provider_media_addr || !data.provider_media_port) {
      return;
    }

    const target = webRtcLinks.noteCallAnswered(data.call_id, {
      addr: data.provider_media_addr,
      port: data.provider_media_port,
      sipPt: data.sip_pt ?? 9,
    });

    if (!target) {
      log(`[webrtc] media info cached for call=${data.call_id}, waiting for session accept`);
      return;
    }

    onLinkWebRtcSession(data.call_id, target.sessionId, target.media);
  });

  onProxyEvent('call_ended', (data) => {
    if (statusStore.noteCallEnded(data)) {
      log(`[call] ${data.call_id} ended: ${data.reason} (${data.duration}s)`);
    }

    broadcast('webrtc-call-ended', { callId: data.call_id });

    const sessionId = webRtcLinks.cleanupCall(data.call_id);
    if (sessionId) {
      onCloseWebRtcSession(sessionId);
    }
  });

  onProxyEvent('sip_unhandled', (data) => {
    log(`[sip] unhandled ${data.method_or_status} Call-ID=${data.call_id?.slice(0, 20)} from=${data.from_addr}:${data.from_port}`);
  });

  onProxyEvent('leg_added', (data) => {
    log(
      `[leg] added: call=${data.call_id} leg=${data.leg_id} kind=${data.kind} state=${data.state}${legMediaDetails(data)}`,
    );
    statusStore.noteLegAdded(data);
  });

  onProxyEvent('leg_removed', (data) => {
    log(`[leg] removed: call=${data.call_id} leg=${data.leg_id}`);
    statusStore.noteLegRemoved(data);
  });

  onProxyEvent('leg_state_changed', (data) => {
    log(
      `[leg] state: call=${data.call_id} leg=${data.leg_id} -> ${data.state}${legMediaDetails(data)}`,
    );
    statusStore.noteLegStateChanged(data);
  });

  onProxyEvent('webrtc_ice_candidate', (data) => {
    broadcast('webrtc-ice', {
      sessionId: data.session_id,
      candidate: {
        candidate: data.candidate,
        sdpMid: data.sdp_mid,
        sdpMLineIndex: data.sdp_mline_index,
      },
    });
  });

  onProxyEvent('webrtc_state', (data) => {
    log(`[webrtc] session=${data.session_id?.slice(0, 8)} state=${data.state}`);
  });

  onProxyEvent('webrtc_track', (data) => {
    log(`[webrtc] session=${data.session_id?.slice(0, 8)} track=${data.kind} codec=${data.codec}`);
  });

  onProxyEvent('webrtc_audio_rx', (data) => {
    if (data.packet_count === 1 || data.packet_count === 50) {
      log(`[webrtc] session=${data.session_id?.slice(0, 8)} browser audio rx #${data.packet_count}`);
    }
  });

  onProxyEvent('voicemail_started', (data) => {
    log(`[voicemail] started for call ${data.call_id} box=${data.voicebox_id || 'default'} caller=${data.caller_number}`);
  });

  onProxyEvent('recording_done', (data) => {
    const boxId = data.voicebox_id || 'default';
    log(`[voicemail] recording done: ${data.file_path} (${data.duration_ms}ms) box=${boxId} caller=${data.caller_number}`);
    void voiceboxManager.addMessage(boxId, {
      callerNumber: data.caller_number || 'Unknown',
      callerName: null,
      fileName: data.file_path,
      durationMs: data.duration_ms,
    }).catch((error) => log(`[voicemail] persist failed: ${error instanceof Error ? error.message : String(error)}`));
  });

  onProxyEvent('voicemail_error', (data) => {
    log(`[voicemail] error: ${data.error} call=${data.call_id}`);
  });

  onProxyEvent('fax_started', (data) => {
    void faxJobManager.noteStarted(data).catch((error) => log(`[fax] persist start failed: ${error instanceof Error ? error.message : String(error)}`));
    log(`[fax] started: call=${data.call_id} leg=${data.leg_id} ${data.direction}/${data.transport} codec=${data.codec || '?'} file=${data.file_path}`);
  });

  onProxyEvent('fax_completed', (data) => {
    void faxJobManager.noteCompleted(data).catch((error) => log(`[fax] persist completion failed: ${error instanceof Error ? error.message : String(error)}`));
    log(
      `[fax] completed: call=${data.call_id} leg=${data.leg_id} success=${data.success} pagesTx=${data.stats.pages_tx} bitrate=${data.stats.bit_rate} completion=${data.completion_label || data.completion_code || 'unknown'}`,
    );
    if (data.direction === 'inbound' && data.success && data.fax_box_id) {
      void faxBoxManager.addMessage(data.fax_box_id, {
        callerNumber: data.caller_number,
        fileName: data.file_path,
        completionCode: data.completion_code,
        completionLabel: data.completion_label,
        pageCount: data.stats.pages_rx || data.stats.pages_tx,
        bitRate: data.stats.bit_rate,
      }).catch((error) => log(`[fax] persist inbox failed: ${error instanceof Error ? error.message : String(error)}`));
    }
    if (data.direction === 'outbound' || data.fax_box_id) {
      void hangupCall(data.call_id);
    }
  });

  onProxyEvent('fax_failed', (data) => {
    void faxJobManager.noteFailed(data).catch((error) => log(`[fax] persist failure failed: ${error instanceof Error ? error.message : String(error)}`));
    log(`[fax] failed: call=${data.call_id} leg=${data.leg_id} error=${data.error}`);
    if (data.direction === 'outbound' || data.fax_box_id) {
      void hangupCall(data.call_id);
    }
  });
}
