import {
  RTCPeerConnection,
  RTCSessionDescription,
  RTCIceCandidate,
  mediaDevices,
  MediaStream,
} from 'react-native-webrtc';

export interface ICEServer {
  urls: string[];
  username?: string;
  credential?: string;
}

export type ConnectionState = 'new' | 'connecting' | 'connected' | 'disconnected' | 'failed' | 'closed';

export type CallType = 'voice' | 'video';

export type WebRTCManagerEvent =
  | { type: 'icecandidate'; candidate: RTCIceCandidate | null }
  | { type: 'connectionstatechange'; state: ConnectionState }
  | { type: 'remotestream'; stream: MediaStream }
  | { type: 'negotiationneeded'; offer: RTCSessionDescriptionInit }
  | { type: 'error'; error: Error };

type EventHandler = (event: WebRTCManagerEvent) => void;

class WebRTCManager {
  private peerConnection: RTCPeerConnection | null = null;
  private localStream: MediaStream | null = null;
  private remoteStream: MediaStream | null = null;
  private pendingCandidates: RTCIceCandidateInit[] = [];
  private iceServers: ICEServer[] = [];
  private eventHandlers: Set<EventHandler> = new Set();
  private disposed = false;
  private isInitiator = false;
  
  // ICE restart 相关状态
  private reconnectAttempts = 0;
  private readonly MAX_RECONNECT_ATTEMPTS = 3;
  private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
  private disconnectTimer: ReturnType<typeof setTimeout> | null = null;

  async initialize(iceServers: ICEServer[] = []): Promise<void> {
    if (this.peerConnection) {
      this.dispose();
    }
    this.disposed = false;
    this.iceServers = iceServers.length > 0
      ? iceServers
      : [{ urls: ['stun:stun.l.google.com:19302'] }];
  }

  private buildPeerConnectionConfig(): RTCConfiguration {
    return {
      iceServers: this.iceServers.map((server) => ({
        urls: server.urls,
        ...(server.username ? { username: server.username } : {}),
        ...(server.credential ? { credential: server.credential } : {}),
      })),
      iceCandidatePoolSize: 10,
    };
  }

  private createPeerConnection(): RTCPeerConnection {
    const config = this.buildPeerConnectionConfig();
    const pc = new RTCPeerConnection(config);

    // @ts-ignore
    pc.onicecandidate = (event) => {
      if (event.candidate) {
        this.emit({ type: 'icecandidate', candidate: event.candidate });
      } else {
        this.emit({ type: 'icecandidate', candidate: null });
      }
    };

    // @ts-ignore
    pc.oniceconnectionstatechange = () => {
      const state = pc.iceConnectionState as ConnectionState;
      this.emit({ type: 'connectionstatechange', state });
      this.handleIceConnectionStateChange(state);
    };

    // @ts-ignore
    pc.onconnectionstatechange = () => {
      const state = pc.connectionState as ConnectionState;
      this.emit({ type: 'connectionstatechange', state });
      this.handleConnectionStateChange(state);
    };

    // @ts-ignore
    pc.ontrack = (event) => {
      console.log('[WebRTC] ontrack event, kind:', event.track?.kind, 'streams:', event.streams?.length ?? 0);
      if (!event.track) {
        console.log('[WebRTC] ontrack: no track in event');
        return;
      }

      // Always manually construct/append to remoteStream to ensure all tracks are collected
      // react-native-webrtc may fire ontrack per-track and event.streams[0] might not contain all tracks
      if (!this.remoteStream) {
        this.remoteStream = new MediaStream();
      }

      // Check if track already exists to avoid duplicates
      const existingTracks = this.remoteStream.getTracks();
      const trackExists = existingTracks.some(t => t.id === event.track.id);
      if (!trackExists) {
        this.remoteStream.addTrack(event.track);
        console.log('[WebRTC] Added remote track:', event.track.kind, 'total tracks:', this.remoteStream.getTracks().length);
      } else {
        console.log('[WebRTC] Remote track already exists:', event.track.kind);
      }

      this.emit({ type: 'remotestream', stream: this.remoteStream });
    };

    // @ts-ignore
    pc.onsignalingstatechange = () => {
      console.log('[WebRTC] Signaling state changed:', pc.signalingState);
    };

    // @ts-ignore
    pc.onnegotiationneeded = async () => {
      console.log('[WebRTC] Negotiation needed, signalingState:', pc.signalingState);
      if (!this.peerConnection || this.peerConnection !== pc) {
        console.log('[WebRTC] PeerConnection changed or disposed, skipping');
        return;
      }
      if (pc.signalingState !== 'stable') {
        console.log('[WebRTC] Skipping negotiation, not in stable state:', pc.signalingState);
        return;
      }
      try {
        const offer = await this.createOffer();
        if (this.peerConnection && !this.disposed) {
          this.emit({ type: 'negotiationneeded', offer });
        }
      } catch (err) {
        console.error('[WebRTC] Negotiation needed failed:', err);
      }
    };

    return pc;
  }

  async createLocalStream(voiceOnly = true): Promise<MediaStream> {
    const constraints = voiceOnly
      ? { audio: true, video: false }
      : { audio: true, video: { facingMode: 'user', frameRate: 30 } };

    try {
      // @ts-ignore
      this.localStream = await mediaDevices.getUserMedia(constraints);
      return this.localStream;
    } catch (error) {
      console.error('[WebRTC] Failed to get local media stream:', error);
      throw error;
    }
  }

  /**
   * Start a call using official addTrack approach
   * Follows react-native-webrtc CallGuide.md
   */
  async startCall(isInitiator: boolean, callType: CallType = 'voice'): Promise<RTCSessionDescriptionInit | null> {
    if (this.disposed) throw new Error('WebRTCManager has been disposed');
    if (!this.localStream) throw new Error('Local stream not initialized');

    this.isInitiator = isInitiator;
    this.peerConnection = this.createPeerConnection();

    // Official approach: add all tracks using addTrack
    // addTrack automatically creates senders with proper directions
    this.localStream.getTracks().forEach((track) => {
      console.log('[WebRTC] Adding track:', track.kind, track.enabled);
      this.peerConnection!.addTrack(track, this.localStream!);
    });

    if (isInitiator) {
      // For initiator, create offer directly
      const offer = await this.createOffer();
      return offer;
    }
    return null;
  }

  async createOffer(): Promise<RTCSessionDescriptionInit> {
    if (!this.peerConnection) throw new Error('PeerConnection not initialized');

    console.log('[WebRTC] Creating offer...');

    const offerOptions = {
      offerToReceiveAudio: true,
      offerToReceiveVideo: true,
    };

    const offer = await this.peerConnection.createOffer(offerOptions);

    if (!this.peerConnection || this.disposed) {
      throw new Error('PeerConnection was disposed during offer creation');
    }

    await this.peerConnection.setLocalDescription(offer);
    return offer;
  }

  async createAnswer(): Promise<RTCSessionDescriptionInit> {
    if (!this.peerConnection) throw new Error('PeerConnection not initialized');

    console.log('[WebRTC] Creating answer...');

    const answerOptions = {
      offerToReceiveAudio: true,
      offerToReceiveVideo: true,
    };

    // @ts-ignore
    const answer = await this.peerConnection.createAnswer(answerOptions);

    if (!this.peerConnection || this.disposed) {
      throw new Error('PeerConnection was disposed during answer creation');
    }

    await this.peerConnection.setLocalDescription(answer);

    // Process pending candidates
    await this.processPendingCandidates();

    return answer;
  }

  async createAnswerFromOffer(offer: RTCSessionDescriptionInit): Promise<RTCSessionDescriptionInit> {
    await this.setRemoteDescription(offer);
    return this.createAnswer();
  }

  /**
   * Rollback to stable state (for glare handling)
   * Used when both peers try to negotiate simultaneously
   */
  async rollback(): Promise<void> {
    if (!this.peerConnection) {
      throw new Error('PeerConnection not initialized');
    }

    const pc = this.peerConnection;
    const signalingState = pc.signalingState;

    console.log('[WebRTC] Attempting rollback, current state:', signalingState);

    // Only rollback if we're not in stable state
    if (signalingState === 'stable') {
      console.log('[WebRTC] Already in stable state, no rollback needed');
      return;
    }

    try {
      // For react-native-webrtc, we may need to recreate the peer connection
      // as rollback is not fully supported
      if (signalingState === 'have-local-offer') {
        // Rollback local offer by setting local description to null/undefined
        // @ts-ignore - react-native-webrtc specific
        if (pc.setLocalDescription) {
          // @ts-ignore
          await pc.setLocalDescription({ type: 'rollback' });
        }
      } else if (signalingState === 'have-remote-offer') {
        // Rollback remote offer
        // @ts-ignore
        if (pc.setRemoteDescription) {
          // @ts-ignore
          await pc.setRemoteDescription({ type: 'rollback' });
        }
      }

      console.log('[WebRTC] Rollback successful');
    } catch (err) {
      console.error('[WebRTC] Rollback failed:', err);
      throw err;
    }
  }

  async setRemoteDescription(description: RTCSessionDescriptionInit): Promise<void> {
    if (!this.peerConnection) {
      console.error('[WebRTC] setRemoteDescription: PeerConnection is null');
      throw new Error('PeerConnection not initialized');
    }

    if (!description.sdp) {
      throw new Error('setRemoteDescription: sdp is required');
    }

    console.log('[WebRTC] Setting remote description, type:', description.type);

    const desc = new RTCSessionDescription({
      type: description.type,
      sdp: description.sdp,
    });
    await this.peerConnection.setRemoteDescription(desc);

    console.log('[WebRTC] Remote description set successfully');

    // Process pending candidates after remote description is set
    await this.processPendingCandidates();
  }

  async addIceCandidate(candidate: RTCIceCandidateInit): Promise<void> {
    if (!this.peerConnection) {
      this.pendingCandidates.push(candidate);
      return;
    }

    if (!this.peerConnection.remoteDescription) {
      this.pendingCandidates.push(candidate);
      return;
    }

    try {
      const iceCandidate = new RTCIceCandidate(candidate);
      await this.peerConnection.addIceCandidate(iceCandidate);
    } catch (error) {
      console.error('[WebRTC] Failed to add ICE candidate:', error);
      this.pendingCandidates.push(candidate);
    }
  }

  private async processPendingCandidates(): Promise<void> {
    if (this.pendingCandidates.length === 0) return;
    if (!this.peerConnection) return;

    const candidates = [...this.pendingCandidates];
    this.pendingCandidates = [];

    for (const candidate of candidates) {
      if (!this.peerConnection) return;

      try {
        const iceCandidate = new RTCIceCandidate(candidate);
        await this.peerConnection.addIceCandidate(iceCandidate);
      } catch (error) {
        console.error('[WebRTC] Failed to add pending ICE candidate:', error);
      }
    }
  }

  setMuted(muted: boolean): void {
    if (!this.localStream) return;
    const audioTracks = this.localStream.getAudioTracks();
    audioTracks.forEach((track) => {
      track.enabled = !muted;
    });
  }

  isMuted(): boolean {
    if (!this.localStream) return false;
    const audioTracks = this.localStream.getAudioTracks();
    return audioTracks.some((track) => !track.enabled);
  }

  /**
   * Enable video - official replaceTrack approach
   */
  async enableVideo(): Promise<MediaStream> {
    if (!this.peerConnection) throw new Error('PeerConnection not initialized');

    console.log('[WebRTC] Enabling video...');

    try {
      // Get video stream
      const videoStream = await mediaDevices.getUserMedia({
        video: { facingMode: 'user', frameRate: 30 },
        audio: false,
      });

      const videoTrack = videoStream.getVideoTracks()[0];

      // Find the sender for video and replace the track
      const senders = this.peerConnection.getSenders();
      const videoSender = senders.find(s => s.track?.kind === 'video');

      if (videoSender) {
        await videoSender.replaceTrack(videoTrack);
        console.log('[WebRTC] Video track replaced on existing sender');
      } else {
        // No existing video sender, add track
        this.peerConnection.addTrack(videoTrack, this.localStream!);
        console.log('[WebRTC] Video track added via addTrack');
      }

      // Update local stream
      const newStream = new MediaStream();

      if (this.localStream) {
        this.localStream.getAudioTracks().forEach((track) => {
          newStream.addTrack(track);
        });
        // Stop old video tracks
        this.localStream.getVideoTracks().forEach((track) => {
          track.stop();
        });
      }

      newStream.addTrack(videoTrack);
      this.localStream = newStream;

      console.log('[WebRTC] Video enabled successfully');

      return newStream;
    } catch (error) {
      console.error('[WebRTC] Failed to enable video:', error);
      throw error;
    }
  }

  /**
   * Disable video - official replaceTrack approach
   */
  async disableVideo(): Promise<MediaStream | null> {
    if (!this.peerConnection) {
      console.log('[WebRTC] disableVideo: No peer connection');
      return null;
    }
    if (!this.localStream) {
      console.log('[WebRTC] disableVideo: No local stream');
      return null;
    }

    console.log('[WebRTC] Disabling video...');

    // Find the sender for video and replace with null
    const senders = this.peerConnection.getSenders();
    const videoSender = senders.find(s => s.track?.kind === 'video');

    if (videoSender) {
      await videoSender.replaceTrack(null);
      console.log('[WebRTC] Video track removed from sender');
    }

    // Stop video tracks
    const videoTracks = this.localStream.getVideoTracks();
    videoTracks.forEach((track) => {
      track.stop();
    });

    // Create new stream with only audio
    const newStream = new MediaStream();
    this.localStream.getAudioTracks().forEach((track) => {
      newStream.addTrack(track);
    });

    this.localStream = newStream;
    console.log('[WebRTC] Video disabled successfully');

    return newStream;
  }

  isVideoEnabled(): boolean {
    if (!this.localStream) return false;
    const videoTracks = this.localStream.getVideoTracks();
    return videoTracks.length > 0 && videoTracks.some((track) => track.enabled);
  }

  getRemoteStream(): MediaStream | null {
    return this.remoteStream;
  }

  getLocalStream(): MediaStream | null {
    return this.localStream;
  }

  getPeerConnection(): RTCPeerConnection | null {
    return this.peerConnection;
  }

  getSignalingState(): RTCSignalingState | null {
    return this.peerConnection?.signalingState || null;
  }

  getIsInitiator(): boolean {
    return this.isInitiator;
  }

  onEvent(handler: EventHandler): () => void {
    this.eventHandlers.add(handler);
    return () => {
      this.eventHandlers.delete(handler);
    };
  }

  private emit(event: WebRTCManagerEvent): void {
    this.eventHandlers.forEach((handler) => {
      try {
        handler(event);
      } catch (error) {
        console.error('[WebRTC] Event handler error:', error);
      }
    });
  }

  // ========== ICE Restart 支持 ==========

  /**
   * 处理 ICE 连接状态变化
   * 根据 W3C 规范: disconnected 状态可能间歇性触发并自发解决
   * failed 状态表示需要 ICE restart
   */
  private handleIceConnectionStateChange(state: ConnectionState): void {
    console.log('[WebRTC] ICE connection state:', state);

    switch (state) {
      case 'connected':        // 连接成功，重置重连状态
        this.resetReconnectState();
        break;

      case 'disconnected':
        // 临时断开，等待一段时间看是否自动恢复
        this.scheduleDisconnectCheck();
        break;

      case 'failed':
        // ICE 失败，尝试 ICE restart
        this.attemptIceRestart();
        break;

      case 'closed':
        this.clearReconnectTimers();
        break;
    }
  }

  /**
   * 处理 PeerConnection 连接状态变化
   */
  private handleConnectionStateChange(state: ConnectionState): void {
    console.log('[WebRTC] PeerConnection state:', state);

    switch (state) {
      case 'connected':
        this.resetReconnectState();
        break;

      case 'disconnected':
        // 等待短暂时间看是否自动恢复
        this.scheduleDisconnectCheck();
        break;

      case 'failed':
        // 连接完全失败
        this.emit({ type: 'error', error: new Error('Connection failed') });
        break;
    }
  }

  /**
   * 安排断开检查
   * 给 disconnected 状态一个恢复窗口（5秒）
   */
  private scheduleDisconnectCheck(): void {
    if (this.disconnectTimer) {
      clearTimeout(this.disconnectTimer);
    }

    this.disconnectTimer = setTimeout(() => {
      const pc = this.peerConnection;
      if (!pc || this.disposed) return;

      // 如果 5 秒后仍然是 disconnected，尝试 ICE restart
      if (pc.iceConnectionState === 'disconnected' || pc.iceConnectionState === 'failed') {
        console.log('[WebRTC] Connection still disconnected after 5s, attempting ICE restart');
        this.attemptIceRestart();
      }
    }, 5000);
  }

  /**
   * 尝试 ICE restart
   * 参考: https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Session_lifetime#ice_restart
   */
  private async attemptIceRestart(): Promise<void> {
    if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
      console.error('[WebRTC] Max reconnection attempts reached');
      this.emit({ type: 'error', error: new Error('Max reconnection attempts reached') });
      return;
    }

    const pc = this.peerConnection;
    if (!pc || this.disposed) {
      console.log('[WebRTC] Cannot restart ICE: PeerConnection not available');
      return;
    }

    // 检查信令状态
    if (pc.signalingState !== 'stable') {
      console.log('[WebRTC] Cannot restart ICE: signaling state not stable:', pc.signalingState);
      return;
    }

    this.reconnectAttempts++;
    console.log(`[WebRTC] Attempting ICE restart (${this.reconnectAttempts}/${this.MAX_RECONNECT_ATTEMPTS})`);

    try {
      // 尝试使用 restartIce() API (现代浏览器支持)
      // @ts-ignore
      if (pc.restartIce) {
        // @ts-ignore
        pc.restartIce();
        console.log('[WebRTC] restartIce() called');
      }

      // 创建新的 offer，触发 ICE restart
      const offer = await pc.createOffer({ iceRestart: true });
      await pc.setLocalDescription(offer);

      console.log('[WebRTC] ICE restart offer created');

      // 发送新的 offer 给对方
      this.emit({ type: 'negotiationneeded', offer });
    } catch (error) {
      console.error('[WebRTC] ICE restart failed:', error);
      this.emit({ type: 'error', error: error as Error });
    }
  }

  /**
   * 重置重连状态
   */
  private resetReconnectState(): void {
    this.reconnectAttempts = 0;
    this.clearReconnectTimers();
  }

  /**
   * 清除重连定时器
   */
  private clearReconnectTimers(): void {
    if (this.disconnectTimer) {
      clearTimeout(this.disconnectTimer);
      this.disconnectTimer = null;
    }
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
  }

  dispose(): void {
    this.disposed = true;
    this.eventHandlers.clear();
    this.pendingCandidates = [];
    this.clearReconnectTimers();

    if (this.localStream) {
      this.localStream.getTracks().forEach((track) => track.stop());
      this.localStream = null;
    }

    if (this.remoteStream) {
      // Do not stop remote tracks; they are managed by the peer connection
      // and will be ended automatically when the connection closes
      this.remoteStream = null;
    }

    if (this.peerConnection) {
      this.peerConnection.close();
      this.peerConnection = null;
    }
  }
}

export const webrtcManager = new WebRTCManager();
