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 { useFailedMessageStore } from '@/stores/failed-message'
import type { FailedMessageRecord, FailedMessageDetail } 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 mockMessages: FailedMessageRecord[] = [
  {
    id: 1,
    error_id: 'err_001',
    data_type: 'order',
    platform: 'Shopee',
    platform_id: 1,
    company_id: 1,
    store_id: 1,
    error_type: 'InvalidArgumentException',
    error_message: 'Missing required field: platform_order_id',
    retry_count: 3,
    message_id: 'msg_abc123',
    failed_at: '2026-03-19T10:30:00',
    created_at: '2026-03-19T10:30:00',
  },
  {
    id: 2,
    error_id: 'err_002',
    data_type: 'product',
    platform: 'Lazada',
    platform_id: 2,
    company_id: 1,
    store_id: 2,
    error_type: 'RuntimeException',
    error_message: 'Database connection timeout after 30s',
    retry_count: 1,
    message_id: 'msg_def456',
    failed_at: '2026-03-19T11:00:00',
    created_at: '2026-03-19T11:00:00',
  },
  {
    id: 3,
    error_id: 'err_003',
    data_type: 'refund',
    platform: 'Shopee',
    platform_id: 1,
    company_id: 2,
    store_id: 3,
    error_type: 'ValidationException',
    error_message: 'Refund amount exceeds order total',
    retry_count: 0,
    message_id: 'msg_ghi789',
    failed_at: '2026-03-20T08:15:00',
    created_at: '2026-03-20T08:15:00',
  },
]

const baseMock = mockMessages[0]!

const mockDetail: FailedMessageDetail = {
  ...baseMock,
  error_code: 'ERR_MISSING_FIELD',
  error_trace: '#0 /app/Entity/Parse/EntityParse.php(42)\n#1 /app/Consumer/OrderConsumer.php(58)\n#2 {main}',
  original_message: { meta: { company_id: 1 }, data: [{ order_id: '123' }] },
}

const mockPlatforms = [
  { id: 1, name: 'Shopee' },
  { id: 2, name: 'Lazada' },
]

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

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

  describe('initial state', () => {
    it('S1: starts with correct initial state', () => {
      const store = useFailedMessageStore()
      expect(store.messages).toEqual([])
      expect(store.loading).toBe(false)
      expect(store.pagination.page).toBe(1)
      expect(store.pagination.per_page).toBe(15)
      expect(store.pagination.total).toBe(0)
      expect(store.filters.data_type).toBeUndefined()
      expect(store.filters.platform_id).toBeUndefined()
      expect(store.filters.failed_at_range).toBeNull()
    })
  })

  describe('fetchMessages', () => {
    it('S2: fetches messages successfully', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce({
        items: mockMessages,
        total: 3,
        page: 1,
        per_page: 15,
      })

      const store = useFailedMessageStore()
      await store.fetchMessages()

      expect(api.get).toHaveBeenCalledWith('/api/v1/failed-messages', {
        page: 1,
        per_page: 15,
        data_type: undefined,
        platform_id: undefined,
        failed_at_from: undefined,
        failed_at_to: undefined,
      })
      expect(store.messages).toEqual(mockMessages)
      expect(store.pagination.total).toBe(3)
      expect(store.loading).toBe(false)
    })

    it('S3: fetches with filters applied', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce({
        items: [mockMessages[0]],
        total: 1,
        page: 1,
        per_page: 15,
      })

      const store = useFailedMessageStore()
      store.filters.data_type = 'order'
      store.filters.platform_id = 1
      store.filters.failed_at_range = ['2026-03-19', '2026-03-20']
      await store.fetchMessages()

      expect(api.get).toHaveBeenCalledWith('/api/v1/failed-messages', {
        page: 1,
        per_page: 15,
        data_type: 'order',
        platform_id: 1,
        failed_at_from: '2026-03-19',
        failed_at_to: '2026-03-20',
      })
      expect(store.messages).toHaveLength(1)
    })

    it('S4: handles fetch failure gracefully', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockRejectedValueOnce(new Error('网络超时'))

      const store = useFailedMessageStore()
      await store.fetchMessages()

      expect(store.messages).toEqual([])
      expect(store.pagination.total).toBe(0)
      expect(store.loading).toBe(false)
    })
  })

  describe('resetFilters', () => {
    it('S5: resets all filters and page', () => {
      const store = useFailedMessageStore()
      store.filters.data_type = 'order'
      store.filters.platform_id = 1
      store.filters.failed_at_range = ['2026-03-01', '2026-03-20']
      store.pagination.page = 3

      store.resetFilters()

      expect(store.filters.data_type).toBeUndefined()
      expect(store.filters.platform_id).toBeUndefined()
      expect(store.filters.failed_at_range).toBeNull()
      expect(store.pagination.page).toBe(1)
    })
  })

  describe('platformMap', () => {
    it('S6: builds platformMap from loaded platforms', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce(mockPlatforms)

      const store = useFailedMessageStore()
      await store.loadLookups()

      expect(store.platformMap.get(1)).toBe('Shopee')
      expect(store.platformMap.get(2)).toBe('Lazada')
    })

    it('S7: handles loadLookups failure gracefully', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockRejectedValueOnce(new Error('加载失败'))

      const store = useFailedMessageStore()
      await store.loadLookups()

      expect(store.platforms).toEqual([])
      expect(store.platformMap.size).toBe(0)
    })
  })
})

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

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

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

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

  async function mountPage() {
    const { api } = await import('@/utils/request')
    vi.mocked(api.get).mockImplementation((url: string) => {
      if (url === '/api/v1/platforms') return Promise.resolve(mockPlatforms)
      if (url === '/api/v1/failed-messages') {
        return Promise.resolve({
          items: mockMessages,
          total: 3,
          page: 1,
          per_page: 15,
        })
      }
      return Promise.resolve(null)
    })

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

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

  it('P1: renders page title', async () => {
    await mountPage()
    expect(wrapper.text()).toContain('失败消息管理')
  }, 15000)

  it('P2: calls API on mount', async () => {
    const { api } = await mountPage()
    expect(api.get).toHaveBeenCalledWith('/api/v1/platforms')
    expect(api.get).toHaveBeenCalledWith('/api/v1/failed-messages', expect.any(Object))
  })

  it('P3: renders table with message data', async () => {
    await mountPage()
    const html = wrapper.html()
    expect(html).toContain('InvalidArgumentException')
    expect(html).toContain('RuntimeException')
    expect(html).toContain('Missing required field')
  })

  it('P4: renders data_type as colored tags with Chinese labels', async () => {
    await mountPage()
    const tags = wrapper.findAll('.ant-tag')
    const tagTexts = tags.map((t) => t.text())
    expect(tagTexts).toContain('订单')
    expect(tagTexts).toContain('产品')
    expect(tagTexts).toContain('退款')
  })

  it('P5: displays platform name from platformMap', async () => {
    await mountPage()
    const html = wrapper.html()
    expect(html).toContain('Shopee')
    expect(html).toContain('Lazada')
  })

  it('P6: formats failed_at as YYYY-MM-DD HH:mm', async () => {
    await mountPage()
    const html = wrapper.html()
    expect(html).toContain('2026-03-19 10:30')
    expect(html).toContain('2026-03-19 11:00')
  })

  it('P7: search button triggers fetch with page=1', async () => {
    const { api } = await mountPage()
    const store = useFailedMessageStore()
    store.pagination.page = 3
    vi.mocked(api.get).mockClear()
    vi.mocked(api.get).mockResolvedValue({
      items: [],
      total: 0,
      page: 1,
      per_page: 15,
    })

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

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

    expect(store.pagination.page).toBe(1)
    expect(api.get).toHaveBeenCalledWith('/api/v1/failed-messages', expect.any(Object))
  })

  it('P8: reset button clears filters and fetches', async () => {
    const { api } = await mountPage()
    const store = useFailedMessageStore()
    store.filters.data_type = 'order'
    store.filters.platform_id = 1
    vi.mocked(api.get).mockClear()
    vi.mocked(api.get).mockResolvedValue({
      items: mockMessages,
      total: 3,
      page: 1,
      per_page: 15,
    })

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

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

    expect(store.filters.data_type).toBeUndefined()
    expect(store.filters.platform_id).toBeUndefined()
  })

  it('P9: clicking view button opens detail drawer', async () => {
    const { api } = await mountPage()
    vi.mocked(api.get).mockResolvedValue(mockDetail)

    const viewButtons = Array.from(document.body.querySelectorAll('.ant-btn-link'))
    expect(viewButtons.length).toBeGreaterThan(0)
    viewButtons[0]!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    await flushPromises()
    await nextTick()

    const drawerBody = document.body.querySelector('.ant-drawer')
    expect(drawerBody).toBeDefined()
    expect(document.body.innerHTML).toContain('失败消息详情')
  })

  it('P10: detail drawer shows error_trace in pre block', async () => {
    const { api } = await mountPage()
    vi.mocked(api.get).mockResolvedValue(mockDetail)

    const viewButtons = Array.from(document.body.querySelectorAll('.ant-btn-link'))
    expect(viewButtons.length).toBeGreaterThan(0)
    viewButtons[0]!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    await flushPromises()
    await nextTick()

    const pres = document.body.querySelectorAll('pre')
    const preTexts = Array.from(pres).map((p) => p.textContent)
    expect(preTexts.some((t) => t?.includes('#0 /app/Entity/Parse/EntityParse.php(42)'))).toBe(true)
  })

  it('P11: detail drawer shows original_message as formatted JSON', async () => {
    const { api } = await mountPage()
    vi.mocked(api.get).mockResolvedValue(mockDetail)

    const viewButtons = Array.from(document.body.querySelectorAll('.ant-btn-link'))
    expect(viewButtons.length).toBeGreaterThan(0)
    viewButtons[0]!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    await flushPromises()
    await nextTick()

    const pres = document.body.querySelectorAll('pre')
    const preTexts = Array.from(pres).map((p) => p.textContent)
    expect(preTexts.some((t) => t?.includes('"company_id": 1'))).toBe(true)
  })

  it('P12: detail request failure shows error message', async () => {
    const { api } = await mountPage()
    vi.mocked(api.get).mockRejectedValue(new Error('详情加载失败'))

    const viewButtons = Array.from(document.body.querySelectorAll('.ant-btn-link'))
    expect(viewButtons.length).toBeGreaterThan(0)
    viewButtons[0]!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    await flushPromises()
    await nextTick()

    // drawer should be open but no detail content
    const drawerBody = document.body.querySelector('.ant-drawer')
    expect(drawerBody).toBeDefined()
  })
})
