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

import type { Permission } from '@/types/permission'
import type { Role } from '@/types/role'
import { RolePermissionsModal } from './RolePermissionsModal'

const getRolePermissionsMock = vi.fn()
const assignRolePermissionsMock = vi.fn()
const getPermissionTreeMock = vi.fn()
const messageSuccessMock = vi.fn()
const messageErrorMock = vi.fn()

vi.mock('antd', () => ({
  Empty: ({ description }: { description?: ReactNode }) => <div>{description ?? 'empty'}</div>,
  Modal: ({
    open,
    title,
    onOk,
    onCancel,
    children,
  }: {
    open: boolean
    title: ReactNode
    onOk?: () => void
    onCancel?: () => void
    children?: ReactNode
  }) => (
    open ? (
      <div data-testid="role-permissions-modal-root">
        <h1>{title}</h1>
        {children}
        <button type="button" onClick={onOk}>modal ok</button>
        <button type="button" onClick={onCancel}>modal cancel</button>
      </div>
    ) : null
  ),
  Spin: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
  Tree: ({
    checkedKeys,
    onCheck,
    treeData = [],
  }: {
    checkedKeys?: string[]
    onCheck?: (keys: string[]) => void
    treeData?: Array<{ key: string, title: ReactNode, children?: unknown[] }>
  }) => (
    <div>
      <div>{`checked:${(checkedKeys ?? []).join(',')}`}</div>
      {treeData.map((node) => (
        <div key={node.key}>{node.title}</div>
      ))}
      <button type="button" onClick={() => onCheck?.(['1', '2'])}>check permissions</button>
    </div>
  ),
  message: {
    success: (content: string) => messageSuccessMock(content),
    error: (content: string) => messageErrorMock(content),
  },
}))

vi.mock('@/services/roles', () => ({
  getRolePermissions: (id: number) => getRolePermissionsMock(id),
  assignRolePermissions: (id: number, permissionIds: number[]) => assignRolePermissionsMock(id, permissionIds),
}))

vi.mock('@/services/permissions', () => ({
  getPermissionTree: () => getPermissionTreeMock(),
}))

function buildRole(): Role {
  return {
    id: 9,
    name: 'Editor',
    code: 'editor',
    description: 'editor role',
    is_system: false,
    is_default: false,
    status: 1,
  }
}

function buildPermission(id: number, name: string, children?: Permission[]): Permission {
  return {
    id,
    name,
    code: `${name.toLowerCase()}_${id}`,
    type: 'menu',
    parent_id: null,
    sort: id,
    status: 1,
    children,
  }
}

describe('RolePermissionsModal', () => {
  beforeEach(() => {
    getRolePermissionsMock.mockReset()
    assignRolePermissionsMock.mockReset()
    getPermissionTreeMock.mockReset()
    messageSuccessMock.mockReset()
    messageErrorMock.mockReset()
  })

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

  it('loads permission data, tracks checked keys, submits assignments, and closes', async () => {
    const user = userEvent.setup()
    const role = buildRole()
    const onSuccess = vi.fn()
    const onClose = vi.fn()

    getPermissionTreeMock.mockResolvedValue([
      buildPermission(1, 'Dashboard'),
      buildPermission(2, 'Users'),
    ])
    getRolePermissionsMock.mockResolvedValue([2])
    assignRolePermissionsMock.mockResolvedValue(undefined)

    render(
      <RolePermissionsModal
        open
        role={role}
        onSuccess={onSuccess}
        onClose={onClose}
      />,
    )

    expect(await screen.findByText('Dashboard')).toBeInTheDocument()
    expect(screen.getByText('Users')).toBeInTheDocument()
    expect(screen.getByText('checked:2')).toBeInTheDocument()

    await user.click(screen.getByRole('button', { name: 'check permissions' }))
    expect(screen.getByText('checked:1,2')).toBeInTheDocument()

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

    await waitFor(() => expect(assignRolePermissionsMock).toHaveBeenCalledWith(9, [1, 2]))
    expect(messageSuccessMock).toHaveBeenCalledWith('权限分配成功')
    expect(onSuccess).toHaveBeenCalledTimes(1)

    await user.click(screen.getByRole('button', { name: 'modal cancel' }))
    expect(onClose).toHaveBeenCalledTimes(1)
  })

  it('surfaces loading failures and renders the empty state', async () => {
    const role = buildRole()

    getPermissionTreeMock.mockRejectedValueOnce(new Error('load permissions failed'))
    getRolePermissionsMock.mockResolvedValueOnce([])

    const { rerender } = render(
      <RolePermissionsModal
        open
        role={role}
        onSuccess={vi.fn()}
        onClose={vi.fn()}
      />,
    )

    await waitFor(() => expect(messageErrorMock).toHaveBeenCalledWith('load permissions failed'))

    getPermissionTreeMock.mockResolvedValueOnce([])
    getRolePermissionsMock.mockResolvedValueOnce([])

    rerender(
      <RolePermissionsModal
        open
        role={{ ...role, id: 10, code: 'auditor', name: 'Auditor' }}
        onSuccess={vi.fn()}
        onClose={vi.fn()}
      />,
    )

    expect(await screen.findByText('暂无权限数据')).toBeInTheDocument()
  })

  it('surfaces assignment failures', async () => {
    const user = userEvent.setup()

    getPermissionTreeMock.mockResolvedValue([buildPermission(1, 'Dashboard')])
    getRolePermissionsMock.mockResolvedValue([])
    assignRolePermissionsMock.mockRejectedValueOnce(new Error('assign failed'))

    render(
      <RolePermissionsModal
        open
        role={buildRole()}
        onSuccess={vi.fn()}
        onClose={vi.fn()}
      />,
    )

    expect(await screen.findByText('Dashboard')).toBeInTheDocument()

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

    await waitFor(() => expect(messageErrorMock).toHaveBeenCalledWith('assign failed'))
  })
})
