import React, {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useState,
} from 'react'
import { Socket, io } from 'socket.io-client'
import { toast } from 'sonner'
import {
  AUTH_EVENTS,
  EXTRACTION_EVENTS,
  PLAYER_EVENTS,
  SOUND_EVENTS,
  USER_EVENTS,
  authEvents,
  extractionEvents,
  playerEvents,
  soundEvents,
  userEvents,
} from '../lib/events'
import { extractionsService } from '../lib/api/services/extractions'
import { useAuth } from './AuthContext'

interface SocketContextType {
  socket: Socket | null
  isConnected: boolean
  connectionError: string | null
  isReconnecting: boolean
}

const SocketContext = createContext<SocketContextType | undefined>(undefined)

interface SocketProviderProps {
  children: React.ReactNode
}

export function SocketProvider({ children }: SocketProviderProps) {
  const { user, loading } = useAuth()
  const [socket, setSocket] = useState<Socket | null>(null)
  const [isConnected, setIsConnected] = useState(false)
  const [connectionError, setConnectionError] = useState<string | null>(null)
  const [isReconnecting, setIsReconnecting] = useState(false)

  const fetchAndShowOngoingExtractions = useCallback(async () => {
    try {
      const processingExtractions = await extractionsService.getProcessingExtractions()
      
      processingExtractions.forEach(extraction => {
        const title = extraction.title || 'Processing extraction...'
        toast.loading(`Extracting: ${title}`, {
          id: `extraction-${extraction.id}`,
          duration: Infinity, // Keep it open until status changes
        })
      })
    } catch (error) {
      console.error('Failed to fetch ongoing extractions:', error)
    }
  }, [])

  const createSocket = useCallback(() => {
    if (!user) return null

    // Get socket URL - use relative URL in production with reverse proxy
    const socketUrl = import.meta.env.PROD
      ? '' // Use relative URL in production (same origin as frontend)
      : import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'

    const newSocket = io(socketUrl, {
      withCredentials: true,
      transports: ['polling', 'websocket'],
      timeout: 20000,
      forceNew: true,
      autoConnect: true,
    })

    newSocket.on('connect', () => {
      setIsConnected(true)
      setConnectionError(null)
      setIsReconnecting(false)
      
      // Fetch and show any ongoing extractions
      fetchAndShowOngoingExtractions()
    })

    newSocket.on('disconnect', () => {
      setIsConnected(false)
    })

    newSocket.on('connect_error', error => {
      setConnectionError(`Connection failed: ${error.message}`)
      setIsConnected(false)
      setIsReconnecting(false)
    })

    // Listen for message events
    newSocket.on('user_message', data => {
      toast.info(`Message from ${data.from_user_name}`, {
        description: data.message,
      })
    })

    newSocket.on('broadcast_message', data => {
      toast.warning(`Broadcast from ${data.from_user_name}`, {
        description: data.message,
      })
    })

    // Listen for player events and emit them locally
    newSocket.on('player_state', data => {
      playerEvents.emit(PLAYER_EVENTS.PLAYER_STATE, data)
    })

    // Listen for sound events and emit them locally
    newSocket.on('sound_played', data => {
      soundEvents.emit(SOUND_EVENTS.SOUND_PLAYED, data)
    })

    newSocket.on('sound_favorited', data => {
      soundEvents.emit(SOUND_EVENTS.SOUND_FAVORITED, data)
    })

    // Listen for user events and emit them locally
    newSocket.on('user_credits_changed', data => {
      userEvents.emit(USER_EVENTS.USER_CREDITS_CHANGED, data)
    })

    // Listen for extraction status updates
    newSocket.on('extraction_status_update', data => {
      const { extraction_id, status, title, error } = data
      
      // Emit local event for other components to listen to
      extractionEvents.emit(EXTRACTION_EVENTS.EXTRACTION_STATUS_UPDATED, data)
      
      // Handle specific status events
      switch (status) {
        case 'processing':
          toast.loading(`Extracting: ${title}`, {
            id: `extraction-${extraction_id}`,
            duration: Infinity, // Keep it open until status changes
          })
          break
        case 'completed':
          toast.dismiss(`extraction-${extraction_id}`)
          toast.success(`Extraction complete: ${title}`, {
            duration: 4000,
          })
          extractionEvents.emit(EXTRACTION_EVENTS.EXTRACTION_COMPLETED, data)
          break
        case 'failed':
          toast.dismiss(`extraction-${extraction_id}`)
          toast.error(`Extraction failed: ${title}`, {
            description: error,
            duration: 6000,
          })
          extractionEvents.emit(EXTRACTION_EVENTS.EXTRACTION_FAILED, data)
          break
      }
    })

    return newSocket
  }, [user, fetchAndShowOngoingExtractions])

  // Handle token refresh - reconnect socket with new token
  const handleTokenRefresh = useCallback(() => {
    if (!user || !socket) return

    setIsReconnecting(true)

    // Disconnect current socket
    socket.disconnect()

    // Create new socket with fresh token
    const newSocket = createSocket()
    if (newSocket) {
      setSocket(newSocket)
    }
  }, [user, socket, createSocket])

  // Listen for token refresh events
  useEffect(() => {
    authEvents.on(AUTH_EVENTS.TOKEN_REFRESHED, handleTokenRefresh)

    return () => {
      authEvents.off(AUTH_EVENTS.TOKEN_REFRESHED, handleTokenRefresh)
    }
  }, [handleTokenRefresh])

  // Initial socket connection
  useEffect(() => {
    if (loading) return

    if (!user) {
      if (socket) {
        socket.disconnect()
        setSocket(null)
        setIsConnected(false)
      }
      return
    }

    const newSocket = createSocket()
    if (newSocket) {
      setSocket(newSocket)
    }

    return () => {
      if (newSocket) {
        newSocket.disconnect()
      }
    }
  }, [loading, user, createSocket])

  const value: SocketContextType = {
    socket,
    isConnected,
    connectionError,
    isReconnecting,
  }

  return (
    <SocketContext.Provider value={value}>{children}</SocketContext.Provider>
  )
}

export function useSocket() {
  const context = useContext(SocketContext)
  if (context === undefined) {
    throw new Error('useSocket must be used within a SocketProvider')
  }
  return context
}
