import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import { nextTick } from 'vue'
import { setActivePinia, createPinia } from 'pinia'
import { useDashboardStore } from '@/stores/dashboard'

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(),
    delete: vi.fn(),
  },
}))

// Stub G2Plot — jsdom has no Canvas
vi.mock('@antv/g2plot', () => ({
  Line: vi.fn().mockImplementation(() => ({
    render: vi.fn(),
    changeData: vi.fn(),
    destroy: vi.fn(),
  })),
}))

import { api } from '@/utils/request'

const mockOverview = {
  today: { success: 10, failed: 2 },
  this_week: { success: 50, failed: 5 },
  this_month: { success: 200, failed: 20 },
  by_type: [
    { data_type: 'order', success: 80, failed: 10 },
    { data_type: 'product', success: 60, failed: 5 },
    { data_type: 'refund', success: 40, failed: 3 },
    { data_type: 'inventory', success: 20, failed: 2 },
  ],
}

const mockTrendData = [
  { date: '2026-03-18', success: 30, failed: 2 },
  { date: '2026-03-19', success: 25, failed: 3 },
]

const mockBreakdownData = [
  { id: 1, name: 'Acme Corp', success: 100, failed: 10 },
  { id: 2, name: 'Beta Inc', success: 80, failed: 0 },
]

function setupApiMocks() {
  vi.mocked(api.get).mockImplementation((url: string) => {
    if (url.includes('overview')) return Promise.resolve(mockOverview) as never
    if (url.includes('trend')) return Promise.resolve(mockTrendData) as never
    if (url.includes('breakdown')) return Promise.resolve(mockBreakdownData) as never
    return Promise.resolve(null) as never
  })
}

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

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

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

  const stubs = {
    ReloadOutlined: { template: '<span />' },
    CheckCircleOutlined: { template: '<span />' },
    CloseCircleOutlined: { template: '<span />' },
    ShoppingCartOutlined: { template: '<span />' },
    ShoppingOutlined: { template: '<span />' },
    DollarOutlined: { template: '<span />' },
    DatabaseOutlined: { template: '<span />' },
    DashboardTrendChart: { template: '<div class="trend-stub" />' },
  }

  async function mountPage() {
    setupApiMocks()

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

    wrapper = mount(DashboardPage, {
      attachTo: document.body,
      global: { stubs },
    })
    await flushPromises()
    await nextTick()
  }

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

    expect(wrapper.text()).toContain('Dashboard')
    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 fetchAll on mount', async () => {
    await mountPage()

    expect(api.get).toHaveBeenCalledWith('/api/v1/dashboard/overview')
    expect(api.get).toHaveBeenCalledWith('/api/v1/dashboard/trend', expect.any(Object))
    expect(api.get).toHaveBeenCalledWith('/api/v1/dashboard/breakdown', expect.any(Object))
  })

  it('P3: displays overview period labels', async () => {
    await mountPage()

    const html = wrapper.html()
    expect(html).toContain('今日')
    expect(html).toContain('本周')
    expect(html).toContain('本月')
  })

  it('P4: displays overview success/failed values', async () => {
    await mountPage()

    const html = wrapper.html()
    // Today's success=10, failed=2
    expect(html).toContain('10')
    expect(html).toContain('2')
  })

  it('P5: displays by-type cards', async () => {
    await mountPage()

    const html = wrapper.html()
    expect(html).toContain('订单')
    expect(html).toContain('产品')
    expect(html).toContain('退款')
    expect(html).toContain('库存')
  })

  it('P6: displays breakdown table data', async () => {
    await mountPage()

    const html = wrapper.html()
    expect(html).toContain('Acme Corp')
    expect(html).toContain('Beta Inc')
  })

  it('P7: failed count > 0 gets red highlight in breakdown', async () => {
    await mountPage()

    const redCells = document.body.querySelectorAll('.text-red-500')
    expect(redCells.length).toBeGreaterThan(0)
  })

  it('P8: refresh button triggers fetchAll', async () => {
    await mountPage()
    vi.mocked(api.get).mockClear()
    setupApiMocks()

    const refreshBtn = wrapper.findAll('.ant-btn').find((b) => b.text().includes('刷新'))
    await refreshBtn?.trigger('click')
    await flushPromises()

    expect(api.get).toHaveBeenCalledWith('/api/v1/dashboard/overview')
  })

  it('P9: dimension change triggers breakdown refetch', async () => {
    await mountPage()
    vi.mocked(api.get).mockClear()
    setupApiMocks()

    const store = useDashboardStore()
    store.breakdownDimension = 'platform'
    await store.fetchBreakdown()

    expect(api.get).toHaveBeenCalledWith('/api/v1/dashboard/breakdown', expect.objectContaining({
      dimension: 'platform',
    }))
  })

  it('P10: shows error alert when overview fails', async () => {
    vi.mocked(api.get).mockImplementation((url: string) => {
      if (url.includes('overview')) return Promise.reject(new Error('服务器错误')) as never
      if (url.includes('trend')) return Promise.resolve(mockTrendData) as never
      if (url.includes('breakdown')) return Promise.resolve(mockBreakdownData) as never
      return Promise.resolve(null) as never
    })

    const { default: DashboardPage } = await import('../index.vue')
    wrapper = mount(DashboardPage, {
      attachTo: document.body,
      global: { stubs },
    })
    await flushPromises()
    await nextTick()

    const html = wrapper.html()
    expect(html).toContain('服务器错误')
  })
})
