import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
import { AuthenticatedSseHub, type SseConnectionState } from '../src/services/sseHub'
import { useAuthStore } from '../src/stores/auth'

beforeEach(() => {
  setActivePinia(createPinia())
  const auth = useAuthStore()
  auth.initialized = true
  auth.applySession({
    accessToken: 'test-access-token',
    expiresAt: '2026-07-31T12:00:00.000Z',
    user: {
      id: 'bao',
      email: 'bao@nexus.local',
      displayName: 'Bao',
      role: 'owner',
    },
  })
})

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

describe('authenticated SSE hub', () => {
  it('backs off with jitter after a normal EOF instead of reconnecting immediately', async () => {
    vi.useFakeTimers()
    vi.spyOn(Math, 'random').mockReturnValue(0.5)
    const fetchMock = vi.fn(async () => new Response('', {
      status: 200,
      headers: { 'Content-Type': 'text/event-stream' },
    }))
    vi.stubGlobal('fetch', fetchMock)

    const states: SseConnectionState[] = []
    const hub = new AuthenticatedSseHub()
    const unsubscribe = hub.subscribe('/api/v1/events', () => undefined, {
      onStateChange: state => states.push(state),
    })

    await vi.advanceTimersByTimeAsync(0)
    expect(fetchMock).toHaveBeenCalledTimes(1)
    expect(states).toContain('open')
    expect(states.at(-1)).toBe('reconnecting')

    await vi.advanceTimersByTimeAsync(999)
    expect(fetchMock).toHaveBeenCalledTimes(1)

    await vi.advanceTimersByTimeAsync(1)
    expect(fetchMock).toHaveBeenCalledTimes(2)

    unsubscribe()
    hub.closeAll()
  })

  it('reconnects a stalled stream when the heartbeat budget expires', async () => {
    vi.useFakeTimers()
    vi.spyOn(Math, 'random').mockReturnValue(0.5)
    const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
      const stream = new ReadableStream<Uint8Array>({
        start(controller) {
          init?.signal?.addEventListener('abort', () => {
            controller.error(init.signal?.reason ?? new Error('aborted'))
          }, { once: true })
        },
      })
      return new Response(stream, {
        status: 200,
        headers: { 'Content-Type': 'text/event-stream' },
      })
    })
    vi.stubGlobal('fetch', fetchMock)

    const states: SseConnectionState[] = []
    const hub = new AuthenticatedSseHub()
    const unsubscribe = hub.subscribe('/api/v1/events', () => undefined, {
      onStateChange: state => states.push(state),
    })

    await vi.advanceTimersByTimeAsync(45_000)
    expect(states).toContain('error')
    expect(fetchMock).toHaveBeenCalledTimes(1)

    await vi.advanceTimersByTimeAsync(1_000)
    expect(fetchMock).toHaveBeenCalledTimes(2)

    unsubscribe()
    hub.closeAll()
  })
})
