import { io, Socket } from 'socket.io-client'
import { ref } from 'vue'
import { useAgentCatalogStore } from '@/store/agentCatalogStore'
import { useConversationCatalogStore } from '@/store/conversationCatalogStore'
import { useConversationStore } from '@/store/conversationStore'
import { useStreamingStore } from '@/store/streamingStore'
import { toast } from 'vue-sonner'
import { getSocketConfig } from '@/lib/backendUrl'
import { useChatStore } from '@/store/chatStore'
import { useRoute, useRouter } from 'vue-router'
import { DEFAULT_CONVERSATION_TITLE } from '@/constants/conversation'

let globalSocket: Socket | null = null

let reloadScheduled = false
async function scheduleCatalogReload(store: ReturnType<typeof useAgentCatalogStore>) {
  if (reloadScheduled) return
  reloadScheduled = true
  try {
    store.invalidate()
    await store.load(true)
  } finally {
    reloadScheduled = false
  }
}

export function useSocket() {
  const socketRef = ref<Socket | null>(null)
  const agentCatalog = useAgentCatalogStore()
  const conversationCatalog = useConversationCatalogStore()
  const router = useRouter()
  const route = useRoute()

  if (!globalSocket) {
    const { host, path } = getSocketConfig()
    const s = io(host, {
      path,
      transports: ['websocket', 'polling'],
    })

    globalSocket = s
    socketRef.value = s

    // Stores used by chat streaming handlers
    const conversationStore = useConversationStore()
    const streamingStore = useStreamingStore()

    s.on('connect', () => {
      console.log('Socket connected:', s.id)
    })
    s.on('disconnect', () => {
      console.log('Socket disconnected.')
    })

    s.on('agent:access_granted', async ({ agentId: _agentId, agentName }) => {
      toast.success(`You've been granted access to "${agentName}"`)

      await scheduleCatalogReload(agentCatalog)
    })
    s.on('agent:access_revoked', async ({ agentId: _agentId, agentName }) => {
      toast.warning(`Your access to agent "${agentName}" was revoked.`)

      await scheduleCatalogReload(agentCatalog)
    })
    const lastPublishedAt: Record<string, string | undefined> = {}

    s.on('agent:updated', (payload: any) => {
      const agentId = payload.agentId ?? payload.id
      const agentName = payload.agentName ?? payload.name
      const status = payload.status as string
      const isShared = Boolean(payload.isShared)
      const shareCount = payload.shareCount ?? 0

      if (!agentId) return

      const previousActive = agentCatalog.agents.find(
        (a) => a.id === agentId && a.status === 'active'
      )
      const previousDraft = agentCatalog.agents.find(
        (a) => a.id === agentId && a.status === 'draft'
      )
      const previousStatus = (
        status === 'draft' ? previousDraft?.status : previousActive?.status
      ) as string | undefined

      const safeName = agentName || previousActive?.name || previousDraft?.name || 'Agent'

      if (isShared && status === 'draft') {
        delete lastPublishedAt[agentId]
        return
      } else if (isShared) {
        const update: {
          id: string
          isShared: boolean
          shareCount: number
          name?: string
          status?: 'active' | 'draft'
        } = {
          id: agentId,
          isShared,
          shareCount,
        }

        if (status === 'active') update.name = agentName
        if (status === 'active' || status === 'draft') {
          update.status = status
        }

        agentCatalog.updateAgent(update)

        if (status === 'active') {
          setTimeout(async () => {
            await scheduleCatalogReload(agentCatalog)
            const latest = agentCatalog.agents.find((a) => a.id === agentId)
            const publishedAt = latest?.published_at

            if (
              publishedAt &&
              lastPublishedAt[agentId] &&
              lastPublishedAt[agentId] !== publishedAt
            ) {
              toast.info(`New version of agent "${latest?.name || agentName}" has been published`)
            }
            if (publishedAt) lastPublishedAt[agentId] = publishedAt
          }, 600)
        }
      } else {
        const update: {
          id: string
          name?: string
          isShared: boolean
          shareCount: number
          status?: 'active' | 'draft'
          hasUnpublishedChanges?: boolean
          indexing?: { status: string }
        } = {
          id: agentId,
          //name: agentName,
          isShared,
          shareCount,
        }

        if (status === 'draft' || (status === 'active' && agentName)) {
          update.name = agentName
        }

        if (status === 'active' || status === 'draft') update.status = status

        if (status === 'draft' && previousDraft?.indexing) {
          update.indexing = previousDraft.indexing
        }

        agentCatalog.updateAgent(update)

        if (
          status === 'draft' &&
          previousActive &&
          previousStatus === 'active' &&
          !previousActive.hasUnpublishedChanges
        ) {
          toast.info(`"${safeName}" is now in draft mode`)
        }
      }
    })

    s.on('agent:publish_event', async (event: any) => {
      if (event.event_type === 'publish_failed') {
        toast.error(event.error || 'Publishing failed')
        return
      }

      if (event.event_type !== 'publish_completed') return

      const agentId = event.agent_id ?? event.id
      if (!agentId) return

      await scheduleCatalogReload(agentCatalog)

      agentCatalog.agents = agentCatalog.agents.filter(
        (a) => !(a.id === agentId && a.status === 'draft')
      )

      const activeRow = agentCatalog.agents.find((a) => a.id === agentId)
      if (activeRow) {
        activeRow.status = 'active'
        activeRow.hasUnpublishedChanges = false
        activeRow.published_at = event.published_at
        activeRow.published_version = true
      } else {
        agentCatalog.updateAgent({
          id: agentId,
          name: event.agent_name,
          status: 'active',
          published_at: event.published_at,
          published_version: true,
          hasUnpublishedChanges: false,
        })
      }

      const latest = agentCatalog.agents.find((a) => a.id === agentId)
      const publishedAt = latest?.published_at

      if (publishedAt && publishedAt !== lastPublishedAt[agentId]) {
        if (latest?.isShared) {
          toast.info(`New version of agent "${latest.name}" has been published`)
        } else {
          toast.success('Agent published successfully!')
        }
        lastPublishedAt[agentId] = publishedAt
      }
    })

    s.on('agent:deleted', ({ agentId, agentName, deletedBy: _deletedBy }) => {
      const chatStore = useChatStore()

      agentCatalog.removeAgent(agentId)
      scheduleCatalogReload(agentCatalog)
      const isOnAgentPage = route.name === 'EditAgent' && route.params.id === agentId

      if (!isOnAgentPage) {
        toast.error(`Agent "${agentName}" has been deleted`)
      }

      if (chatStore.agentIds.includes(agentId)) {
        const newAgentIds = chatStore.agentIds.filter((id) => id !== agentId)
        chatStore.updateAgents(newAgentIds)

        if (newAgentIds.length === 0) {
          toast.info('The selected agent was deleted. Please select another agent to continue.')
        }
      }

      if (isOnAgentPage) {
        router.push({ name: 'Home' })
      }
    })

    // Chat streaming: token chunks
    s.on('chat_token', (data: any) => {
      const conversationId = data?.conversationId
      if (!conversationId) return

      streamingStore.appendToReplyBuffer(conversationId, data.token)
      const buffer = streamingStore.getReplyBuffer(conversationId)!

      if (conversationId === conversationStore.activeConversationId) {
        conversationStore.setStreamingReply(conversationId, buffer.text)
        conversationStore.setPendingLiveEvent(conversationId, false)
      }

      if (buffer.text === data.token && conversationId === conversationStore.activeConversationId) {
        streamingStore.clearPendingEvent(conversationId)
        conversationStore.setPendingLiveEvent(conversationId, false)
      }
    })

    // Chat streaming: live events
    s.on('chat_event', (data: any) => {
      const conversationId = data?.conversationId
      if (!conversationId) return

      streamingStore.addEventToBuffer(conversationId, data.eventKind, data.eventData)
      const eventBuffer = streamingStore.getEventBuffer(conversationId)!

      if (conversationId === conversationStore.activeConversationId) {
        conversationStore.setLiveEvents(conversationId, eventBuffer.liveEvents)
        conversationStore.setDetailedEventLog(conversationId, eventBuffer.detailedEventLog)
        conversationStore.setPendingLiveEvent(conversationId, eventBuffer.hasPendingLiveEvent)
      }
    })

    // Chat streaming: end of response
    s.on('chat_end', (payload: any) => {
      const conversationId = payload?.conversationId
      if (!conversationId) return

      conversationStore.setStreamingState(conversationId, false)
      conversationStore.setMessages(conversationId, payload.messages)

      if (payload.hasEventLog) {
        const eventLog = Array.isArray(payload.eventLog) ? payload.eventLog : []
        conversationStore.setDetailedEventLog(conversationId, eventLog)
        const lastMessage = payload.messages?.[payload.messages?.length - 1]
        if (lastMessage && lastMessage.role === 'assistant') {
          conversationStore.updateMessage(conversationId, lastMessage.id, {
            hasEventLog: payload.hasEventLog,
            eventLog: eventLog,
          })
        }
      }

      streamingStore.deleteReplyBuffer(conversationId)
      streamingStore.deleteEventBuffer(conversationId)

      if (payload.title && payload.title !== DEFAULT_CONVERSATION_TITLE) {
        conversationStore.updateTitle(conversationId, payload.title)
        conversationCatalog.updateConversationTitle(conversationId, payload.title)
      }
      // useChatStore().setMessages(payload.messages)

      if (conversationStore.activeConversationId === conversationId) {
        useChatStore().updateAgents(payload.agentIds)
        conversationStore.updateAgentIds(conversationId, payload.agentIds)
      }

      if (conversationStore.activeConversationId !== conversationId) {
        toast.success(
          `Conversation "${conversationStore.conversationTitleById(conversationId)}" is ready`
        )
      }
    })

    // Chat streaming: error
    s.on('chat_error', (error: any) => {
      console.error('chat_error', error)
      const conversationId = error?.conversationId
      if (conversationId && conversationId !== useConversationStore().activeConversationId) {
        return
      }
      toast.error(error?.error || 'An unexpected error occurred')
      if (conversationId) {
        conversationStore.setStreamingState(conversationId, false)
      }
    })
  } else {
    // already initialized, just reuse
    socketRef.value = globalSocket
  }

  return socketRef
}
