import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { mount, flushPromises } from '@vue/test-utils'
import { nextTick } from 'vue'
import { useMqStatusStore } from '@/stores/mq-status'
import type { MqStatusData } from '@/types/api'

Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: (query: string) => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: () => {},
    removeListener: () => {},
    addEventListener: () => {},
    removeEventListener: () => {},
    dispatchEvent: () => false,
  }),
})

vi.mock('@/utils/request', () => ({
  api: {
    get: vi.fn(),
    post: vi.fn(),
    put: vi.fn(),
    patch: vi.fn(),
  },
}))

const mockStatusData: MqStatusData = {
  business_queues: [
    { queue: 'orders.queue', messages: 5, consumers: 2, status: 'active' },
    { queue: 'products.queue', messages: 0, consumers: 1, status: 'empty' },
    { queue: 'refunds.queue', messages: 120, consumers: 3, status: 'high_load' },
    { queue: 'inventory.queue', messages: 'N/A', consumers: 'N/A', status: 'error' },
  ],
  retry_queues: [
    { queue: 'orders.retry.queue', messages: 2, consumers: 1, status: 'active' },
    { queue: 'products.retry.queue', messages: 0, consumers: 1, status: 'empty' },
  ],
  error_queue: { queue: 'errors.queue', messages: 3, consumers: 1, status: 'processing' },
  summary: { total_messages: 130, total_consumers: 9 },
  fetched_at: '2026-03-20 14:30:00',
}

const mockFilteredStatusData: MqStatusData = {
  business_queues: [
    { queue: 'orders.queue', messages: 5, consumers: 2, status: 'active' },
  ],
  retry_queues: [
    { queue: 'orders.retry.queue', messages: 2, consumers: 1, status: 'active' },
  ],
  error_queue: [],
  summary: { total_messages: 7, total_consumers: 3 },
  fetched_at: '2026-03-20 14:31:00',
}

// ─── Store Tests ───────────────────────────────────────

describe('useMqStatusStore', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
    vi.restoreAllMocks()
  })

  describe('initial state', () => {
    it('S1: starts with correct initial state', () => {
      const store = useMqStatusStore()
      expect(store.statusData).toBeNull()
      expect(store.loading).toBe(false)
      expect(store.errorMessage).toBe('')
      expect(store.queueType).toBeUndefined()
    })
  })

  describe('fetchStatus', () => {
    it('S2: fetches status data successfully', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce(mockStatusData)

      const store = useMqStatusStore()
      await store.fetchStatus()

      expect(api.get).toHaveBeenCalledWith('/api/v1/mq/status', {})
      expect(store.statusData).toEqual(mockStatusData)
      expect(store.loading).toBe(false)
      expect(store.errorMessage).toBe('')
    })

    it('S3: fetches with queue parameter when filtered', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce(mockFilteredStatusData)

      const store = useMqStatusStore()
      store.queueType = 'orders'
      await store.fetchStatus()

      expect(api.get).toHaveBeenCalledWith('/api/v1/mq/status', { queue: 'orders' })
      expect(store.statusData).toEqual(mockFilteredStatusData)
    })

    it('S4: sets errorMessage on failure', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockRejectedValueOnce(new Error('连接超时'))

      const store = useMqStatusStore()
      await store.fetchStatus()

      expect(store.errorMessage).toBe('连接超时')
      expect(store.loading).toBe(false)
    })
  })

  describe('healthySummary', () => {
    it('S5: computes healthy/total correctly', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce(mockStatusData)

      const store = useMqStatusStore()
      await store.fetchStatus()

      // active(orders.queue, orders.retry.queue) + empty(products.queue, products.retry.queue) = 4 healthy
      // total = 4 business + 2 retry + 1 error = 7
      expect(store.healthySummary.healthy).toBe(4)
      expect(store.healthySummary.total).toBe(7)
    })
  })

  describe('resetFilter', () => {
    it('S6: clears queue type filter', () => {
      const store = useMqStatusStore()
      store.queueType = 'orders'
      store.resetFilter()
      expect(store.queueType).toBeUndefined()
    })
  })
})

// ─── Page Component Tests ──────────────────────────────

describe('MqStatusPage', () => {
  let wrapper: ReturnType<typeof mount>

  beforeEach(() => {
    setActivePinia(createPinia())
    vi.restoreAllMocks()
    document.body.innerHTML = ''
  })

  afterEach(() => {
    wrapper?.unmount()
    document.body.innerHTML = ''
  })

  async function mountPage(data: MqStatusData = mockStatusData) {
    const { api } = await import('@/utils/request')
    vi.mocked(api.get).mockResolvedValue(data)

    const { default: MqStatusPage } = await import('../index.vue')

    wrapper = mount(MqStatusPage, {
      attachTo: document.body,
      global: {
        stubs: {
          ReloadOutlined: { template: '<span />' },
        },
      },
    })
    await flushPromises()
    await nextTick()
    return { wrapper, api }
  }

  it('P1: renders page title and refresh button', async () => {
    await mountPage()

    expect(wrapper.text()).toContain('消息队列状态')
    const buttons = wrapper.findAll('.ant-btn')
    const buttonTexts = buttons.map((b) => b.text())
    expect(buttonTexts.some((t) => t.includes('刷新'))).toBe(true)
  }, 15000)

  it('P2: calls fetchStatus on mount', async () => {
    const { api } = await mountPage()

    expect(api.get).toHaveBeenCalledWith('/api/v1/mq/status', {})
  })

  it('P3: displays total_messages and total_consumers in stat cards', async () => {
    await mountPage()

    const html = wrapper.html()
    expect(html).toContain('总消息数')
    expect(html).toContain('130')
    expect(html).toContain('总消费者数')
    expect(html).toContain('9')
  })

  it('P4: renders business queues table', async () => {
    await mountPage()

    const html = wrapper.html()
    expect(html).toContain('业务队列')
    expect(html).toContain('orders.queue')
    expect(html).toContain('products.queue')
    expect(html).toContain('refunds.queue')
    expect(html).toContain('inventory.queue')
  })

  it('P5: renders retry queues table', async () => {
    await mountPage()

    const html = wrapper.html()
    expect(html).toContain('重试队列')
    expect(html).toContain('orders.retry.queue')
    expect(html).toContain('products.retry.queue')
  })

  it('P6: renders error queue with message count', async () => {
    await mountPage()

    const html = wrapper.html()
    expect(html).toContain('错误队列')
    expect(html).toContain('errors.queue')
  })

  it('P7: renders status tags with correct colors', async () => {
    await mountPage()

    const html = wrapper.html()
    expect(html).toContain('活跃')
    expect(html).toContain('空闲')
    expect(html).toContain('高负载')
    expect(html).toContain('异常')
  })

  it('P8: filter change triggers fetchStatus with queue param', async () => {
    const { api } = await mountPage()
    vi.mocked(api.get).mockClear()
    vi.mocked(api.get).mockResolvedValue(mockFilteredStatusData)

    const store = useMqStatusStore()
    store.queueType = 'orders'
    await store.fetchStatus()

    expect(api.get).toHaveBeenCalledWith('/api/v1/mq/status', { queue: 'orders' })
  })

  it('P9: refresh button triggers fetchStatus', async () => {
    const { api } = await mountPage()
    vi.mocked(api.get).mockClear()
    vi.mocked(api.get).mockResolvedValue(mockStatusData)

    const buttons = Array.from(document.body.querySelectorAll('.ant-btn'))
    const refreshBtn = buttons.find((b) => b.textContent?.trim().includes('刷新'))
    expect(refreshBtn).toBeDefined()

    refreshBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    await flushPromises()
    await nextTick()

    expect(api.get).toHaveBeenCalledWith('/api/v1/mq/status', {})
  })

  it('P10: shows error alert when API fails', async () => {
    const { api } = await import('@/utils/request')
    vi.mocked(api.get).mockRejectedValue(new Error('RabbitMQ 连接失败'))

    const { default: MqStatusPage } = await import('../index.vue')
    wrapper = mount(MqStatusPage, {
      attachTo: document.body,
      global: {
        stubs: { ReloadOutlined: { template: '<span />' } },
      },
    })
    await flushPromises()
    await nextTick()

    const html = wrapper.html()
    expect(html).toContain('RabbitMQ 连接失败')
  })

  it('P11: displays fetched_at timestamp', async () => {
    await mountPage()

    const html = wrapper.html()
    expect(html).toContain('2026-03-20 14:30:00')
    expect(html).toContain('最后更新')
  })

  it('P12: hides error queue section when filtered', async () => {
    await mountPage(mockFilteredStatusData)

    const cards = wrapper.findAll('.ant-card')
    const cardTitles = cards.map((c) => c.text())
    expect(cardTitles.some((t) => t.includes('业务队列'))).toBe(true)
    expect(cardTitles.some((t) => t.includes('重试队列'))).toBe(true)
    // error_queue is [] when filtered, so no error queue card should render
    const errorCard = cards.find((c) => c.text().includes('errors.queue'))
    expect(errorCard).toBeUndefined()
  })

  it('P13: error queue messages > 0 shown with red highlight', async () => {
    await mountPage()

    // error_queue has messages=3, should have red highlight class
    const errorQueueCards = wrapper.findAll('.ant-card')
    const errorCard = errorQueueCards.find((c) => c.text().includes('错误队列'))
    expect(errorCard).toBeDefined()

    const redSpan = errorCard?.find('.text-red-500')
    expect(redSpan?.exists()).toBe(true)
    expect(redSpan?.text()).toBe('3')
  })
})
