/**
 * Real-time Monitoring Utilities - WebSocket and event listener helpers
 */

import type { Address } from 'viem'

export interface MonitorEvent {
  type: 'block' | 'transaction' | 'contract_event' | 'state_change'
  timestamp: number
  data: any
}

export type EventCallback = (event: MonitorEvent) => void

class RealtimeMonitor {
  private listeners: Map<string, Set<EventCallback>> = new Map()
  private ws: WebSocket | null = null
  private reconnectAttempts = 0
  private maxReconnectAttempts = 5
  private reconnectDelay = 3000

  constructor(private wsUrl?: string) {}

  /**
   * Subscribe to block updates
   */
  subscribeToBlocks(chainId: number, callback: EventCallback): Promise<() => void> {
    return Promise.resolve(this.subscribe(`blocks:${chainId}`, callback))
  }

  /**
   * Subscribe to transaction updates
   */
  subscribeToTransaction(txHash: string, callback: EventCallback): Promise<() => void> {
    return Promise.resolve(this.subscribe(`tx:${txHash}`, callback))
  }

  /**
   * Subscribe to contract events
   */
  subscribeToContractEvents(
    contractAddress: Address,
    eventName: string,
    callback: EventCallback
  ): Promise<() => void> {
    return Promise.resolve(this.subscribe(`contract:${contractAddress}:${eventName}`, callback))
  }

  /**
   * Subscribe to contract state changes
   */
  subscribeToStateChanges(contractAddress: Address, callback: EventCallback): Promise<() => void> {
    return Promise.resolve(this.subscribe(`state:${contractAddress}`, callback))
  }

  private subscribe(eventKey: string, callback: EventCallback): () => void {
    if (!this.listeners.has(eventKey)) {
      this.listeners.set(eventKey, new Set())
    }
    this.listeners.get(eventKey)!.add(callback)

    // Return unsubscribe function
    return () => {
      const callbacks = this.listeners.get(eventKey)
      if (callbacks) {
        callbacks.delete(callback)
        if (callbacks.size === 0) {
          this.listeners.delete(eventKey)
        }
      }
    }
  }

  /**
   * Connect to WebSocket server (if URL provided)
   */
  connect(wsUrl?: string): void {
    const url = wsUrl || this.wsUrl
    if (!url) {
      console.warn('No WebSocket URL provided, using polling fallback')
      this.startPolling()
      return
    }

    try {
      this.ws = new WebSocket(url)

      this.ws.onopen = () => {
        console.log('WebSocket connected')
        this.reconnectAttempts = 0
        this.notifyListeners('connection', {
          type: 'state_change',
          timestamp: Date.now(),
          data: { connected: true },
        })
      }

      this.ws.onmessage = (event) => {
        try {
          const data = JSON.parse(event.data)
          this.handleMessage(data)
        } catch (error) {
          console.error('Failed to parse WebSocket message:', error)
        }
      }

      this.ws.onerror = (error) => {
        console.error('WebSocket error:', error)
      }

      this.ws.onclose = () => {
        console.log('WebSocket disconnected')
        this.notifyListeners('connection', {
          type: 'state_change',
          timestamp: Date.now(),
          data: { connected: false },
        })
        this.reconnect()
      }
    } catch (error) {
      console.error('Failed to connect WebSocket:', error)
      this.startPolling()
    }
  }

  /**
   * Reconnect to WebSocket
   */
  private reconnect(): void {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.error('Max reconnection attempts reached, using polling fallback')
      this.startPolling()
      return
    }

    this.reconnectAttempts++
    setTimeout(() => {
      if (this.wsUrl) {
        this.connect()
      }
    }, this.reconnectDelay * this.reconnectAttempts)
  }

  /**
   * Start polling fallback (if WebSocket unavailable)
   */
  private startPolling(): void {
    // Polling implementation would go here
    // For now, just log
    console.log('Polling fallback not implemented')
  }

  /**
   * Handle incoming WebSocket message
   */
  private handleMessage(data: any): void {
    const { type, key, event } = data

    if (type === 'event' && key) {
      this.notifyListeners(key, event)
    }
  }

  /**
   * Notify listeners of an event
   */
  private notifyListeners(eventKey: string, event: MonitorEvent): void {
    const callbacks = this.listeners.get(eventKey)
    if (callbacks) {
      callbacks.forEach((callback) => {
        try {
          callback(event)
        } catch (error) {
          console.error('Error in event callback:', error)
        }
      })
    }
  }

  /**
   * Send message to WebSocket server
   */
  send(message: any): void {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(message))
    } else {
      console.warn('WebSocket not connected, message not sent')
    }
  }

  /**
   * Disconnect WebSocket
   */
  disconnect(): void {
    if (this.ws) {
      this.ws.close()
      this.ws = null
    }
    this.listeners.clear()
  }
}

// Singleton instance
let monitorInstance: RealtimeMonitor | null = null

export function getRealtimeMonitor(wsUrl?: string): RealtimeMonitor {
  if (!monitorInstance) {
    monitorInstance = new RealtimeMonitor(wsUrl)
  }
  return monitorInstance
}

/**
 * Hook-style function to monitor contract state
 */
export async function monitorContractState(
  _contractAddress: Address,
  _publicClient: any,
  onStateChange: (state: any) => void,
  pollInterval = 10000
): Promise<() => void> {
  let isMonitoring = true
  let lastState: any = null

  const checkState = async () => {
    if (!isMonitoring) return

    try {
      // Poll contract state
      // In production, this would read actual contract state
      const currentState = {
        paused: false,
        admin: '0x',
        timestamp: Date.now(),
      }

      if (JSON.stringify(currentState) !== JSON.stringify(lastState)) {
        onStateChange(currentState)
        lastState = currentState
      }
    } catch (error) {
      console.error('Error checking contract state:', error)
    }
  }

  const interval = setInterval(checkState, pollInterval)
  await checkState() // Initial check

  return () => {
    isMonitoring = false
    clearInterval(interval)
  }
}
