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

import type { Role } from '@/types'
import type { PaginatedData } from '@/types/http'
import type { User, UserListParams, UserStatus } from '@/types/user'
import { UsersPage } from './UsersPage'

const originalGetComputedStyle = window.getComputedStyle
const useAuthMock = vi.fn()
const listUsersMock = vi.fn<(params: UserListParams) => Promise<PaginatedData<User>>>()
const deleteUserMock = vi.fn<(id: number) => Promise<void>>()
const updateUserStatusMock = vi.fn<(id: number, payload: { status: UserStatus }) => Promise<void>>()
const getUserRolesMock = vi.fn<(id: number) => Promise<Role[]>>()
const listRolesMock = vi.fn<() => Promise<PaginatedData<Role>>>()

vi.mock('antd', async () => {
  const actual = await vi.importActual<typeof import('antd')>('antd')

  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)
  }

  return {
    ...actual,
    Popconfirm: ({
      children,
      title,
      onConfirm,
    }: {
      children: ReactElement
      title?: ReactNode
      onConfirm?: () => void
    }) => (
      <div>
        {children}
        <button type="button" onClick={() => onConfirm?.()}>
          {typeof title === 'string' ? title : 'confirm action'}
        </button>
      </div>
    ),
    Table: ({
      columns,
      dataSource,
      rowKey,
      locale,
    }: {
      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 }
    }) => {
      const rows = dataSource ?? []

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

      return (
        <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>
      )
    },
  }
})

vi.mock('@/app/providers/auth-context', () => ({
  useAuth: () => useAuthMock(),
}))

vi.mock('@/services/users', () => ({
  listUsers: (params: UserListParams) => listUsersMock(params),
  deleteUser: (id: number) => deleteUserMock(id),
  updateUserStatus: (id: number, payload: { status: UserStatus }) => updateUserStatusMock(id, payload),
  getUserRoles: (id: number) => getUserRolesMock(id),
}))

vi.mock('@/services/roles', () => ({
  listRoles: () => listRolesMock(),
}))

vi.mock('./UserDetailDrawer', () => ({
  UserDetailDrawer: ({
    open,
    userId,
    onClose,
  }: {
    open: boolean
    userId?: number
    onClose: () => void
  }) => (
    open ? (
      <div data-testid="user-detail-drawer">
        <span>{`detail:${userId}`}</span>
        <button type="button" onClick={onClose}>
          detail close
        </button>
      </div>
    ) : null
  ),
}))

vi.mock('./UserEditDrawer', () => ({
  UserEditDrawer: ({
    open,
    user,
    onSuccess,
    onClose,
  }: {
    open: boolean
    user: User | null
    onSuccess: () => void
    onClose: () => void
  }) => (
    open ? (
      <div data-testid="user-edit-drawer">
        <span>{`edit:${user?.username ?? 'none'}`}</span>
        <button type="button" onClick={onSuccess}>
          edit success
        </button>
        <button type="button" onClick={onClose}>
          edit close
        </button>
      </div>
    ) : null
  ),
}))

vi.mock('./AssignRolesModal', () => ({
  AssignRolesModal: ({
    open,
    user,
    currentRoles,
    allRoles,
    onSuccess,
    onClose,
  }: {
    open: boolean
    user: User | null
    currentRoles: Role[]
    allRoles: Role[]
    onSuccess: () => void
    onClose: () => void
  }) => (
    open ? (
      <div data-testid="assign-roles-modal">
        <span>{`assign:${user?.username ?? 'none'}:${currentRoles.length}:${allRoles.length}`}</span>
        <button type="button" onClick={onSuccess}>
          assign success
        </button>
        <button type="button" onClick={onClose}>
          assign close
        </button>
      </div>
    ) : null
  ),
}))

vi.mock('./CreateUserModal', () => ({
  CreateUserModal: ({
    open,
    roles,
    onSuccess,
    onClose,
  }: {
    open: boolean
    roles: Role[]
    onSuccess: () => void
    onClose: () => void
  }) => (
    open ? (
      <div data-testid="create-user-modal">
        <span>{`create:${roles.length}`}</span>
        <button type="button" onClick={onSuccess}>
          create success
        </button>
        <button type="button" onClick={onClose}>
          create close
        </button>
      </div>
    ) : null
  ),
}))

const roles: Role[] = [
  {
    id: 10,
    name: '管理员',
    code: 'admin',
    description: 'system administrator',
    is_system: true,
    is_default: false,
    status: 1,
  },
  {
    id: 20,
    name: '审计员',
    code: 'auditor',
    description: 'audit operator',
    is_system: false,
    is_default: false,
    status: 1,
  },
]

function buildUser(id: number, username: string, status: UserStatus): User {
  return {
    id,
    username,
    email: `${username}@example.com`,
    phone: `13800000${String(id).padStart(3, '0')}`,
    nickname: `${username}-nick`,
    avatar: '',
    gender: 0,
    birthday: '',
    region: '',
    bio: '',
    status,
    last_login_at: '2026-03-27 12:30:00',
    last_login_ip: '127.0.0.1',
    created_at: `2026-03-2${id} 10:00:00`,
    updated_at: `2026-03-2${id} 11:00:00`,
  }
}

function cloneUser(user: User): User {
  return { ...user }
}

describe('UsersPage', () => {
  let currentUsers: User[]

  beforeEach(() => {
    currentUsers = [
      buildUser(1, 'admin-root', 1),
      buildUser(2, 'disabled-user', 3),
      buildUser(3, 'locked-user', 2),
      buildUser(4, 'pending-user', 0),
      buildUser(5, 'editor-user', 1),
    ]

    useAuthMock.mockReturnValue({
      user: {
        id: 1,
        username: 'admin-root',
        email: 'admin-root@example.com',
        phone: '13800000001',
        nickname: 'admin-root',
        avatar: '',
        status: 1,
      },
      roles: [],
      isAdmin: true,
      isAuthenticated: true,
      isLoading: false,
      onLoginSuccess: vi.fn(),
      logout: vi.fn(),
      refreshUser: vi.fn(),
    })

    listUsersMock.mockReset()
    deleteUserMock.mockReset()
    updateUserStatusMock.mockReset()
    getUserRolesMock.mockReset()
    listRolesMock.mockReset()

    listUsersMock.mockImplementation(async (params: UserListParams) => {
      const keyword = (params.keyword || '').toLowerCase()
      const items = currentUsers.filter((user) => {
        if (!keyword) {
          return true
        }
        return [
          user.username,
          user.email,
          user.phone,
          user.nickname,
        ].some((value) => value.toLowerCase().includes(keyword))
      })

      return {
        items: items.map(cloneUser),
        total: items.length,
        page: params.page ?? 1,
        page_size: params.page_size ?? 20,
      }
    })

    deleteUserMock.mockImplementation(async (id: number) => {
      currentUsers = currentUsers.filter((user) => user.id !== id)
    })

    updateUserStatusMock.mockImplementation(async (id: number, payload: { status: UserStatus }) => {
      currentUsers = currentUsers.map((user) => (
        user.id === id ? { ...user, status: payload.status } : user
      ))
    })

    getUserRolesMock.mockImplementation(async (id: number) => (
      id === 5 ? [roles[0], roles[1]] : [roles[1]]
    ))

    listRolesMock.mockResolvedValue({
      items: roles,
      total: roles.length,
      page: 1,
      page_size: 100,
    })

    vi.spyOn(window, 'getComputedStyle').mockImplementation((element) => (
      originalGetComputedStyle.call(window, element)
    ))
    vi.spyOn(message, 'success').mockImplementation(() => undefined as never)
    vi.spyOn(message, 'error').mockImplementation(() => undefined as never)
  })

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

  it('loads, filters, resets, and refreshes the user list', async () => {
    const user = userEvent.setup()

    render(<UsersPage />)

    expect(await screen.findByText('admin-root')).toBeInTheDocument()
    expect(screen.getByText('disabled-user')).toBeInTheDocument()
    expect(listRolesMock).toHaveBeenCalledTimes(1)

    await user.type(screen.getByPlaceholderText('用户名/邮箱/手机号'), 'editor')

    await waitFor(() => expect(screen.queryByText('admin-root')).not.toBeInTheDocument())
    expect(screen.getByText('editor-user')).toBeInTheDocument()
    expect(listUsersMock).toHaveBeenLastCalledWith(expect.objectContaining({ keyword: 'editor' }))

    await user.click(screen.getByRole('button', { name: /重\s*置$/ }))

    await waitFor(() => expect(screen.getByText('admin-root')).toBeInTheDocument())
    expect(screen.getByText('disabled-user')).toBeInTheDocument()
    expect(listUsersMock).toHaveBeenLastCalledWith(expect.objectContaining({ keyword: undefined }))

    const callCountBeforeRefresh = listUsersMock.mock.calls.length
    await user.click(screen.getByRole('button', { name: /刷新$/ }))

    await waitFor(() => expect(listUsersMock.mock.calls.length).toBeGreaterThan(callCountBeforeRefresh))
  })

  it('opens detail, edit, create, and assign-role overlays and refetches after success', async () => {
    const user = userEvent.setup()

    render(<UsersPage />)

    expect(await screen.findByText('editor-user')).toBeInTheDocument()

    const callCountBeforeCreate = listUsersMock.mock.calls.length
    await user.click(screen.getByRole('button', { name: /创建用户$/ }))
    expect(screen.getByTestId('create-user-modal')).toHaveTextContent('create:2')

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

    await waitFor(() => expect(listUsersMock.mock.calls.length).toBeGreaterThan(callCountBeforeCreate))
    await waitFor(() => expect(screen.queryByTestId('create-user-modal')).not.toBeInTheDocument())

    const editorRow = screen.getByTestId('table-row-5')

    await user.click(within(editorRow).getByRole('button', { name: /详情$/ }))
    expect(screen.getByTestId('user-detail-drawer')).toHaveTextContent('detail:5')

    await user.click(screen.getByRole('button', { name: 'detail close' }))
    await waitFor(() => expect(screen.queryByTestId('user-detail-drawer')).not.toBeInTheDocument())

    const callCountBeforeEdit = listUsersMock.mock.calls.length
    await user.click(within(editorRow).getByRole('button', { name: /编辑$/ }))
    expect(screen.getByTestId('user-edit-drawer')).toHaveTextContent('edit:editor-user')

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

    await waitFor(() => expect(listUsersMock.mock.calls.length).toBeGreaterThan(callCountBeforeEdit))
    await waitFor(() => expect(screen.queryByTestId('user-edit-drawer')).not.toBeInTheDocument())

    const callCountBeforeAssign = listUsersMock.mock.calls.length
    await user.click(within(editorRow).getByRole('button', { name: /角色$/ }))

    await waitFor(() => expect(getUserRolesMock).toHaveBeenCalledWith(5))
    expect(screen.getByTestId('assign-roles-modal')).toHaveTextContent('assign:editor-user:2:2')

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

    await waitFor(() => expect(listUsersMock.mock.calls.length).toBeGreaterThan(callCountBeforeAssign))
    await waitFor(() => expect(screen.queryByTestId('assign-roles-modal')).not.toBeInTheDocument())
  })

  it('blocks self deletion, updates each status branch, and deletes other users', async () => {
    const user = userEvent.setup()

    render(<UsersPage />)

    expect(await screen.findByText('admin-root')).toBeInTheDocument()

    const selfRow = screen.getByTestId('table-row-1')
    await user.click(within(selfRow).getByRole('button', { name: '确定要删除用户「admin-root」吗？此操作不可恢复。' }))

    await waitFor(() => expect(message.error).toHaveBeenCalledWith('不能删除当前登录的账号'))
    expect(deleteUserMock).not.toHaveBeenCalled()

    await user.click(within(screen.getByTestId('table-row-5')).getByRole('button', { name: '确定要禁用该用户吗？' }))
    await waitFor(() => expect(updateUserStatusMock).toHaveBeenCalledWith(5, { status: 3 }))
    await waitFor(() => expect(within(screen.getByTestId('table-row-5')).getByRole('button', { name: '激活' })).toBeInTheDocument())

    await user.click(within(screen.getByTestId('table-row-2')).getByRole('button', { name: '确定要激活该用户吗？' }))
    await waitFor(() => expect(updateUserStatusMock).toHaveBeenCalledWith(2, { status: 1 }))
    await waitFor(() => expect(within(screen.getByTestId('table-row-2')).getByRole('button', { name: '禁用' })).toBeInTheDocument())

    await user.click(within(screen.getByTestId('table-row-3')).getByRole('button', { name: '该用户因多次失败已被锁定，确定要解锁并激活吗？' }))
    await waitFor(() => expect(updateUserStatusMock).toHaveBeenCalledWith(3, { status: 1 }))

    await user.click(within(screen.getByTestId('table-row-4')).getByRole('button', { name: '该用户尚未激活，确定要激活该用户吗？' }))
    await waitFor(() => expect(updateUserStatusMock).toHaveBeenCalledWith(4, { status: 1 }))

    await user.click(within(screen.getByTestId('table-row-2')).getByRole('button', { name: '确定要删除用户「disabled-user」吗？此操作不可恢复。' }))
    await waitFor(() => expect(deleteUserMock).toHaveBeenCalledWith(2))
    await waitFor(() => expect(screen.queryByTestId('table-row-2')).not.toBeInTheDocument())
    expect(message.success).toHaveBeenCalledWith('用户 disabled-user 已删除')
  })

  it('surfaces role-loading failures without opening the modal', async () => {
    const user = userEvent.setup()
    getUserRolesMock.mockRejectedValueOnce(new Error('获取用户角色失败'))

    render(<UsersPage />)

    expect(await screen.findByText('editor-user')).toBeInTheDocument()

    await user.click(within(screen.getByTestId('table-row-5')).getByRole('button', { name: /角色$/ }))

    await waitFor(() => expect(message.error).toHaveBeenCalledWith('获取用户角色失败'))
    expect(screen.queryByTestId('assign-roles-modal')).not.toBeInTheDocument()
  })

  it('renders the page error state and retries fetching', async () => {
    const user = userEvent.setup()

    listUsersMock.mockReset()
    listUsersMock.mockRejectedValueOnce(new Error('用户列表拉取失败'))
    listUsersMock.mockImplementation(async (params: UserListParams) => ({
      items: currentUsers.map(cloneUser),
      total: currentUsers.length,
      page: params.page ?? 1,
      page_size: params.page_size ?? 20,
    }))

    render(<UsersPage />)

    expect(await screen.findByText('用户列表拉取失败')).toBeInTheDocument()

    await user.click(screen.getByRole('button', { name: /重新加载$/ }))

    await waitFor(() => expect(screen.getByText('admin-root')).toBeInTheDocument())
    expect(listUsersMock).toHaveBeenCalledTimes(2)
  })
})
