/**
 * useMessageStore 单元测试
 *
 * 覆盖：初始状态、未读计数、消息加载、已读标记、乐观回滚、请求去重、SSE 连接。
 */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'

// --- Mock 消息 API ---
const mockGetUnreadCount = vi.fn()
const mockListMessages = vi.fn()
const mockMarkRead = vi.fn()

vi.mock('../api/messages', () => ({
  getUnreadCount: (...args: unknown[]) => mockGetUnreadCount(...args),
  listMessages: (...args: unknown[]) => mockListMessages(...args),
  markRead: (...args: unknown[]) => mockMarkRead(...args),
}))

// --- Mock EventSource ---
const mockEventSourceClose = vi.fn()
const mockEventSourceAddEventListener = vi.fn()
const mockEventSourceCtor = vi.fn()

class MockEventSource {
  onopen: (() => void) | null = null
  onerror: (() => void) | null = null
  onmessage: (() => void) | null = null

  constructor(url: string) {
    mockEventSourceCtor(url)
  }

  addEventListener = mockEventSourceAddEventListener
  close = mockEventSourceClose
}

// 保存原始引用以便恢复
const OriginalEventSource = globalThis.EventSource

beforeEach(() => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  globalThis.EventSource = MockEventSource as any
})

afterEach(() => {
  globalThis.EventSource = OriginalEventSource
})

// 在 mock 生效后导入被测模块
// 必须用动态 import 重置 zustand 状态，但 zustand create 只执行一次，
// 所以需要手动重置模块级变量
import { useMessageStore } from './message'

// --- 测试用 fixture ---
const fakeMessages = [
  {
    id: 'msg-1',
    tenant_id: 't-1',
    sender_type: 'system',
    recipient_id: 'u-1',
    recipient_type: 'user',
    title: '预约提醒',
    body: '您有一个即将到来的预约',
    priority: 'high',
    is_read: false,
    is_archived: false,
    status: 'sent',
    created_at: '2026-05-01T00:00:00Z',
    updated_at: '2026-05-01T00:00:00Z',
  },
  {
    id: 'msg-2',
    tenant_id: 't-1',
    sender_type: 'doctor',
    recipient_id: 'u-1',
    recipient_type: 'user',
    title: '随访通知',
    body: '请完成本周随访',
    priority: 'normal',
    is_read: false,
    is_archived: false,
    status: 'sent',
    created_at: '2026-05-02T00:00:00Z',
    updated_at: '2026-05-02T00:00:00Z',
  },
]

beforeEach(() => {
  vi.clearAllMocks()
  localStorage.clear()

  // 重置 store 到初始状态
  useMessageStore.setState({
    unreadCount: 0,
    recentMessages: [],
  })

  // 重置模块级去重 promise（通过重新设置状态间接处理）
})

// ============================================================
// 初始状态
// ============================================================
describe('初始状态', () => {
  it('unreadCount 应为 0', () => {
    expect(useMessageStore.getState().unreadCount).toBe(0)
  })

  it('recentMessages 应为空数组', () => {
    expect(useMessageStore.getState().recentMessages).toEqual([])
  })
})

// ============================================================
// fetchUnreadCount
// ============================================================
describe('fetchUnreadCount', () => {
  it('成功时设置 unreadCount', async () => {
    mockGetUnreadCount.mockResolvedValue({ count: 7 })

    await useMessageStore.getState().fetchUnreadCount()

    expect(mockGetUnreadCount).toHaveBeenCalledTimes(1)
    expect(useMessageStore.getState().unreadCount).toBe(7)
  })

  it('API 失败时保持原值不变', async () => {
    useMessageStore.setState({ unreadCount: 3 })
    mockGetUnreadCount.mockRejectedValue(new Error('network'))

    await useMessageStore.getState().fetchUnreadCount()

    expect(useMessageStore.getState().unreadCount).toBe(3)
  })

  it('并发调用应去重（仅发一次请求）', async () => {
    let resolveApi: (value: { count: number }) => void
    const pending = new Promise<{ count: number }>((resolve) => {
      resolveApi = resolve
    })
    mockGetUnreadCount.mockReturnValue(pending)

    // 同时发起两次调用
    const call1 = useMessageStore.getState().fetchUnreadCount()
    const call2 = useMessageStore.getState().fetchUnreadCount()

    // API 只被调用一次
    expect(mockGetUnreadCount).toHaveBeenCalledTimes(1)

    // 解除 pending
    resolveApi!({ count: 5 })
    await Promise.all([call1, call2])

    expect(useMessageStore.getState().unreadCount).toBe(5)
  })

  it('前一次完成后再次调用应发起新请求', async () => {
    mockGetUnreadCount.mockResolvedValue({ count: 1 })
    await useMessageStore.getState().fetchUnreadCount()

    mockGetUnreadCount.mockResolvedValue({ count: 2 })
    await useMessageStore.getState().fetchUnreadCount()

    expect(mockGetUnreadCount).toHaveBeenCalledTimes(2)
    expect(useMessageStore.getState().unreadCount).toBe(2)
  })
})

// ============================================================
// fetchRecentMessages
// ============================================================
describe('fetchRecentMessages', () => {
  it('成功时设置 recentMessages', async () => {
    mockListMessages.mockResolvedValue({ data: fakeMessages })

    await useMessageStore.getState().fetchRecentMessages()

    expect(mockListMessages).toHaveBeenCalledWith({ page: 1, page_size: 5 })
    expect(useMessageStore.getState().recentMessages).toEqual(fakeMessages)
  })

  it('API 失败时保持原值不变', async () => {
    useMessageStore.setState({ recentMessages: fakeMessages })
    mockListMessages.mockRejectedValue(new Error('network'))

    await useMessageStore.getState().fetchRecentMessages()

    expect(useMessageStore.getState().recentMessages).toEqual(fakeMessages)
  })

  it('并发调用应去重', async () => {
    let resolveApi: (value: { data: typeof fakeMessages }) => void
    const pending = new Promise<{ data: typeof fakeMessages }>((resolve) => {
      resolveApi = resolve
    })
    mockListMessages.mockReturnValue(pending)

    const call1 = useMessageStore.getState().fetchRecentMessages()
    const call2 = useMessageStore.getState().fetchRecentMessages()

    expect(mockListMessages).toHaveBeenCalledTimes(1)

    resolveApi!({ data: fakeMessages })
    await Promise.all([call1, call2])

    expect(useMessageStore.getState().recentMessages).toEqual(fakeMessages)
  })

  it('返回空列表时 recentMessages 为空数组', async () => {
    mockListMessages.mockResolvedValue({ data: [] })

    await useMessageStore.getState().fetchRecentMessages()

    expect(useMessageStore.getState().recentMessages).toEqual([])
  })
})

// ============================================================
// markAsRead
// ============================================================
describe('markAsRead', () => {
  beforeEach(() => {
    useMessageStore.setState({
      unreadCount: 2,
      recentMessages: [...fakeMessages],
    })
  })

  it('乐观更新：立即减少 unreadCount 并标记消息已读', async () => {
    mockMarkRead.mockResolvedValue({ success: true })

    await useMessageStore.getState().markAsRead('msg-1')

    expect(useMessageStore.getState().unreadCount).toBe(1)
    const updated = useMessageStore.getState().recentMessages
    expect(updated.find((m) => m.id === 'msg-1')?.is_read).toBe(true)
    expect(updated.find((m) => m.id === 'msg-2')?.is_read).toBe(false)
  })

  it('调用 markRead API 传入正确的 id', async () => {
    mockMarkRead.mockResolvedValue({ success: true })

    await useMessageStore.getState().markAsRead('msg-1')

    expect(mockMarkRead).toHaveBeenCalledWith('msg-1')
  })

  it('API 失败时回滚到之前的状态', async () => {
    mockMarkRead.mockRejectedValue(new Error('server error'))

    await useMessageStore.getState().markAsRead('msg-1')

    // 应回滚到原始状态
    expect(useMessageStore.getState().unreadCount).toBe(2)
    expect(
      useMessageStore.getState().recentMessages.find((m) => m.id === 'msg-1')?.is_read,
    ).toBe(false)
  })

  it('unreadCount 不应为负数', async () => {
    useMessageStore.setState({ unreadCount: 0 })
    mockMarkRead.mockResolvedValue({ success: true })

    await useMessageStore.getState().markAsRead('msg-1')

    expect(useMessageStore.getState().unreadCount).toBe(0)
  })

  it('对不在 recentMessages 中的消息标记已读仍减少 unreadCount', async () => {
    mockMarkRead.mockResolvedValue({ success: true })

    await useMessageStore.getState().markAsRead('msg-other')

    expect(useMessageStore.getState().unreadCount).toBe(1)
    // recentMessages 中的消息不受影响
    expect(
      useMessageStore.getState().recentMessages.every((m) => !m.is_read),
    ).toBe(true)
  })
})

// ============================================================
// connectSSE
// ============================================================
describe('connectSSE', () => {
  beforeEach(() => {
    vi.useFakeTimers()
    mockGetUnreadCount.mockResolvedValue({ count: 0 })
    mockListMessages.mockResolvedValue({ data: [] })
  })

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

  it('返回 dispose 函数', () => {
    localStorage.setItem('access_token', 'test-token')
    const dispose = useMessageStore.getState().connectSSE()
    expect(typeof dispose).toBe('function')
    dispose()
  })

  it('无 token 时创建 EventSource 后立即返回', () => {
    // 不设置 token
    const dispose = useMessageStore.getState().connectSSE()

    // EventSource 构造未被调用（因为没有 token）
    expect(mockEventSourceCtor).not.toHaveBeenCalled()

    dispose()
  })

  it('有 token 时创建 EventSource 并传递正确 URL', () => {
    localStorage.setItem('access_token', 'my-jwt-token')
    vi.stubEnv('VITE_API_BASE_URL', '')

    const dispose = useMessageStore.getState().connectSSE()

    expect(mockEventSourceCtor).toHaveBeenCalledTimes(1)
    const url = mockEventSourceCtor.mock.calls[0][0] as string
    expect(url).toContain('/messages/stream?token=')
    expect(url).toContain(encodeURIComponent('my-jwt-token'))

    dispose()
  })

  it('dispose 关闭 EventSource', () => {
    localStorage.setItem('access_token', 'token')

    const dispose = useMessageStore.getState().connectSSE()
    dispose()

    expect(mockEventSourceClose).toHaveBeenCalled()
  })

  it('注册 message 和 alert 事件监听器', () => {
    localStorage.setItem('access_token', 'token')

    const dispose = useMessageStore.getState().connectSSE()

    const eventTypes = mockEventSourceAddEventListener.mock.calls.map(
      (call: any[]) => call[0],
    )
    expect(eventTypes).toContain('message')
    expect(eventTypes).toContain('alert')
    expect(eventTypes).toContain('vital_update')

    dispose()
  })

  it('message 事件触发 fetchUnreadCount 和 fetchRecentMessages', async () => {
    localStorage.setItem('access_token', 'token')

    const dispose = useMessageStore.getState().connectSSE()

    // 找到 message 事件的回调
    const messageCall = mockEventSourceAddEventListener.mock.calls.find(
      (call: any[]) => call[0] === 'message',
    )
    const messageCallback = messageCall![1] as () => void

    messageCallback()

    // 等待异步操作完成
    await vi.runAllTimersAsync()

    expect(mockGetUnreadCount).toHaveBeenCalled()
    expect(mockListMessages).toHaveBeenCalled()

    dispose()
  })

  it('alert 事件仅触发 fetchUnreadCount', async () => {
    localStorage.setItem('access_token', 'token')

    const dispose = useMessageStore.getState().connectSSE()

    const alertCall = mockEventSourceAddEventListener.mock.calls.find(
      (call: any[]) => call[0] === 'alert',
    )
    const alertCallback = alertCall![1] as () => void

    alertCallback()

    await vi.runAllTimersAsync()

    expect(mockGetUnreadCount).toHaveBeenCalled()
    // alert 不应触发 fetchRecentMessages
    expect(mockListMessages).not.toHaveBeenCalled()

    dispose()
  })

  it('onerror 关闭连接并启动重连定时器', () => {
    localStorage.setItem('access_token', 'token')

    // 需要捕获 EventSource 实例以设置 onerror
    let instance: MockEventSource | null = null
    const OrigMock = MockEventSource
    const CapturingEventSource = class extends OrigMock {
      constructor(url: string) {
        super(url)
        instance = this
      }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    globalThis.EventSource = CapturingEventSource as any

    const dispose = useMessageStore.getState().connectSSE()

    expect(instance).not.toBeNull()
    instance!.onerror!()

    expect(mockEventSourceClose).toHaveBeenCalled()

    dispose()
  })

  it('dispose 后不会重连', () => {
    localStorage.setItem('access_token', 'token')

    let instance: MockEventSource | null = null
    const OrigMock = MockEventSource
    const CapturingEventSource = class extends OrigMock {
      constructor(url: string) {
        super(url)
        instance = this
      }
    }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    globalThis.EventSource = CapturingEventSource as any

    const dispose = useMessageStore.getState().connectSSE()

    instance!.onerror!()
    dispose()

    // 清除 constructor 调用计数
    mockEventSourceCtor.mockClear()

    // 推进定时器 — 不应触发新的 EventSource 创建
    vi.advanceTimersByTime(60_000)

    expect(mockEventSourceCtor).not.toHaveBeenCalled()
  })
})

// ============================================================
// 状态重置
// ============================================================
describe('状态重置', () => {
  it('通过 setState 可手动重置到初始状态', async () => {
    mockGetUnreadCount.mockResolvedValue({ count: 10 })
    mockListMessages.mockResolvedValue({ data: fakeMessages })

    await useMessageStore.getState().fetchUnreadCount()
    await useMessageStore.getState().fetchRecentMessages()

    expect(useMessageStore.getState().unreadCount).toBe(10)
    expect(useMessageStore.getState().recentMessages).toEqual(fakeMessages)

    // 重置
    useMessageStore.setState({ unreadCount: 0, recentMessages: [] })

    expect(useMessageStore.getState().unreadCount).toBe(0)
    expect(useMessageStore.getState().recentMessages).toEqual([])
  })
})
