import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { SseProvider } from '../SseProvider'

const mockInvalidateQueries = vi.fn()

vi.mock('@tanstack/react-query', async () => {
  const actual = await vi.importActual('@tanstack/react-query')
  return { ...actual, useQueryClient: () => ({ invalidateQueries: mockInvalidateQueries }) }
})

vi.mock('@/shared/model/auth', () => ({
  $token: {
    defaultState: null,
    subscribe: () => () => {},
    getState: () => null,
    watch: () => () => {},
    on: () => {},
    reset: () => {},
  },
}))

let mockToken: string | null = null
let mockEventHandlers: Record<string, (event: MessageEvent) => void> = {}
let mockCloseCalls = 0

class MockEventSource {
  url: string
  constructor(url: string) {
    this.url = url
    mockCloseCalls = 0
    mockEventHandlers = {}
  }
  addEventListener(type: string, handler: (event: MessageEvent) => void) {
    mockEventHandlers[type] = handler
  }
  removeEventListener(type: string, _handler: (event: MessageEvent) => void) {
    delete mockEventHandlers[type]
  }
  close() {
    mockCloseCalls++
  }
}

vi.mock('@/shared/lib/sse', () => ({
  createEventStream: (token: string) => {
    mockToken = token
    return new MockEventSource(`/api/sse/stream?token=${token}`) as unknown as EventSource
  },
}))

vi.mock('effector-react', async () => {
  const actual = await vi.importActual('effector-react')
  return { ...actual, useUnit: () => mockToken }
})

function renderSse() {
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } })
  return render(
    <QueryClientProvider client={qc}>
      <SseProvider />
    </QueryClientProvider>,
  )
}

describe('SseProvider', () => {
  afterEach(() => {
    mockToken = null
    mockInvalidateQueries.mockReset()
    mockCloseCalls = 0
    mockEventHandlers = {}
  })

  it('renders nothing (returns null)', () => {
    mockToken = null
    const { container } = renderSse()
    expect(container.innerHTML).toBe('')
  })

  it('does not create EventSource when token is null', () => {
    mockToken = null
    renderSse()
    expect(mockToken).toBeNull()
  })

  it('creates EventSource when token is set', () => {
    mockToken = 'test-jwt'
    renderSse()
    expect(mockToken).toBe('test-jwt')
  })

  it('closes EventSource on unmount', () => {
    mockToken = 'test-jwt'
    const { unmount } = renderSse()
    expect(mockCloseCalls).toBe(0)
    unmount()
    expect(mockCloseCalls).toBe(1)
  })

  it('invalidates unread-count and conversations on message:new', () => {
    mockToken = 'test-jwt'
    renderSse()
    const handler = mockEventHandlers['message:new']
    expect(handler).toBeDefined()
    handler(new MessageEvent('message:new', { data: JSON.stringify({ orderId: 'o1' }) }))

    expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['me', 'messages', 'unread-count'] })
    expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['me', 'conversations'] })
    expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['me', 'orders', 'o1'] })
    expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'o1'] })
  })

  it('invalidates order queries on order:statusChanged', () => {
    mockToken = 'test-jwt'
    renderSse()
    const handler = mockEventHandlers['order:statusChanged']
    handler(new MessageEvent('order:statusChanged', { data: JSON.stringify({ orderId: 'o2' }) }))
    expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['me', 'orders', 'o2'] })
    expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'o2'] })
  })

  it('invalidates order queries on order:updated', () => {
    mockToken = 'test-jwt'
    renderSse()
    const handler = mockEventHandlers['order:updated']
    handler(new MessageEvent('order:updated', { data: JSON.stringify({ orderId: 'o3' }) }))
    expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['me', 'orders', 'o3'] })
    expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'o3'] })
  })

  it('invalidates admin queries on order:new', () => {
    mockToken = 'test-jwt'
    renderSse()
    const handler = mockEventHandlers['order:new']
    handler(new MessageEvent('order:new', { data: JSON.stringify({ orderId: 'o4' }) }))
    expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders', 'summary'] })
    expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['admin', 'orders'] })
  })

  it('handles invalid JSON gracefully', () => {
    mockToken = 'test-jwt'
    renderSse()
    const handler = mockEventHandlers['message:new']
    expect(() => {
      handler(new MessageEvent('message:new', { data: ':heartbit' }))
    }).not.toThrow()
    expect(mockInvalidateQueries).not.toHaveBeenCalled()
  })
})
