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 { useOrderStore, type OrderRecord } from '@/stores/order'

// jsdom 不支持 matchMedia，Ant Design Vue 响应式布局需要
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: (query: string) => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: () => {},
    removeListener: () => {},
    addEventListener: () => {},
    removeEventListener: () => {},
    dispatchEvent: () => false,
  }),
})

let mockRouteQuery: Record<string, string> = {}
vi.mock('vue-router', () => ({
  useRoute: () => ({ query: mockRouteQuery }),
  useRouter: () => ({ push: vi.fn() }),
}))

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

const mockOrders: OrderRecord[] = [
  {
    id: 1,
    company_id: 1,
    platform_id: 1,
    store_id: 1,
    order_status_id: 3,
    platform_order_id: 'ORD-20260101-001',
    total_amount: '199.99',
    total_paid: '189.99',
    total_discount: '10.00',
    created_date: '2026-01-01T10:00:00Z',
    paid_date: '2026-01-01T10:05:00Z',
    shipping_date: '2026-01-02T08:00:00Z',
  },
  {
    id: 2,
    company_id: 2,
    platform_id: 1,
    store_id: 2,
    order_status_id: 1,
    platform_order_id: 'ORD-20260201-002',
    total_amount: '59.00',
    total_paid: '0',
    total_discount: '0',
    created_date: '2026-02-01T14:00:00Z',
    paid_date: null,
    shipping_date: null,
  },
]

const mockPaginatedResponse = {
  items: mockOrders,
  total: 2,
  page: 1,
  per_page: 15,
}

const mockOrderDetail = {
  ...mockOrders[0],
  buyer_user_id: 'buyer_123',
  payment_method_id: 1,
  presale: false,
  total_received: '189.99',
  freight_fee: '0.00',
  tax_fee: '0.00',
  discount_fee: '10.00',
  commission_fee: '5.00',
  coupon_amount: '0.00',
  voucher_amount: '0.00',
  order_type_id: 1,
  updated_date: '2026-01-02T08:00:00Z',
  zipcode: '310000',
  city: '杭州',
  province: '浙江',
  country: '中国',
  ext: { remark: '加急发货', tags: ['vip'] },
  created_at: '2026-01-01T10:00:00Z',
  updated_at: '2026-01-02T08:00:00Z',
  order_items: [
    {
      id: 101,
      company_id: 1,
      platform_id: 1,
      store_id: 1,
      order_id: 1,
      platform_order_id: 'ORD-20260101-001',
      sub_order_id: 'SUB-001',
      sub_order_type_id: 1,
      product_id: 500,
      platform_product_id: 'PROD-001',
      product_sku: 'SKU-ABC-001',
      product_barcode: '6901234567890',
      unit_price: '99.99',
      quantity: 2,
      discount: '10.00',
      total: '189.98',
      created_date: '2026-01-01T10:00:00Z',
      ext: null,
      created_at: '2026-01-01T10:00:00Z',
      updated_at: '2026-01-01T10:00:00Z',
    },
  ],
}

const mockRawDetail = {
  id: 1,
  platform_order_id: 'ORD-20260101-001',
  store_id: 1,
  company_id: 1,
  platform_id: 1,
  order_status_id: 3,
  raw: { tid: 'tmall_001', status: 'TRADE_FINISHED', orders: [{ oid: 'sub_1' }] },
  hash: 'abc123',
  ext: null,
  created_date: '2026-01-01T10:00:00Z',
}

const mockLookupCompanies = [
  { id: 1, name: 'Company A', label: '公司A' },
  { id: 2, name: 'Company B', label: '公司B' },
]
const mockLookupPlatforms = [{ id: 1, developer_id: 1 }]
const mockLookupStores = [
  { id: 1, company_id: 1, platform_id: 1, name: 'Store 1', label: '店铺1' },
  { id: 2, company_id: 2, platform_id: 1, name: 'Store 2', label: '店铺2' },
]

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

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

  describe('initial state', () => {
    it('starts with empty orders and default pagination', () => {
      const store = useOrderStore()
      expect(store.orders).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.order_status_id).toBeUndefined()
      expect(store.filters.platform_order_id).toBe('')
      expect(store.filters.created_date_range).toBeNull()
      expect(store.filters.paid_date_range).toBeNull()
    })
  })

  describe('fetchOrders', () => {
    it('loads data and updates state', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce(mockPaginatedResponse)

      const store = useOrderStore()
      await store.fetchOrders()

      expect(api.get).toHaveBeenCalledWith(
        '/api/v1/orders',
        expect.objectContaining({
          page: 1,
          per_page: 15,
          order_status_id: undefined,
          platform_order_id: undefined,
          created_date_from: undefined,
          created_date_to: undefined,
          paid_date_from: undefined,
          paid_date_to: undefined,
        }),
      )
      expect(store.orders).toEqual(mockOrders)
      expect(store.pagination.total).toBe(2)
    })

    it('passes date range params correctly', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get).mockResolvedValueOnce(mockPaginatedResponse)

      const store = useOrderStore()
      store.filters.order_status_id = 3
      store.filters.platform_order_id = 'ORD-001'
      store.filters.created_date_range = ['2026-01-01', '2026-01-31']
      store.filters.paid_date_range = ['2026-02-01', '2026-02-28']
      store.cascadeValue.company_id = 1
      store.cascadeValue.platform_id = 2
      store.cascadeValue.store_id = 3

      await store.fetchOrders()

      expect(api.get).toHaveBeenCalledWith(
        '/api/v1/orders',
        expect.objectContaining({
          order_status_id: 3,
          platform_order_id: 'ORD-001',
          created_date_from: '2026-01-01',
          created_date_to: '2026-01-31',
          paid_date_from: '2026-02-01',
          paid_date_to: '2026-02-28',
          company_id: 1,
          platform_id: 2,
          store_id: 3,
        }),
      )
    })

    it('shows error message on failure', async () => {
      const { api } = await import('@/utils/request')
      const { message } = await import('ant-design-vue')
      const errorSpy = vi.spyOn(message, 'error')
      vi.mocked(api.get).mockRejectedValueOnce(new Error('Network error'))

      const store = useOrderStore()
      await store.fetchOrders()

      expect(errorSpy).toHaveBeenCalledWith('Network error')
      expect(store.orders).toEqual([])
    })
  })

  describe('resetFilters', () => {
    it('clears all filters including date ranges and resets page to 1', () => {
      const store = useOrderStore()
      store.filters.order_status_id = 3
      store.filters.platform_order_id = 'ORD-001'
      store.filters.created_date_range = ['2026-01-01', '2026-01-31']
      store.filters.paid_date_range = ['2026-02-01', '2026-02-28']
      store.cascadeValue.company_id = 1
      store.cascadeValue.platform_id = 2
      store.cascadeValue.store_id = 3
      store.pagination.page = 5

      store.resetFilters()

      expect(store.filters.order_status_id).toBeUndefined()
      expect(store.filters.platform_order_id).toBe('')
      expect(store.filters.created_date_range).toBeNull()
      expect(store.filters.paid_date_range).toBeNull()
      expect(store.cascadeValue.company_id).toBeUndefined()
      expect(store.cascadeValue.platform_id).toBeUndefined()
      expect(store.cascadeValue.store_id).toBeUndefined()
      expect(store.pagination.page).toBe(1)
    })
  })

  describe('name maps', () => {
    it('builds lookup maps from loaded data', async () => {
      const { api } = await import('@/utils/request')
      vi.mocked(api.get)
        .mockResolvedValueOnce(mockLookupCompanies)
        .mockResolvedValueOnce(mockLookupPlatforms)
        .mockResolvedValueOnce(mockLookupStores)

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

      expect(store.companyMap.get(1)).toBe('公司A')
      expect(store.platformMap.get(1)).toBe('平台 #1')
      expect(store.storeMap.get(1)).toBe('店铺1')
    })

    it('handles loadLookups failure gracefully', async () => {
      const { api } = await import('@/utils/request')
      const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
      vi.mocked(api.get).mockRejectedValueOnce(new Error('Network error'))

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

      expect(warnSpy).toHaveBeenCalledWith('加载查找表数据失败', expect.any(Error))
      expect(store.companyMap.size).toBe(0)
      warnSpy.mockRestore()
    })
  })

  describe('fetchOrders error handling', () => {
    it('clears orders on fetch failure', async () => {
      const { api } = await import('@/utils/request')
      const { message } = await import('ant-design-vue')
      vi.spyOn(message, 'error')

      const store = useOrderStore()
      vi.mocked(api.get).mockResolvedValueOnce(mockPaginatedResponse)
      await store.fetchOrders()
      expect(store.orders.length).toBe(2)

      vi.mocked(api.get).mockRejectedValueOnce(new Error('Server error'))
      await store.fetchOrders()

      expect(store.orders).toEqual([])
      expect(store.pagination.total).toBe(0)
    })
  })
})

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

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

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

  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/orders') return Promise.resolve(mockPaginatedResponse) as never
      if (url === '/api/v1/companies') return Promise.resolve(mockLookupCompanies) as never
      if (url === '/api/v1/platforms') return Promise.resolve(mockLookupPlatforms) as never
      if (url === '/api/v1/stores') return Promise.resolve(mockLookupStores) as never
      return Promise.resolve([]) as never
    })

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

    wrapper = mount(OrdersPage, {
      attachTo: document.body,
      global: {
        stubs: {
          SearchOutlined: { template: '<span />' },
          ReloadOutlined: { template: '<span />' },
          EyeOutlined: { template: '<span />' },
          CopyOutlined: { template: '<span class="copy-icon-stub" />' },
          CascadeFilter: { template: '<div class="cascade-filter-stub" />' },
        },
      },
    })
    await flushPromises()
    await nextTick()
    return { wrapper, api }
  }

  it('renders page title and action buttons', 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)
    expect(buttonTexts.some((t) => t.includes('重置'))).toBe(true)
  }, 15000)

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

    expect(api.get).toHaveBeenCalledWith(
      '/api/v1/orders',
      expect.objectContaining({
        page: 1,
        per_page: 15,
      }),
    )
  })

  it('renders table with order data', async () => {
    await mountPage()

    const html = wrapper.html()
    expect(html).toContain('ORD-20260101-001')
    expect(html).toContain('199.99')
    expect(html).toContain('189.99')
  })

  it('renders company/platform/store names from lookup maps', async () => {
    await mountPage()

    const html = wrapper.html()
    expect(html).toContain('公司A')
    expect(html).toContain('店铺1')
  })

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

    const tags = wrapper.findAll('.ant-tag')
    const tagTexts = tags.map((t) => t.text())
    expect(tagTexts).toContain('已支付')
    expect(tagTexts).toContain('待付款')
  })

  it('renders CascadeFilter component', async () => {
    await mountPage()

    expect(wrapper.find('.cascade-filter-stub').exists()).toBe(true)
  })

  it('search button resets page to 1 and fetches', async () => {
    const { api } = await mountPage()

    const store = useOrderStore()
    store.pagination.page = 3
    store.filters.order_status_id = 3
    vi.mocked(api.get).mockClear()
    vi.mocked(api.get).mockResolvedValue(mockPaginatedResponse)

    const buttons = Array.from(document.body.querySelectorAll('.ant-btn'))
    const searchBtn = buttons.find((b) => b.textContent?.trim().includes('搜索'))
    searchBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    await flushPromises()
    await nextTick()

    expect(store.pagination.page).toBe(1)
    expect(api.get).toHaveBeenCalledWith(
      '/api/v1/orders',
      expect.objectContaining({
        page: 1,
        order_status_id: 3,
      }),
    )
  })

  it('resets filters when clicking reset button', async () => {
    await mountPage()

    const store = useOrderStore()
    store.filters.order_status_id = 3
    store.filters.platform_order_id = 'ORD-001'
    store.filters.created_date_range = ['2026-01-01', '2026-01-31']

    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.order_status_id).toBeUndefined()
    expect(store.filters.platform_order_id).toBe('')
    expect(store.filters.created_date_range).toBeNull()
  })

  it('opens detail drawer when clicking view button', async () => {
    const { api } = await mountPage()
    vi.mocked(api.get)
      .mockResolvedValueOnce(mockOrderDetail)
      .mockResolvedValueOnce(mockRawDetail)

    const buttons = Array.from(document.body.querySelectorAll('.ant-btn'))
    const viewBtn = buttons.find((b) => b.textContent?.trim() === '查看')
    expect(viewBtn).toBeDefined()

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

    const drawer = document.body.querySelector('.ant-drawer')
    expect(drawer).toBeTruthy()
  })

  it('displays ext JSON in drawer when available', async () => {
    const { api } = await mountPage()
    vi.mocked(api.get)
      .mockResolvedValueOnce(mockOrderDetail)
      .mockResolvedValueOnce(mockRawDetail)

    const buttons = Array.from(document.body.querySelectorAll('.ant-btn'))
    const viewBtn = buttons.find((b) => b.textContent?.trim() === '查看')
    viewBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    await flushPromises()
    await nextTick()

    const drawerHtml = document.body.querySelector('.ant-drawer')?.innerHTML || ''
    expect(drawerHtml).toContain('remark')
    expect(drawerHtml).toContain('加急发货')
  })

  it('displays raw JSON in drawer when available', async () => {
    const { api } = await mountPage()
    vi.mocked(api.get)
      .mockResolvedValueOnce(mockOrderDetail)
      .mockResolvedValueOnce(mockRawDetail)

    const buttons = Array.from(document.body.querySelectorAll('.ant-btn'))
    const viewBtn = buttons.find((b) => b.textContent?.trim() === '查看')
    viewBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    await flushPromises()
    await nextTick()

    const drawerHtml = document.body.querySelector('.ant-drawer')?.innerHTML || ''
    expect(drawerHtml).toContain('tmall_001')
    expect(drawerHtml).toContain('TRADE_FINISHED')
  })

  it('shows placeholder when ext/raw is null', async () => {
    const { api } = await mountPage()
    vi.mocked(api.get)
      .mockResolvedValueOnce({ ...mockOrderDetail, ext: null, order_items: [] })
      .mockRejectedValueOnce(new Error('Not found'))

    const buttons = Array.from(document.body.querySelectorAll('.ant-btn'))
    const viewBtn = buttons.find((b) => b.textContent?.trim() === '查看')
    viewBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    await flushPromises()
    await nextTick()

    const drawerHtml = document.body.querySelector('.ant-drawer')?.innerHTML || ''
    expect(drawerHtml).toContain('暂无数据')
  })

  // ─── P17.1 双栏布局测试 ─────────────────────────────────

  async function openDrawerWithData(options?: { rawFail?: boolean }) {
    const { api } = await mountPage()
    if (options?.rawFail) {
      vi.mocked(api.get)
        .mockResolvedValueOnce(mockOrderDetail)
        .mockRejectedValueOnce(new Error('Not found'))
    } else {
      vi.mocked(api.get)
        .mockResolvedValueOnce(mockOrderDetail)
        .mockResolvedValueOnce(mockRawDetail)
    }

    const buttons = Array.from(document.body.querySelectorAll('.ant-btn'))
    const viewBtn = buttons.find((b) => b.textContent?.trim() === '查看')
    viewBtn?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
    await flushPromises()
    await nextTick()
    return { api }
  }

  it('renders two-column layout in drawer', async () => {
    await openDrawerWithData()

    const drawer = document.body.querySelector('.ant-drawer')
    const row = drawer?.querySelector('.ant-row')
    expect(row).toBeTruthy()
    const cols = row?.querySelectorAll(':scope > .ant-col')
    expect(cols?.length).toBe(2)
  })

  it('left column contains all business sections', async () => {
    await openDrawerWithData()

    const drawer = document.body.querySelector('.ant-drawer')
    const cols = drawer?.querySelectorAll('.ant-row > .ant-col')
    const leftCol = cols?.[0]
    expect(leftCol).toBeTruthy()

    const leftHtml = leftCol?.innerHTML || ''
    expect(leftHtml).toContain('基本信息')
    expect(leftHtml).toContain('金额信息')
    expect(leftHtml).toContain('时间与地址')
    expect(leftHtml).toContain('订单子项')
    expect(leftHtml).toContain('扩展数据')
  })

  it('right column renders raw JSON', async () => {
    await openDrawerWithData()

    const drawer = document.body.querySelector('.ant-drawer')
    const cols = drawer?.querySelectorAll('.ant-row > .ant-col')
    const rightCol = cols?.[1]
    expect(rightCol).toBeTruthy()

    const rightHtml = rightCol?.innerHTML || ''
    expect(rightHtml).toContain('原始数据')
    expect(rightHtml).toContain('tmall_001')
    expect(rightHtml).toContain('TRADE_FINISHED')
  })

  it('right column shows empty state when raw is null', async () => {
    await openDrawerWithData({ rawFail: true })

    const drawer = document.body.querySelector('.ant-drawer')
    const cols = drawer?.querySelectorAll('.ant-row > .ant-col')
    const rightCol = cols?.[1]
    expect(rightCol).toBeTruthy()

    const rightHtml = rightCol?.innerHTML || ''
    expect(rightHtml).toContain('暂无原始数据')
  })

  it('right column copy button calls clipboard API', async () => {
    const writeTextMock = vi.fn().mockResolvedValue(undefined)
    Object.assign(navigator, {
      clipboard: { writeText: writeTextMock },
    })

    await openDrawerWithData()

    const drawer = document.body.querySelector('.ant-drawer')
    const cols = drawer?.querySelectorAll('.ant-row > .ant-col')
    const rightCol = cols?.[1]
    const copyBtn = rightCol?.querySelector('.ant-btn')
    expect(copyBtn).toBeTruthy()

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

    expect(writeTextMock).toHaveBeenCalledWith(
      JSON.stringify(mockRawDetail.raw, null, 2),
    )
  })

  it('left column does not contain raw data section', async () => {
    await openDrawerWithData()

    const drawer = document.body.querySelector('.ant-drawer')
    const cols = drawer?.querySelectorAll('.ant-row > .ant-col')
    const leftCol = cols?.[0]
    const leftHtml = leftCol?.innerHTML || ''

    expect(leftHtml).not.toContain('tmall_001')
    expect(leftHtml).not.toContain('TRADE_FINISHED')
  })

  it('table copy button is before platform_order_id text', async () => {
    await mountPage()

    const table = document.body.querySelector('.ant-table')
    const cells = table?.querySelectorAll('td') || []
    let targetCell: Element | null = null
    cells.forEach((cell) => {
      if (cell.textContent?.includes('ORD-20260101-001')) {
        targetCell = cell
      }
    })
    expect(targetCell).toBeTruthy()

    const span = targetCell!.querySelector('.inline-flex')
    expect(span).toBeTruthy()
    const children = span?.children
    // First child is copy icon, second is text
    expect(children?.[0]?.classList.contains('copy-icon-stub')).toBe(true)
    expect(children?.[1]?.textContent).toContain('ORD-20260101-001')
  })

  it('table copy button calls clipboard API', async () => {
    const writeTextMock = vi.fn().mockResolvedValue(undefined)
    Object.assign(navigator, {
      clipboard: { writeText: writeTextMock },
    })

    await mountPage()

    const table = document.body.querySelector('.ant-table')
    const copyIcon = table?.querySelector('.copy-icon-stub')
    expect(copyIcon).toBeTruthy()

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

    expect(writeTextMock).toHaveBeenCalledWith('ORD-20260101-001')
  })

  // ─── P18.2 URL 参数自动填充测试 ───────────────────────────

  it('fills filter and fetches when auto_submit=1 with platform_order_id', async () => {
    mockRouteQuery = { platform_order_id: 'ORD-FROM-URL', auto_submit: '1' }
    const { api } = await mountPage()

    const store = useOrderStore()
    expect(store.filters.platform_order_id).toBe('ORD-FROM-URL')
    expect(api.get).toHaveBeenCalledWith(
      '/api/v1/orders',
      expect.objectContaining({
        platform_order_id: 'ORD-FROM-URL',
        page: 1,
      }),
    )
  })

  it('does not fill filter when platform_order_id present but no auto_submit', async () => {
    mockRouteQuery = { platform_order_id: 'ORD-FROM-URL' }
    await mountPage()

    const store = useOrderStore()
    expect(store.filters.platform_order_id).toBe('')
  })

  it('does not fill filter when only auto_submit is present without platform_order_id', async () => {
    mockRouteQuery = { auto_submit: '1' }
    await mountPage()

    const store = useOrderStore()
    expect(store.filters.platform_order_id).toBe('')
  })
})
