import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount } from '@vue/test-utils'
import { defineComponent, h, toRef } from 'vue'
import { useBackendEvents } from '../backendEvents'

const { emitMock } = vi.hoisted(() => ({
  emitMock: vi.fn(),
}))

vi.mock('../eventBus', () => ({
  eventBus: {
    emit: emitMock,
  },
}))

class MockEventSource {
  static instances: MockEventSource[] = []
  public onmessage: ((event: MessageEvent) => void) | null = null
  public close = vi.fn(() => {
    this.closed = true
  })
  public closed = false

  constructor(public url: string) {
    MockEventSource.instances.push(this)
  }
}

const TestHarness = defineComponent({
  name: 'BackendEventsHarness',
  props: {
    userId: {
      type: String,
      required: true,
    },
  },
  setup(props) {
    useBackendEvents(toRef(props, 'userId'))
    return () => h('div')
  },
})

describe('useBackendEvents', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    MockEventSource.instances = []
    vi.stubGlobal('EventSource', MockEventSource)
  })

  afterEach(() => {
    vi.unstubAllGlobals()
  })

  it('connects when user id becomes available after mount', async () => {
    const wrapper = mount(TestHarness, { props: { userId: '' } })

    expect(MockEventSource.instances.length).toBe(0)

    await wrapper.setProps({ userId: 'user-1' })

    expect(MockEventSource.instances.length).toBe(1)
    expect(MockEventSource.instances[0]?.url).toBe('/api/events')
  })

  it('reconnects when user id changes and closes previous connection', async () => {
    const wrapper = mount(TestHarness, { props: { userId: 'user-1' } })

    expect(MockEventSource.instances.length).toBe(1)
    const firstConnection = MockEventSource.instances[0]

    await wrapper.setProps({ userId: 'user-2' })

    expect(firstConnection?.close).toHaveBeenCalledTimes(1)
    expect(MockEventSource.instances.length).toBe(2)
    expect(MockEventSource.instances[1]?.url).toBe('/api/events')
  })

  it('emits parsed backend events on message', async () => {
    mount(TestHarness, { props: { userId: 'user-1' } })

    const connection = MockEventSource.instances[0]
    expect(connection).toBeDefined()

    connection?.onmessage?.({
      data: JSON.stringify({ type: 'profile_updated', payload: { id: 'user-1' } }),
    } as MessageEvent)

    expect(emitMock).toHaveBeenCalledWith('profile_updated', {
      type: 'profile_updated',
      payload: { id: 'user-1' },
    })
    expect(emitMock).toHaveBeenCalledWith('sse', {
      type: 'profile_updated',
      payload: { id: 'user-1' },
    })
  })

  it('closes the event source on unmount', () => {
    const wrapper = mount(TestHarness, { props: { userId: 'user-1' } })

    const connection = MockEventSource.instances[0]
    wrapper.unmount()

    expect(connection?.close).toHaveBeenCalledTimes(1)
  })
})
