import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'

vi.mock('@/stores/auth', () => ({
  useAuthStore: () => ({ token: 'jwt-token' }),
}))

import { useAcpStream } from './useAcpStream'

class MockWebSocket {
  static CONNECTING = 0
  static OPEN = 1
  static CLOSING = 2
  static CLOSED = 3
  static instances: MockWebSocket[] = []
  static last(): MockWebSocket {
    return this.instances[this.instances.length - 1]
  }

  url: string
  protocols: string[] | undefined
  readyState = 0
  onopen: ((ev: unknown) => void) | null = null
  onmessage: ((ev: { data: string }) => void) | null = null
  onerror: ((ev: unknown) => void) | null = null
  onclose: ((ev: unknown) => void) | null = null
  sent: string[] = []

  constructor(url: string, protocols?: string[]) {
    this.url = url
    this.protocols = protocols
    MockWebSocket.instances.push(this)
    queueMicrotask(() => {
      this.readyState = 1
      this.onopen?.({})
    })
  }

  send(data: string) {
    this.sent.push(data)
  }

  close() {
    this.readyState = 3
    this.onclose?.({})
  }

  emit(data: unknown) {
    this.onmessage?.({ data: JSON.stringify(data) })
  }
}

beforeEach(() => {
  MockWebSocket.instances = []
  vi.stubGlobal('WebSocket', MockWebSocket)
  vi.stubGlobal('crypto', { randomUUID: () => 'fake-uuid' })
  vi.stubGlobal('window', { location: { protocol: 'http:', host: 'test.local' } })
})

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

describe('useAcpStream', () => {
  it('connects with ?since=0 on mount', async () => {
    const stream = useAcpStream('sess-1')
    await new Promise((r) => setTimeout(r, 10))
    const ws = MockWebSocket.last()
    expect(ws.url).toContain('/sessions/sess-1/ws')
    expect(ws.url).toContain('since=0')
    expect(ws.url).not.toContain('token=')
    expect(ws.protocols).toEqual(['acp-v1', 'acw-token.jwt-token'])
    expect(stream.status.value).toBe('streaming')
    stream.close()
  })

  it('updates sessionStatus from state event', async () => {
    const stream = useAcpStream('s1')
    await new Promise((r) => setTimeout(r, 10))
    MockWebSocket.last().emit({ kind: 'state', status: 'running' })
    expect(stream.sessionStatus.value).toBe('running')
    stream.close()
  })

  it('accumulates events and tracks lastEventID', async () => {
    const stream = useAcpStream('s2')
    await new Promise((r) => setTimeout(r, 10))
    MockWebSocket.last().emit({
      id: 1,
      direction: 'out',
      kind: 'notification',
      method: 'session/update',
      payload: {},
    })
    MockWebSocket.last().emit({
      id: 2,
      direction: 'out',
      kind: 'notification',
      method: 'session/update',
      payload: {},
    })
    expect(stream.events.value).toHaveLength(2)
    stream.close()
  })

  it('reconnects with ?since=<last> after close', async () => {
    const stream = useAcpStream('s3')
    await new Promise((r) => setTimeout(r, 10))
    MockWebSocket.last().emit({ id: 5, direction: 'out', kind: 'notification' })

    MockWebSocket.last().close()
    await new Promise((r) => setTimeout(r, 1100))
    const ws2 = MockWebSocket.last()
    expect(ws2.url).toContain('since=5')
    stream.close()
  })

  it('sends session/prompt with client-<uuid> ID', async () => {
    const stream = useAcpStream('s4')
    await new Promise((r) => setTimeout(r, 10))
    stream.prompt('hello', 'agent-sid')
    const sent = MockWebSocket.last().sent
    expect(sent).toHaveLength(1)
    const parsed = JSON.parse(sent[0]) as {
      method: string
      id: string
      params: { sessionId: string; prompt: { text: string }[] }
    }
    expect(parsed.method).toBe('session/prompt')
    expect(parsed.id).toMatch(/^client-/)
    expect(parsed.params.sessionId).toBe('agent-sid')
    expect(parsed.params.prompt[0].text).toBe('hello')
    stream.close()
  })

  it('manual close prevents reconnect', async () => {
    const stream = useAcpStream('s5')
    await new Promise((r) => setTimeout(r, 10))
    stream.close()
    expect(stream.status.value).toBe('closed')
    await new Promise((r) => setTimeout(r, 1500))
    expect(MockWebSocket.instances).toHaveLength(1)
  })
})
