import type { ReactElement, ReactNode } from 'react'
import { cloneElement, isValidElement } 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 { Permission, PermissionStatus } from '@/types/permission'
import { PermissionsPage } from './PermissionsPage'

const getPermissionTreeMock = vi.fn<() => Promise<Permission[]>>()
const listPermissionsMock = vi.fn<() => Promise<Permission[]>>()
const deletePermissionMock = vi.fn<(id: number) => Promise<void>>()
const updatePermissionStatusMock = vi.fn<(id: number, status: PermissionStatus) => Promise<void>>()
const messageSuccessMock = vi.fn()
const messageErrorMock = vi.fn()

vi.mock('antd', async () => {
  type MockTreeNode = {
    key: string
    title: ReactNode
    children?: MockTreeNode[]
  }

  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 renderTreeNodes(nodes: MockTreeNode[]): ReactNode {
    return nodes.map((node) => (
      <div key={node.key} data-testid={`tree-node-${node.key}`}>
        {node.title}
        {node.children ? renderTreeNodes(node.children) : null}
      </div>
    ))
  }

  return {
    Button: ({
      children,
      onClick,
      htmlType,
      type: buttonType,
      icon,
      danger,
      ...props
    }: {
      children?: ReactNode
      onClick?: () => void
      htmlType?: 'button' | 'submit' | 'reset'
      [key: string]: unknown
    }) => {
      void buttonType
      void icon
      void danger

      return (
        <button type={htmlType ?? 'button'} onClick={onClick} {...props}>
          {children}
        </button>
      )
    },
    Popconfirm: ({
      children,
      onConfirm,
    }: {
      children: ReactElement
      onConfirm?: () => void
    }) => {
      if (!isValidElement(children)) {
        return children
      }

      const child = children as ReactElement<{ onClick?: () => void }>

      return cloneElement(child, {
        onClick: () => onConfirm?.(),
      })
    },
    Segmented: ({
      options,
      onChange,
    }: {
      options: Array<{ label: ReactNode, value: string }>
      onChange?: (value: string) => void
    }) => (
      <div>
        {options.map((option) => (
          <button
            key={option.value}
            type="button"
            onClick={() => onChange?.(option.value)}
          >
            {option.label}
          </button>
        ))}
      </div>
    ),
    Space: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
    Spin: ({ children }: { children?: ReactNode }) => <div>{children}</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>
      )
    },
    Tag: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
    Tree: ({
      treeData = [],
    }: {
      treeData?: MockTreeNode[]
    }) => <div>{renderTreeNodes(treeData)}</div>,
    message: {
      success: (content: string) => messageSuccessMock(content),
      error: (content: string) => messageErrorMock(content),
    },
  }
})

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

vi.mock('@/components/common', () => ({
  PageHeader: ({
    title,
    description,
    actions,
  }: {
    title: ReactNode
    description?: ReactNode
    actions?: ReactNode
  }) => (
    <section>
      <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>,
  TreeCard: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
}))

vi.mock('@/services/permissions', () => ({
  getPermissionTree: () => getPermissionTreeMock(),
  listPermissions: () => listPermissionsMock(),
  deletePermission: (id: number) => deletePermissionMock(id),
  updatePermissionStatus: (id: number, status: PermissionStatus) => updatePermissionStatusMock(id, status),
}))

vi.mock('./PermissionFormModal', () => ({
  PermissionFormModal: ({
    open,
    permission,
    parentPermission,
    onSuccess,
    onClose,
  }: {
    open: boolean
    permission: Permission | null
    parentPermission: Permission | null
    onSuccess: () => void
    onClose: () => void
  }) => (
    open ? (
      <div data-testid="permission-form-modal">
        <span>{`permission-form:${permission?.code ?? 'create'}:${parentPermission?.code ?? 'root'}`}</span>
        <button type="button" onClick={onSuccess}>permission form success</button>
        <button type="button" onClick={onClose}>permission form close</button>
      </div>
    ) : null
  ),
}))

function buildPermission(
  id: number,
  name: string,
  code: string,
  status: PermissionStatus,
  parentId: number | null = null,
  children?: Permission[],
): Permission {
  return {
    id,
    name,
    code,
    type: 'menu',
    parent_id: parentId,
    path: `/admin/${code}`,
    icon: `${code}-icon`,
    sort: id,
    status,
    children,
  }
}

function flattenPermissions(permissions: Permission[]): Permission[] {
  return permissions.flatMap((permission) => [
    { ...permission, children: permission.children?.map((child) => ({ ...child })) },
    ...(permission.children ? flattenPermissions(permission.children) : []),
  ])
}

function togglePermissionStatus(permissions: Permission[], id: number, status: PermissionStatus): Permission[] {
  return permissions.map((permission) => (
    permission.id === id
      ? { ...permission, status }
      : {
        ...permission,
        children: permission.children ? togglePermissionStatus(permission.children, id, status) : undefined,
      }
  ))
}

function deletePermissionFromTree(permissions: Permission[], id: number): Permission[] {
  return permissions
    .filter((permission) => permission.id !== id)
    .map((permission) => ({
      ...permission,
      children: permission.children ? deletePermissionFromTree(permission.children, id) : undefined,
    }))
}

describe('PermissionsPage', () => {
  let currentTreePermissions: Permission[]

  beforeEach(() => {
    currentTreePermissions = [
      buildPermission(1, 'Dashboard', 'dashboard', 1, null, [
        buildPermission(2, 'User Manage', 'user:manage', 1, 1),
      ]),
      buildPermission(3, 'Audit API', 'audit:read', 0),
    ]

    getPermissionTreeMock.mockReset()
    listPermissionsMock.mockReset()
    deletePermissionMock.mockReset()
    updatePermissionStatusMock.mockReset()
    messageSuccessMock.mockReset()
    messageErrorMock.mockReset()

    getPermissionTreeMock.mockImplementation(async () => currentTreePermissions.map((permission) => ({ ...permission })))
    listPermissionsMock.mockImplementation(async () => flattenPermissions(currentTreePermissions))
    deletePermissionMock.mockImplementation(async (id: number) => {
      currentTreePermissions = deletePermissionFromTree(currentTreePermissions, id)
    })
    updatePermissionStatusMock.mockImplementation(async (id: number, status: PermissionStatus) => {
      currentTreePermissions = togglePermissionStatus(currentTreePermissions, id, status)
    })
  })

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

  it('loads tree/list views and handles create/edit/create-child/refresh/toggle/delete flows', async () => {
    const user = userEvent.setup()

    render(<PermissionsPage />)

    expect(await screen.findByText('Dashboard')).toBeInTheDocument()
    expect(screen.getByText('User Manage')).toBeInTheDocument()
    expect(getPermissionTreeMock).toHaveBeenCalledTimes(1)
    expect(listPermissionsMock).toHaveBeenCalledTimes(1)

    const initialFetchCount = getPermissionTreeMock.mock.calls.length

    await user.click(screen.getByRole('button', { name: '创建权限' }))
    expect(screen.getByTestId('permission-form-modal')).toHaveTextContent('permission-form:create:root')

    await user.click(screen.getByRole('button', { name: 'permission form success' }))
    await waitFor(() => expect(getPermissionTreeMock.mock.calls.length).toBeGreaterThan(initialFetchCount))
    await waitFor(() => expect(screen.queryByTestId('permission-form-modal')).not.toBeInTheDocument())

    const dashboardNode = screen.getByTestId('tree-node-1')
    await user.click(within(dashboardNode).getAllByRole('button', { name: '添加子权限' })[0])
    expect(screen.getByTestId('permission-form-modal')).toHaveTextContent('permission-form:create:dashboard')

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

    const childNode = screen.getByTestId('tree-node-2')
    await user.click(within(childNode).getByRole('button', { name: '编辑' }))
    expect(screen.getByTestId('permission-form-modal')).toHaveTextContent('permission-form:user:manage:root')

    await user.click(screen.getByRole('button', { name: 'permission form success' }))
    await waitFor(() => expect(screen.queryByTestId('permission-form-modal')).not.toBeInTheDocument())

    await user.click(within(childNode).getByRole('button', { name: '禁用' }))
    await waitFor(() => expect(updatePermissionStatusMock).toHaveBeenCalledWith(2, 0))
    expect(messageSuccessMock).toHaveBeenCalledWith('状态已更新')
    await waitFor(() => expect(within(screen.getByTestId('tree-node-2')).getByRole('button', { name: '启用' })).toBeInTheDocument())

    const refreshCallCount = getPermissionTreeMock.mock.calls.length
    await user.click(screen.getByRole('button', { name: '刷新' }))
    await waitFor(() => expect(getPermissionTreeMock.mock.calls.length).toBeGreaterThan(refreshCallCount))

    await user.click(screen.getByRole('button', { name: '列表' }))
    expect(await screen.findByTestId('table-row-3')).toBeInTheDocument()

    await user.click(within(screen.getByTestId('table-row-3')).getByRole('button', { name: '删除' }))
    await waitFor(() => expect(deletePermissionMock).toHaveBeenCalledWith(3))
    expect(messageSuccessMock).toHaveBeenCalledWith('权限已删除')
    await waitFor(() => expect(screen.queryByTestId('table-row-3')).not.toBeInTheDocument())
  })

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

    getPermissionTreeMock.mockReset()
    listPermissionsMock.mockReset()
    getPermissionTreeMock.mockRejectedValueOnce(new Error('permissions failed'))
    listPermissionsMock.mockResolvedValue([])
    getPermissionTreeMock.mockResolvedValue(currentTreePermissions)
    listPermissionsMock.mockResolvedValue(flattenPermissions(currentTreePermissions))

    render(<PermissionsPage />)

    expect(await screen.findByText('permissions failed')).toBeInTheDocument()

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

    await waitFor(() => expect(screen.getByText('Dashboard')).toBeInTheDocument())
    expect(getPermissionTreeMock).toHaveBeenCalledTimes(2)
  })
})
