import type { ReactNode } from 'react'
import { render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import type { Device, AdminDeviceListParams } from '@/types/device'
import { DevicesPage } from './DevicesPage'

const listAllDevicesMock = vi.fn<(params?: AdminDeviceListParams) => Promise<{ items: Device[]; total: number; page: number; page_size: number }>>()
const deleteDeviceMock = vi.fn<(id: number) => Promise<void>>()
const trustDeviceMock = vi.fn<(id: number, duration?: string) => Promise<void>>()
const untrustDeviceMock = vi.fn<(id: number) => Promise<void>>()

vi.mock('antd', async () => {
  const React = await import('react')
  void React // suppress unused warning

  function resolveRowKey<RecordType extends Record<string, unknown>>(
    record: RecordType,
    rowKey: string | ((row: RecordType) => string | number) | undefined,
    index: number,
  ): string {
    if (typeof rowKey === 'function') {
      return String(rowKey(record))
    }
    if (typeof rowKey === 'string') {
      return String(record[rowKey] ?? index)
    }
    return String(index)
  }

  function flattenChildren(children: ReactNode): string {
    if (typeof children === 'string' || typeof children === 'number') {
      return String(children)
    }
    if (Array.isArray(children)) {
      return children.map(flattenChildren).join(' ').trim()
    }
    return ''
  }

  return {
    message: {
      success: vi.fn(),
      error: vi.fn(),
    },
    Button: ({
      children,
      onClick,
      htmlType,
      type: buttonType,
      icon,
      ...props
    }: {
      children?: ReactNode
      onClick?: () => void
      htmlType?: 'button' | 'submit' | 'reset'
      type?: 'link' | 'text' | 'default' | 'primary'
      icon?: ReactNode
      danger?: boolean
      [key: string]: unknown
    }) => {
      void buttonType
      void icon
      void props

      return (
        <button type={htmlType ?? 'button'} onClick={onClick}>
          {icon && <span>{flattenChildren(icon)}</span>}
          {children}
        </button>
      )
    },
    Input: ({
      onPressEnter,
      prefix,
      allowClear,
      type,
      ...props
    }: {
      onPressEnter?: () => void
      prefix?: ReactNode
      allowClear?: boolean
      type?: string
      [key: string]: unknown
    }) => {
      void prefix
      void allowClear
      void props

      return (
        <input
          type={type ?? 'text'}
          onKeyDown={(event) => {
            if (event.key === 'Enter') {
              onPressEnter?.()
            }
          }}
        />
      )
    },
    Select: ({
      value,
      onChange,
      options = [],
      placeholder,
      allowClear,
      ...props
    }: {
      value?: string | number | boolean
      onChange?: (value: unknown) => void
      options?: Array<{ value: string | number | boolean, label: ReactNode }>
      placeholder?: string
      allowClear?: boolean
      [key: string]: unknown
    }) => {
      void allowClear
      void props

      return (
        <select
          aria-label={placeholder ?? 'select'}
          value={value === undefined ? '' : String(value)}
          onChange={(event) => {
            const nextValue = event.target.value
            if (nextValue === '') {
              onChange?.(undefined)
              return
            }

            if (value === true || value === false) {
              onChange?.(nextValue === 'true')
              return
            }

            const matchedOption = options.find((option) => String(option.value) === nextValue)
            onChange?.(matchedOption?.value ?? nextValue)
          }}
        >
          <option value="">{placeholder ?? 'all'}</option>
          {options.map((option) => (
            <option key={String(option.value)} value={String(option.value)}>
              {flattenChildren(option.label)}
            </option>
          ))}
        </select>
      )
    },
    Popconfirm: ({
      title,
      onConfirm,
      children,
    }: {
      title?: ReactNode
      onConfirm?: () => void
      children?: ReactNode
    }) => (
      <div data-testid="popconfirm" data-title={String(title)}>
        <span>{children}</span>
        <button type="button" onClick={onConfirm}>confirm</button>
      </div>
    ),
    Space: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
    Table: ({
      columns,
      dataSource,
      rowKey,
      locale,
      pagination,
    }: {
      columns: Array<{
        key?: string
        title?: ReactNode
        dataIndex?: string
        render?: (value: unknown, record: Record<string, unknown>, index: number) => ReactNode
      }>
      dataSource?: Array<Record<string, unknown>>
      rowKey?: string | ((row: Record<string, unknown>) => string | number)
      locale?: { emptyText?: ReactNode }
      pagination?: {
        current?: number
        pageSize?: number
        total?: number
        onChange?: (page: number, pageSize: number) => void
      }
    }) => {
      const rows = dataSource ?? []

      if (rows.length === 0) {
        return <div>{locale?.emptyText ?? null}</div>
      }

      return (
        <div>
          <table>
            <thead>
              <tr>
                {columns.map((column, index) => (
                  <th key={column.key ?? column.dataIndex ?? index}>{column.title}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {rows.map((record, rowIndex) => (
                <tr
                  key={resolveRowKey(record, rowKey, rowIndex)}
                  data-testid={`table-row-${resolveRowKey(record, rowKey, rowIndex)}`}
                >
                  {columns.map((column, columnIndex) => {
                    const value = column.dataIndex ? record[column.dataIndex] : undefined
                    const content = column.render ? column.render(value, record, rowIndex) : value
                    return (
                      <td key={column.key ?? column.dataIndex ?? columnIndex}>
                        {content as ReactNode}
                      </td>
                    )
                  })}
                </tr>
              ))}
            </tbody>
          </table>
          <button
            type="button"
            onClick={() => pagination?.onChange?.(1, 50)}
          >
            paginate
          </button>
          <span>{`${pagination?.current ?? 1}-${pagination?.pageSize ?? 20}-${pagination?.total ?? rows.length}`}</span>
        </div>
      )
    },
    Tag: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
  }
})

vi.mock('@ant-design/icons', () => ({
  SearchOutlined: () => <span>search</span>,
  ReloadOutlined: () => <span>reload</span>,
  DeleteOutlined: () => <span>delete</span>,
}))

vi.mock('@/components/common', () => ({
  PageHeader: ({
    title,
    description,
    actions,
  }: {
    title: ReactNode
    description?: ReactNode
    actions?: ReactNode
  }) => (
    <section data-testid="page-header">
      <h1>{title}</h1>
      <p>{description}</p>
      {actions}
    </section>
  ),
}))

vi.mock('@/components/feedback', () => ({
  PageEmpty: ({ description }: { description?: ReactNode }) => <div>{description ?? 'empty'}</div>,
  PageError: ({
    description,
    onRetry,
  }: {
    description?: ReactNode
    onRetry?: () => void
  }) => (
    <div>
      <p>{description}</p>
      <button type="button" onClick={onRetry}>retry</button>
    </div>
  ),
}))

vi.mock('@/components/layout', () => ({
  PageLayout: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
  FilterCard: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
  TableCard: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
}))

vi.mock('@/services/devices', () => ({
  listAllDevices: (params?: AdminDeviceListParams) => listAllDevicesMock(params),
  adminDeleteDevice: (id: number) => deleteDeviceMock(id),
  adminTrustDevice: (id: number, duration?: string) => trustDeviceMock(id, duration),
  adminUntrustDevice: (id: number) => untrustDeviceMock(id),
}))

function buildDevice(id: number, userId: number, isTrusted: boolean, status: 0 | 1 = 1): Device {
  return {
    id,
    user_id: userId,
    device_id: `device-${id}`,
    device_name: `Device ${id}`,
    device_type: 1, // Web
    device_os: 'Windows 10',
    device_browser: 'Chrome',
    ip: `192.168.1.${id}`,
    location: 'Shanghai',
    status,
    is_trusted: isTrusted,
    trust_expires_at: isTrusted ? '2026-04-30T00:00:00Z' : null,
    last_active_time: '2026-03-27T10:00:00Z',
    created_at: '2026-01-15T00:00:00Z',
    updated_at: '2026-03-27T10:00:00Z',
  }
}

describe('DevicesPage', () => {
  let currentDevices: Device[]

  beforeEach(() => {
    currentDevices = [
      buildDevice(1, 7, true, 1),
      buildDevice(2, 8, false, 1),
      buildDevice(3, 7, false, 0),
    ]

    listAllDevicesMock.mockReset()
    deleteDeviceMock.mockReset()
    trustDeviceMock.mockReset()
    untrustDeviceMock.mockReset()

    listAllDevicesMock.mockImplementation(async (params) => {
      const page = params?.page ?? 1
      const pageSize = params?.page_size ?? 20

      let items = [...currentDevices]

      if (params?.keyword) {
        const kw = params.keyword.toLowerCase()
        items = items.filter(
          (d) =>
            d.device_name.toLowerCase().includes(kw) ||
            d.ip.toLowerCase().includes(kw) ||
            (d.location && d.location.toLowerCase().includes(kw)),
        )
      }

      if (params?.user_id !== undefined) {
        items = items.filter((d) => d.user_id === params.user_id)
      }

      if (params?.status !== undefined) {
        items = items.filter((d) => d.status === params.status)
      }

      if (params?.is_trusted !== undefined) {
        items = items.filter((d) => d.is_trusted === params.is_trusted)
      }

      const total = items.length
      const start = (page - 1) * pageSize
      const pagedItems = items.slice(start, start + pageSize)

      return {
        items: pagedItems,
        total,
        page,
        page_size: pageSize,
      }
    })
  })

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

  it('loads device list and renders table', async () => {
    render(<DevicesPage />)

    expect(await screen.findByText('Device 1')).toBeInTheDocument()
    expect(screen.getByText('Device 2')).toBeInTheDocument()
    expect(screen.getByText('Device 3')).toBeInTheDocument()
    expect(listAllDevicesMock).toHaveBeenLastCalledWith(
      expect.objectContaining({ size: 20 }),
    )
  })

  it('shows error state and retry', async () => {
    const user = userEvent.setup()

    listAllDevicesMock.mockReset()
    listAllDevicesMock.mockRejectedValueOnce(new Error('network error'))
    listAllDevicesMock.mockResolvedValue({
      items: currentDevices,
      total: currentDevices.length,
      page: 1,
      page_size: 20,
    })

    render(<DevicesPage />)

    expect(await screen.findByText('network error')).toBeInTheDocument()

    await user.click(screen.getByRole('button', { name: 'retry' }))

    await waitFor(() => {
      expect(screen.getByText('Device 1')).toBeInTheDocument()
    })
  })

  it('renders empty state when no devices', async () => {
    listAllDevicesMock.mockResolvedValue({
      items: [],
      total: 0,
      page: 1,
      page_size: 20,
    })

    render(<DevicesPage />)

    expect(await screen.findByText('暂无设备数据')).toBeInTheDocument()
  })

  it('renders page header with title and description', async () => {
    render(<DevicesPage />)

    const header = screen.getByTestId('page-header')
    expect(within(header).getByText('设备管理')).toBeInTheDocument()
    expect(within(header).getByText('管理系统所有设备，支持查看、信任状态管理和删除')).toBeInTheDocument()
  })

  it('renders refresh button', async () => {
    render(<DevicesPage />)

    await screen.findByText('Device 1')
    expect(screen.getByRole('button', { name: '刷新' })).toBeInTheDocument()
  })
})
