/**
 * Pusher composable for real-time notifications
 *
 * Works on Vercel (serverless) and Capacitor apps.
 * Falls back to polling if Pusher is not configured.
 */

import type PusherType from 'pusher-js'
import type { Channel } from 'pusher-js'

// Cache for the Pusher class to avoid re-importing
let PusherClass: typeof PusherType | null = null

// Module-level logger for getPusherClass (uses fixed tag since it's outside composable context)
const pusherModuleLog = createLogger('Pusher')

/**
 * Lazy-load Pusher class, handling ESM/CJS interop issues
 * pusher-js v8.x may export differently in different environments
 */
async function getPusherClass(): Promise<typeof PusherType | null> {
  if (PusherClass) {
    return PusherClass
  }

  if (!import.meta.client) {
    return null
  }

  try {
    const pusherModule = await import('pusher-js')

    // Helper to check if something is a constructor function
    const isConstructor = (value: unknown): value is typeof PusherType => {
      return typeof value === 'function' && value.prototype && value.prototype.constructor === value
    }

    let candidate: unknown = null

    // Try different export patterns
    // 1. Default export
    if ((pusherModule as unknown as { default?: unknown }).default) {
      candidate = (pusherModule as unknown as { default: unknown }).default
    } else if ((pusherModule as unknown as { Pusher?: unknown }).Pusher) {
      // 2. Named export 'Pusher'
      candidate = (pusherModule as unknown as { Pusher: unknown }).Pusher
    } else if (typeof pusherModule === 'function') {
      // 3. Module itself might be the constructor
      candidate = pusherModule
    } else if (typeof pusherModule === 'object' && pusherModule !== null) {
      // 4. Check for common export patterns in the module object
      const moduleObj = pusherModule as Record<string, unknown>
      // Look for a constructor-like property
      for (const key of ['Pusher', 'default', 'pusher', 'PusherClient']) {
        if (typeof moduleObj[key] === 'function') {
          candidate = moduleObj[key]
          break
        }
      }
    }

    // Validate that we found a constructor
    if (!candidate) {
      pusherModuleLog.error('Could not find Pusher constructor in module:', pusherModule)
      return null
    }

    if (!isConstructor(candidate)) {
      pusherModuleLog.error('Found Pusher export but it is not a constructor:', candidate)
      pusherModuleLog.error('Module structure:', Object.keys(pusherModule as Record<string, unknown>))
      return null
    }

    PusherClass = candidate as typeof PusherType
    return PusherClass
  } catch (error) {
    pusherModuleLog.error('Failed to load pusher-js:', error)
    return null
  }
}

export const usePusher = () => {
  const pusherInstance = useState<PusherType | null>('pusher:instance', () => null)
  const isConnected = useState<boolean>('pusher:connected', () => false)
  // Promise-based lock to prevent concurrent initialization
  const initializationPromise = useState<Promise<PusherType | null> | null>('pusher:init-promise', () => null)

  const config = useRuntimeConfig()
  const { isCapacitor, baseURL } = useCapacitor()
  const log = useLogger('Pusher')

  // Intercept fetch/XMLHttpRequest in Capacitor to rewrite /pusher/auth requests
  // This runs once when the composable is first called
  if (import.meta.client && isCapacitor.value && baseURL.value) {
    // Use a global flag to ensure we only patch once
    const patchKey = 'pusher-fetch-patched'
    if (!(window as unknown as Record<string, boolean>)[patchKey]) {
      (window as unknown as Record<string, boolean>)[patchKey] = true

      const originalFetch = window.fetch
      const authEndpoint = `${baseURL.value}/api/user/notifications/pusher-auth`
      const { getAuthToken } = useCapacitor()

      // Patch fetch to intercept /pusher/auth requests
      window.fetch = function (input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
        const url = typeof input === 'string' ? input : input instanceof URL ? input.href : (input as Request).url

        // If Pusher is trying to use /pusher/auth, rewrite it to our endpoint
        if (url.includes('/pusher/auth') || url.endsWith('/pusher/auth')) {
          const newInput = typeof input === 'string'
            ? authEndpoint
            : input instanceof URL
              ? new URL(authEndpoint)
              : new Request(authEndpoint, input)

          // Add Bearer token if available
          const token = getAuthToken()
          const headers = new Headers(init?.headers)
          if (token && !headers.has('Authorization')) {
            headers.set('Authorization', `Bearer ${token}`)
          }
          headers.set('Content-Type', 'application/json')
          headers.set('Accept', 'application/json')

          return originalFetch.call(this, newInput, {
            ...init,
            headers
          })
        }

        return originalFetch.call(this, input, init)
      }

      // Also patch XMLHttpRequest for older Pusher-js versions
      const originalXHROpen = XMLHttpRequest.prototype.open
      XMLHttpRequest.prototype.open = function (method: string, url: string | URL, async?: boolean, username?: string | null, password?: string | null) {
        const urlString = typeof url === 'string' ? url : url.href
        if (urlString.includes('/pusher/auth') || urlString.endsWith('/pusher/auth')) {
          return originalXHROpen.call(this, method, authEndpoint, async ?? true, username, password)
        }
        return originalXHROpen.call(this, method, url, async ?? true, username, password)
      }

      const originalXHRSend = XMLHttpRequest.prototype.send
      XMLHttpRequest.prototype.send = function (body?: XMLHttpRequestBodyInit | null) {
        // Add Bearer token if available
        const token = getAuthToken()
        // Check if headers were already set by checking the request headers
        try {
          if (token) {
            this.setRequestHeader('Authorization', `Bearer ${token}`)
          }
          this.setRequestHeader('Content-Type', 'application/json')
          this.setRequestHeader('Accept', 'application/json')
        } catch {
          // Headers might already be set, ignore
        }
        return originalXHRSend.call(this, body ?? null)
      }
    }
  }

  // Get Pusher config - check runtimeConfig first, then fallback to environment
  const pusherKey = (config.public.pusherKey as string | undefined) || (import.meta.client ? ((window as unknown as Record<string, unknown>).__PUSHER_KEY__ as string | undefined) : undefined)
  const pusherCluster = (config.public.pusherCluster as string | undefined) || (import.meta.client ? ((window as unknown as Record<string, unknown>).__PUSHER_CLUSTER__ as string | undefined) : undefined)

  async function initializePusher() {
    // Early return if instance already exists
    if (pusherInstance.value) {
      return pusherInstance.value
    }

    // If initialization is already in progress, return the existing promise
    if (initializationPromise.value) {
      return initializationPromise.value
    }

    // Start initialization and store the promise to prevent concurrent calls
    const initPromise = (async () => {
      try {
        if (!pusherKey || !pusherCluster) {
          log.warn('Not configured. Set NUXT_PUBLIC_PUSHER_KEY and NUXT_PUBLIC_PUSHER_CLUSTER environment variables.')
          log.warn('Current config:', { pusherKey: !!pusherKey, pusherCluster: !!pusherCluster, isCapacitor: isCapacitor.value })
          return null
        }

        // Lazy-load Pusher class
        const Pusher = await getPusherClass()
        if (!Pusher) {
          log.error('Failed to load Pusher class')
          return null
        }

        // Validate Pusher is actually a constructor before using it
        if (typeof Pusher !== 'function') {
          log.error('Pusher class is not a function:', typeof Pusher, Pusher)
          return null
        }

        // Double-check instance wasn't created while we were loading (shouldn't happen, but defensive)
        if (pusherInstance.value) {
          return pusherInstance.value
        }

        // In Capacitor, use the full backend URL for auth endpoint
        const authEndpoint = isCapacitor.value && baseURL.value
          ? `${baseURL.value}/api/user/notifications/pusher-auth`
          : '/api/user/notifications/pusher-auth'

        // Pusher configuration
        // For Capacitor: fetch/XHR interception (set up above) handles auth automatically
        // For web: use standard authEndpoint with cookies
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const pusherConfig: any = {
          cluster: pusherCluster,
          enabledTransports: ['ws', 'wss'],
          forceTLS: true
        }

        if (!isCapacitor.value) {
          pusherConfig.authEndpoint = authEndpoint
          pusherConfig.auth = {
            headers: {}
          }
        }

        const pusher = new Pusher(pusherKey, pusherConfig)

        pusher.connection.bind('connected', () => {
          isConnected.value = true
        })

        pusher.connection.bind('disconnected', () => {
          isConnected.value = false
        })

        pusher.connection.bind('error', (error: Error) => {
          log.error('Connection error:', error)
          isConnected.value = false
        })

        pusherInstance.value = pusher
        return pusher
      } finally {
        // Clear the initialization promise when done (success or failure)
        initializationPromise.value = null
      }
    })()

    // Store the promise so concurrent calls can await it
    initializationPromise.value = initPromise
    return initPromise
  }

  async function subscribeToUserChannel(userId: string): Promise<Channel | null> {
    const pusher = await initializePusher()
    if (!pusher) {
      log.warn('Cannot subscribe: Pusher not initialized')
      return null
    }

    const channelName = `private-user-${userId}`
    const channel = pusher.subscribe(channelName)

    channel.bind('pusher:subscription_error', (error: unknown) => {
      log.error('Subscription error for channel:', channelName, error)
    })

    return channel
  }

  function disconnect() {
    if (pusherInstance.value) {
      pusherInstance.value.disconnect()
      pusherInstance.value = null
      isConnected.value = false
    }
  }

  return {
    pusher: pusherInstance,
    isConnected: computed(() => isConnected.value),
    initializePusher,
    subscribeToUserChannel,
    disconnect
  }
}
