import { BaseService } from '@/core/base/BaseService'
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import { useToast } from '@/core/composables/useToast'
import { eventBus } from '@/core/event-bus'
import type { Ref } from 'vue'
import { ref } from 'vue'

interface WebSocketConfig {
  enabled: boolean
  reconnectDelay: number
  maxReconnectAttempts: number
  fallbackToPolling: boolean
  pollingInterval: number
}

export class WalletWebSocketService extends BaseService {
  protected readonly metadata = {
    name: 'WalletWebSocketService',
    version: '1.0.0',
    dependencies: ['PaymentService', 'AuthService', 'WalletService']
  }

  private ws: WebSocket | null = null
  private reconnectTimer: NodeJS.Timeout | null = null
  private reconnectAttempts = 0
  private pollingTimer: NodeJS.Timeout | null = null
  private stabilityTimer: NodeJS.Timeout | null = null
  private usingPollingFallback = false
  private consecutiveFailures = 0
  private config: WebSocketConfig = {
    enabled: true,
    reconnectDelay: 1000,
    maxReconnectAttempts: 5,
    fallbackToPolling: true,
    pollingInterval: 10000 // 10 seconds
  }

  // Service dependencies (auto-injected by BaseService)
  protected paymentService: any
  protected walletService: any

  // WebSocket state
  public readonly isConnected: Ref<boolean> = ref(false)
  public readonly connectionStatus: Ref<string> = ref('disconnected')

  private toast = useToast()

  protected async onInitialize(): Promise<void> {
    console.log('WalletWebSocketService: Starting initialization...')

    // Get services (already injected by BaseService)
    this.paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) as any
    this.walletService = injectService(SERVICE_TOKENS.WALLET_SERVICE) as any

    console.log('WalletWebSocketService: Services injected', {
      paymentService: !!this.paymentService,
      walletService: !!this.walletService
    })

    // Load config from app config if available
    const appConfig = (window as any).appConfig
    if (appConfig?.modules?.wallet?.config?.websocket) {
      this.config = { ...this.config, ...appConfig.modules.wallet.config.websocket }
    }

    console.log('WalletWebSocketService: Config loaded', this.config)

    // Only proceed if WebSocket is enabled
    if (!this.config.enabled) {
      console.log('WalletWebSocketService: WebSocket disabled in config')
      return
    }

    // Register with VisibilityService for connection management
    if (this.visibilityService) {
      this.visibilityService.registerService(
        this.metadata.name,
        this.onResume.bind(this),
        this.onPause.bind(this)
      )
      console.log('WalletWebSocketService: Registered with VisibilityService')
    } else {
      console.warn('WalletWebSocketService: VisibilityService not available')
    }

    // Listen for authentication events to connect when user logs in
    eventBus.on('auth:login', () => {
      console.log('WalletWebSocketService: Auth login detected, attempting connection...')
      setTimeout(() => {
        // Small delay to ensure PaymentService has processed the auth event
        this.connectIfNeeded()
      }, 500)
    })

    eventBus.on('auth:logout', () => {
      console.log('WalletWebSocketService: Auth logout detected, disconnecting...')
      this.disconnect()
    })

    console.log('WalletWebSocketService: Registered auth event listeners')

    // Initial connection attempt (will fail if not authenticated, but that's OK)
    console.log('WalletWebSocketService: Attempting initial connection...')
    await this.connectIfNeeded()
  }

  /**
   * Connect to LNbits WebSocket if we have wallet credentials
   */
  private async connectIfNeeded(): Promise<void> {
    console.log('WalletWebSocketService: Checking for wallet credentials...')

    const wallet = this.paymentService?.getPreferredWallet?.()
    console.log('WalletWebSocketService: Wallet check result', {
      hasPaymentService: !!this.paymentService,
      hasGetPreferredWallet: !!this.paymentService?.getPreferredWallet,
      wallet: wallet ? { id: wallet.id, hasInkey: !!wallet.inkey } : null
    })

    if (wallet?.inkey) {
      console.log('WalletWebSocketService: Connecting with wallet inkey...')
      await this.connect(wallet.inkey)
    } else {
      console.log('WalletWebSocketService: No wallet available for WebSocket connection')
    }
  }

  /**
   * Establish WebSocket connection to LNbits
   */
  private async connect(walletInkey: string): Promise<void> {
    try {
      console.log('WalletWebSocketService: Starting connection process...')

      // Close existing connection if any
      this.disconnect()

      // Build WebSocket URL
      const baseUrl = import.meta.env.VITE_LNBITS_BASE_URL || import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000'
      const wsProtocol = baseUrl.startsWith('https') ? 'wss:' : 'ws:'
      const host = baseUrl.replace(/^https?:\/\//, '').replace(/\/$/, '')
      const wsUrl = `${wsProtocol}//${host}/api/v1/ws/${walletInkey}`

      console.log('WalletWebSocketService: Connection details', {
        baseUrl,
        wsProtocol,
        host,
        wsUrl,
        walletInkey: walletInkey.substring(0, 8) + '...' // Only show first 8 chars for security
      })

      this.connectionStatus.value = 'connecting'

      // Create WebSocket connection with detailed monitoring
      this.ws = new WebSocket(wsUrl)

      // Log initial WebSocket state
      console.log('WalletWebSocketService: WebSocket created', {
        readyState: this.ws.readyState,
        url: this.ws.url,
        protocol: this.ws.protocol,
        extensions: this.ws.extensions
      })

      // Set up event handlers with enhanced diagnostics
      this.ws.onopen = (event) => this.handleOpenWithDiagnostics(event)
      this.ws.onmessage = (event) => this.handleMessageWithDiagnostics(event)
      this.ws.onclose = (event) => this.handleCloseWithDiagnostics(event)
      this.ws.onerror = (event) => this.handleWebSocketErrorWithDiagnostics(event)

      console.log('WalletWebSocketService: WebSocket created, waiting for connection...')

    } catch (error) {
      console.error('WalletWebSocketService: Failed to create WebSocket', error)
      this.connectionStatus.value = 'error'
      this.scheduleReconnect()
    }
  }

  /**
   * Handle WebSocket connection opened
   */
  private handleOpen(_event: Event): void {
    // Record connection start time for duration tracking
    this.connectionStartTime = Date.now()

    console.log('WalletWebSocketService: Connected successfully')
    this.isConnected.value = true
    this.connectionStatus.value = 'connected'

    // Clear any pending reconnect timer
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
      this.reconnectTimer = null
    }

    // Only reset counters after connection has been stable for 10 seconds
    this.stabilityTimer = setTimeout(() => {
      if (this.isConnected.value) {
        console.log('WalletWebSocketService: Connection stable, resetting retry counters')
        this.reconnectAttempts = 0
        this.consecutiveFailures = 0

        // Stop polling fallback if we have a stable WebSocket connection
        if (this.usingPollingFallback) {
          this.stopPollingFallback()
          if (this.toast) {
            this.toast.success('WebSocket reconnected', {
              description: 'Real-time updates restored via WebSocket connection.'
            })
          }
        }
      }
      this.stabilityTimer = null
    }, 10000)

    // Send a ping to test connection stability
    this.sendPing()
  }

  /**
   * Handle incoming WebSocket messages
   */
  private handleMessage(event: MessageEvent): void {
    try {
      const data = JSON.parse(event.data)
      console.log('WalletWebSocketService: Received message', data)

      // Handle payment notification first (this updates transactions)
      if (data.payment) {
        this.handlePaymentNotification(data.payment)
      }

      // Handle wallet balance update
      if (data.wallet_balance !== undefined) {
        console.log('WalletWebSocketService: Processing balance update', {
          newBalance: data.wallet_balance,
          hasPayment: !!data.payment,
          paymentAmount: data.payment?.amount
        })

        let finalBalance = data.wallet_balance

        // For outgoing payments, LNbits sends pre-payment balance, so we need to adjust
        // For incoming payments, LNbits sends post-payment balance, so use as-is
        if (data.payment && data.payment.amount < 0) {
          // Outgoing payment - subtract the payment amount from the balance
          const paymentSats = Math.abs(data.payment.amount) / 1000
          finalBalance = data.wallet_balance - paymentSats
          console.log('WalletWebSocketService: Adjusting balance for outgoing payment', {
            originalBalance: data.wallet_balance,
            paymentSats: paymentSats,
            finalBalance: finalBalance
          })
        } else if (data.payment && data.payment.amount > 0) {
          // Incoming payment - use balance as-is (already post-payment)
          console.log('WalletWebSocketService: Using balance as-is for incoming payment', {
            balance: data.wallet_balance
          })
        } else {
          // No payment in message - use balance as-is
          console.log('WalletWebSocketService: Using balance as-is (no payment)', {
            balance: data.wallet_balance
          })
        }

        console.log('WalletWebSocketService: Updating balance to', finalBalance, 'sats')
        this.updateWalletBalance(finalBalance)
      }

    } catch (error) {
      console.error('WalletWebSocketService: Failed to parse message', error)
    }
  }

  /**
   * Update wallet balance via PaymentService
   */
  private updateWalletBalance(balanceSats: number): void {
    console.log('WalletWebSocketService: Updating balance to', balanceSats, 'sats')

    // Convert sats to millisats for internal storage (LNbits WebSocket sends balance in sats)
    const balanceMsat = balanceSats * 1000

    // Get the wallet ID we're connected to
    const wallet = this.paymentService?.getPreferredWallet?.()
    const walletId = wallet?.id

    // Update balance via PaymentService (which manages wallet state)
    if (this.paymentService?.updateWalletBalance) {
      this.paymentService.updateWalletBalance(balanceMsat, walletId)
    }
  }

  /**
   * Handle incoming payment notification
   */
  private handlePaymentNotification(payment: any): void {
    console.log('WalletWebSocketService: Payment notification', payment)

    // Add transaction to wallet service
    if (this.walletService?.addTransaction) {
      this.walletService.addTransaction(payment)
    }

    // Show toast notification for incoming payments
    if (payment.amount > 0 && !payment.pending) {
      const amountSats = Math.abs(payment.amount / 1000)
      if (payment.amount > 0) {
        this.toast.success(`Received ${amountSats} sats!`)
      } else {
        this.toast.info(`Sent ${amountSats} sats`)
      }
    }
  }

  /**
   * Handle WebSocket connection closed
   */
  private handleClose(event: CloseEvent): void {
    console.log('WalletWebSocketService: Connection closed', event.code, event.reason)
    this.isConnected.value = false
    this.connectionStatus.value = 'disconnected'
    this.ws = null

    // Clear stability timer since connection closed
    if (this.stabilityTimer) {
      clearTimeout(this.stabilityTimer)
      this.stabilityTimer = null
    }

    // Handle specific close codes
    if (event.code === 1006) {
      console.warn('WalletWebSocketService: Abnormal closure detected - possible server issue')
      // For code 1006, increase delay to avoid overwhelming server
      this.scheduleReconnect(true)
    } else if (event.code !== 1000) {
      // Normal reconnection for other non-normal closures
      this.scheduleReconnect(false)
    }
    // Code 1000 = normal closure, don't reconnect
  }

  /**
   * Handle WebSocket error
   */
  private handleWebSocketError(event: Event): void {
    console.error('WalletWebSocketService: WebSocket error', event)
    this.connectionStatus.value = 'error'

    // Additional error details if available
    if (this.ws) {
      console.error('WalletWebSocketService: WebSocket state:', this.ws.readyState)
      console.error('WalletWebSocketService: WebSocket URL:', this.ws.url)
    }

    // Check if this is a network connectivity issue
    if (!navigator.onLine) {
      console.log('WalletWebSocketService: Network appears to be offline')
      this.connectionStatus.value = 'offline'
    }
  }

  /**
   * Schedule a reconnection attempt
   */
  private scheduleReconnect(isAbnormalClosure = false): void {
    this.consecutiveFailures++

    // Don't reconnect if we've exceeded max attempts
    if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
      console.log('WalletWebSocketService: Max reconnection attempts reached')
      this.connectionStatus.value = 'failed'

      // Check if we should fallback to polling
      if (this.config.fallbackToPolling && !this.usingPollingFallback) {
        console.log('WalletWebSocketService: Falling back to polling for balance updates')
        this.startPollingFallback()
      } else {
        // Show user notification about WebSocket issues
        if (this.toast) {
          this.toast.info('Real-time balance updates temporarily unavailable', {
            description: 'WebSocket connection failed. Balance will update on page refresh.'
          })
        }
      }
      return
    }

    // Clear any existing reconnect timer
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
    }

    // Calculate delay with exponential backoff
    let delay = this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts)

    // For abnormal closures (1006), use longer delays to avoid overwhelming server
    if (isAbnormalClosure) {
      delay = Math.max(delay, 5000) // Minimum 5 second delay for 1006 errors
    }

    this.reconnectAttempts++

    console.log(`WalletWebSocketService: Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts})${isAbnormalClosure ? ' [abnormal closure]' : ''}`)
    this.connectionStatus.value = 'reconnecting'

    this.reconnectTimer = setTimeout(() => {
      this.connectIfNeeded()
    }, delay)
  }

  /**
   * Disconnect WebSocket connection
   */
  public disconnect(): void {
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
      this.reconnectTimer = null
    }

    if (this.stabilityTimer) {
      clearTimeout(this.stabilityTimer)
      this.stabilityTimer = null
    }

    if (this.ws) {
      // Close with normal closure code
      this.ws.close(1000, 'Client disconnect')
      this.ws = null
    }

    // Stop polling fallback
    this.stopPollingFallback()

    this.isConnected.value = false
    this.connectionStatus.value = 'disconnected'
  }

  /**
   * Resume connection when app becomes visible
   */
  private async onResume(): Promise<void> {
    console.log('WalletWebSocketService: Resuming connection')

    // Try WebSocket first, then fallback to polling if needed
    if (!this.isConnected.value && !this.usingPollingFallback) {
      this.reconnectAttempts = 0
      await this.connectIfNeeded()
    } else if (this.usingPollingFallback) {
      // Resume polling if we were using fallback
      this.schedulePolling()
    }
  }

  /**
   * Pause connection when app loses visibility
   */
  private async onPause(): Promise<void> {
    console.log('WalletWebSocketService: Pausing connection')

    // Disconnect WebSocket to save battery
    if (this.ws) {
      this.ws.close(1000, 'App paused')
      this.ws = null
      this.isConnected.value = false
    }

    // Pause polling to save battery
    if (this.pollingTimer) {
      clearTimeout(this.pollingTimer)
      this.pollingTimer = null
    }

    // Clear reconnect timers
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer)
      this.reconnectTimer = null
    }
  }

  /**
   * Send ping to test connection
   */
  private sendPing(): void {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      try {
        // Send a simple ping message (browser WebSocket doesn't have ping method)
        // Most WebSocket servers respond to text messages as keepalive
        this.ws.send(JSON.stringify({ type: 'ping' }))
      } catch (error) {
        console.log('WalletWebSocketService: Ping failed, connection may be unstable')
      }
    }
  }

  /**
   * Manual reconnection method
   */
  public async reconnect(): Promise<void> {
    console.log('WalletWebSocketService: Manual reconnection triggered')
    this.reconnectAttempts = 0

    // Disconnect current connection if any
    this.disconnect()

    // Wait a moment before reconnecting
    setTimeout(() => {
      this.connectIfNeeded()
    }, 1000)
  }

  /**
   * Clean up on service destruction
   */
  public async cleanup(): Promise<void> {
    this.disconnect()
  }

  /**
   * Start polling fallback when WebSocket fails persistently
   */
  private startPollingFallback(): void {
    if (this.usingPollingFallback) return

    this.usingPollingFallback = true
    this.connectionStatus.value = 'polling-fallback'

    console.log(`WalletWebSocketService: Starting polling fallback (${this.config.pollingInterval}ms interval)`)

    // Show user notification
    if (this.toast) {
      this.toast.info('Using backup connection', {
        description: 'Real-time updates temporarily using polling instead of WebSocket.'
      })
    }

    this.schedulePolling()
  }

  /**
   * Stop polling fallback
   */
  private stopPollingFallback(): void {
    if (!this.usingPollingFallback) return

    this.usingPollingFallback = false

    if (this.pollingTimer) {
      clearTimeout(this.pollingTimer)
      this.pollingTimer = null
    }

    console.log('WalletWebSocketService: Stopped polling fallback')
  }

  /**
   * Schedule next polling attempt
   */
  private schedulePolling(): void {
    if (!this.usingPollingFallback) return

    this.pollingTimer = setTimeout(() => {
      this.performPollingUpdate()
    }, this.config.pollingInterval)
  }

  /**
   * Perform a polling-based balance update
   */
  private async performPollingUpdate(): Promise<void> {
    try {
      const wallet = this.paymentService?.getPreferredWallet?.()
      if (!wallet?.inkey) {
        console.log('WalletWebSocketService: No wallet available for polling')
        this.schedulePolling() // Continue polling
        return
      }

      // Fetch balance from LNbits API
      const baseUrl = import.meta.env.VITE_LNBITS_BASE_URL || import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000'
      const response = await fetch(`${baseUrl}/api/v1/wallet`, {
        headers: {
          'X-Api-Key': wallet.inkey
        }
      })

      if (!response.ok) {
        console.error('WalletWebSocketService: Polling failed', response.status, response.statusText)
        this.schedulePolling() // Continue polling despite error
        return
      }

      const data = await response.json()

      if (data.balance !== undefined) {
        // LNbits API returns balance in millisats
        const balanceMsat = parseInt(data.balance)
        console.log('WalletWebSocketService: Polling update - balance:', balanceMsat, 'msat')

        // Update balance via PaymentService
        if (this.paymentService?.updateWalletBalance) {
          this.paymentService.updateWalletBalance(balanceMsat, wallet.id)
        }
      }

      // Schedule next polling
      this.schedulePolling()

    } catch (error) {
      console.error('WalletWebSocketService: Polling error', error)
      this.schedulePolling() // Continue polling despite error
    }
  }

  /**
   * Enhanced diagnostic handlers for WebSocket events
   */
  private handleOpenWithDiagnostics(event: Event): void {
    const now = Date.now()
    console.log('WalletWebSocketService: WebSocket OPEN event', {
      timestamp: now,
      readyState: this.ws?.readyState,
      url: this.ws?.url,
      protocol: this.ws?.protocol,
      extensions: this.ws?.extensions,
      event: event
    })

    // Call original handler
    this.handleOpen(event)

    // Schedule a delayed check to see if connection stays open
    setTimeout(() => {
      if (this.ws) {
        console.log('WalletWebSocketService: Connection health check (5s after open)', {
          readyState: this.ws.readyState,
          readyStateText: this.getReadyStateText(this.ws.readyState),
          isConnected: this.isConnected.value,
          status: this.connectionStatus.value
        })
      }
    }, 5000)
  }

  private handleMessageWithDiagnostics(event: MessageEvent): void {
    console.log('WalletWebSocketService: WebSocket MESSAGE event', {
      timestamp: Date.now(),
      dataLength: event.data?.length || 0,
      dataType: typeof event.data,
      readyState: this.ws?.readyState,
      preview: event.data?.substring ? event.data.substring(0, 100) + '...' : event.data
    })

    // Call original handler
    this.handleMessage(event)
  }

  private handleCloseWithDiagnostics(event: CloseEvent): void {
    const now = Date.now()
    console.log('WalletWebSocketService: WebSocket CLOSE event', {
      timestamp: now,
      code: event.code,
      reason: event.reason,
      wasClean: event.wasClean,
      readyState: this.ws?.readyState,
      readyStateText: this.ws ? this.getReadyStateText(this.ws.readyState) : 'null',
      closeCodeMeaning: this.getCloseCodeMeaning(event.code),
      connectionDuration: this.getConnectionDuration(now)
    })

    // Call original handler
    this.handleClose(event)
  }

  private handleWebSocketErrorWithDiagnostics(event: Event): void {
    console.error('WalletWebSocketService: WebSocket ERROR event', {
      timestamp: Date.now(),
      readyState: this.ws?.readyState,
      readyStateText: this.ws ? this.getReadyStateText(this.ws.readyState) : 'null',
      url: this.ws?.url,
      event: event,
      networkStatus: navigator.onLine ? 'online' : 'offline',
      userAgent: navigator.userAgent
    })

    // Call original handler
    this.handleWebSocketError(event)
  }

  private getReadyStateText(readyState: number): string {
    switch (readyState) {
      case WebSocket.CONNECTING: return 'CONNECTING (0)'
      case WebSocket.OPEN: return 'OPEN (1)'
      case WebSocket.CLOSING: return 'CLOSING (2)'
      case WebSocket.CLOSED: return 'CLOSED (3)'
      default: return `UNKNOWN (${readyState})`
    }
  }

  private getCloseCodeMeaning(code: number): string {
    switch (code) {
      case 1000: return 'Normal Closure'
      case 1001: return 'Going Away'
      case 1002: return 'Protocol Error'
      case 1003: return 'Unsupported Data'
      case 1005: return 'No Status Rcvd'
      case 1006: return 'Abnormal Closure'
      case 1007: return 'Invalid frame payload data'
      case 1008: return 'Policy Violation'
      case 1009: return 'Message Too Big'
      case 1010: return 'Mandatory Extension'
      case 1011: return 'Internal Server Error'
      case 1015: return 'TLS Handshake'
      default: return `Unknown Code (${code})`
    }
  }

  private connectionStartTime = 0

  private getConnectionDuration(closeTime: number): string {
    if (this.connectionStartTime === 0) return 'Unknown'
    const duration = closeTime - this.connectionStartTime
    return `${duration}ms`
  }
}

export default WalletWebSocketService