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 { Role, User } from '@/types'
import { AssignRolesModal } from './AssignRolesModal'

const assignUserRolesMock = vi.fn()
const messageSuccessMock = vi.fn()
const messageErrorMock = vi.fn()

vi.mock('antd', () => ({
  Modal: ({
    open,
    title,
    onOk,
    onCancel,
    children,
    confirmLoading,
  }: {
    open: boolean
    title?: ReactNode
    onOk?: () => void
    onCancel?: () => void
    children?: ReactNode
    confirmLoading?: boolean
  }) => (
    open ? (
      <div data-testid="assign-roles-modal-root">
        <h1>{title}</h1>
        <div>{`loading:${String(confirmLoading ?? false)}`}</div>
        {children}
        <button type="button" onClick={onOk}>modal ok</button>
        <button type="button" onClick={onCancel}>modal cancel</button>
      </div>
    ) : null
  ),
  Transfer: ({
    dataSource = [],
    targetKeys = [],
    titles,
    onChange,
    filterOption,
    render,
  }: {
    dataSource?: Array<{
      key: string
      title: string
      description: string
      disabled?: boolean
    }>
    targetKeys?: string[]
    titles?: ReactNode[]
    onChange?: (keys: string[]) => void
    filterOption?: (input: string, item: { title: string, description: string }) => boolean
    render?: (item: { title: string }) => ReactNode
  }) => (
    <div>
      <div>{`titles:${(titles ?? []).join('|')}`}</div>
      <div>{`target:${targetKeys.join(',')}`}</div>
      <div>{`filter-title:${String(filterOption?.('admin', { title: 'Admin', description: 'system role' }))}`}</div>
      <div>{`filter-desc:${String(filterOption?.('audit', { title: 'User', description: 'audit role' }))}`}</div>
      {dataSource.map((item) => (
        <div key={item.key}>{`${render?.(item) ?? item.title}:${String(item.disabled ?? false)}`}</div>
      ))}
      <button type="button" onClick={() => onChange?.(['2'])}>transfer change</button>
    </div>
  ),
  message: {
    success: (content: string) => messageSuccessMock(content),
    error: (content: string) => messageErrorMock(content),
  },
}))

vi.mock('@/services/users', () => ({
  assignUserRoles: (userId: number, payload: unknown) => assignUserRolesMock(userId, payload),
}))

function buildUser(): User {
  return {
    id: 42,
    username: 'editor-user',
    email: 'editor@example.com',
    phone: '13800000042',
    nickname: 'Editor',
    avatar: '',
    gender: 0,
    birthday: '',
    region: '',
    bio: '',
    status: 1,
    last_login_at: '2026-03-28 08:00:00',
    last_login_ip: '127.0.0.1',
    created_at: '2026-03-20 08:00:00',
    updated_at: '2026-03-28 08:00:00',
  }
}

function buildRoles(): Role[] {
  return [
    {
      id: 1,
      name: 'Admin',
      code: 'admin',
      description: 'system administrator',
      is_system: true,
      is_default: false,
      status: 1,
    },
    {
      id: 2,
      name: 'Auditor',
      code: 'auditor',
      description: 'audit role',
      is_system: false,
      is_default: false,
      status: 1,
    },
  ]
}

describe('AssignRolesModal', () => {
  beforeEach(() => {
    assignUserRolesMock.mockReset()
    messageSuccessMock.mockReset()
    messageErrorMock.mockReset()
  })

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

  it('syncs current roles, reacts to changes, and submits assignments', async () => {
    const user = userEvent.setup()
    const onSuccess = vi.fn()
    const allRoles = buildRoles()

    assignUserRolesMock.mockResolvedValue(undefined)

    const { rerender } = render(
      <AssignRolesModal
        open
        user={buildUser()}
        currentRoles={[allRoles[0]]}
        allRoles={allRoles}
        onSuccess={onSuccess}
        onClose={vi.fn()}
      />,
    )

    expect(screen.getByText('target:1')).toBeInTheDocument()
    expect(screen.getByText('Admin:true')).toBeInTheDocument()
    expect(screen.getByText('Auditor:false')).toBeInTheDocument()
    expect(screen.getByText('filter-title:true')).toBeInTheDocument()
    expect(screen.getByText('filter-desc:true')).toBeInTheDocument()

    rerender(
      <AssignRolesModal
        open
        user={buildUser()}
        currentRoles={[allRoles[1]]}
        allRoles={allRoles}
        onSuccess={onSuccess}
        onClose={vi.fn()}
      />,
    )

    await waitFor(() => expect(screen.getByText('target:2')).toBeInTheDocument())

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

    await waitFor(() => expect(assignUserRolesMock).toHaveBeenCalledWith(42, {
      role_ids: [2],
    }))
    expect(messageSuccessMock).toHaveBeenCalledTimes(1)
    expect(onSuccess).toHaveBeenCalledTimes(1)
  })

  it('resets state on cancel and returns early when there is no user', async () => {
    const user = userEvent.setup()
    const onClose = vi.fn()
    const allRoles = buildRoles()

    const { rerender } = render(
      <AssignRolesModal
        open
        user={buildUser()}
        currentRoles={[allRoles[0]]}
        allRoles={allRoles}
        onSuccess={vi.fn()}
        onClose={onClose}
      />,
    )

    await user.click(screen.getByRole('button', { name: 'transfer change' }))
    expect(screen.getByText('target:2')).toBeInTheDocument()

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

    expect(screen.getByText('target:1')).toBeInTheDocument()
    expect(onClose).toHaveBeenCalledTimes(1)

    rerender(
      <AssignRolesModal
        open
        user={null}
        currentRoles={[allRoles[0]]}
        allRoles={allRoles}
        onSuccess={vi.fn()}
        onClose={vi.fn()}
      />,
    )

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

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

    assignUserRolesMock.mockRejectedValueOnce(new Error('assign failed'))

    render(
      <AssignRolesModal
        open
        user={buildUser()}
        currentRoles={[allRoles[0]]}
        allRoles={allRoles}
        onSuccess={vi.fn()}
        onClose={vi.fn()}
      />,
    )

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

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