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

import type { Role } from '@/types/auth'
import type { User } from '@/types/user'
import { UserDetailDrawer } from './UserDetailDrawer'

const getUserMock = vi.fn()
const getUserRolesMock = vi.fn()
const consoleErrorMock = vi.fn()

vi.mock('antd', () => {
  const Descriptions = ({
    children,
  }: {
    children?: ReactNode
  }) => <div>{children}</div>

  return {
    Avatar: ({
      src,
      children,
    }: {
      src?: string
      children?: ReactNode
    }) => <div>{src ? `avatar:${src}` : 'avatar:none'}{children}</div>,
    Descriptions: Object.assign(Descriptions, {
      Item: ({
        label,
        children,
      }: {
        label?: ReactNode
        children?: ReactNode
      }) => (
        <div>
          <span>{label}</span>
          <span>{children}</span>
        </div>
      ),
    }),
    Drawer: ({
      open,
      title,
      onClose,
      children,
    }: {
      open: boolean
      title?: ReactNode
      onClose?: () => void
      children?: ReactNode
    }) => (
      open ? (
        <div data-testid="user-detail-drawer-root">
          <h1>{title}</h1>
          {children}
          <button type="button" onClick={onClose}>drawer close</button>
        </div>
      ) : null
    ),
    Spin: () => <div>loading</div>,
    Tag: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
    Typography: {
      Title: ({ children }: { children?: ReactNode }) => <h2>{children}</h2>,
    },
  }
})

vi.mock('@ant-design/icons', () => ({
  UserOutlined: () => <span>user-icon</span>,
}))

vi.mock('@/services/users', () => ({
  getUser: (userId: number) => getUserMock(userId),
  getUserRoles: (userId: number) => getUserRolesMock(userId),
}))

function deferred<T>() {
  let resolve!: (value: T) => void

  const promise = new Promise<T>((res) => {
    resolve = res
  })

  return { promise, resolve }
}

function buildUser(overrides?: Partial<User>): User {
  return {
    id: 9,
    username: 'bob',
    email: 'bob@example.com',
    phone: '13800000009',
    nickname: 'Bob',
    avatar: '',
    gender: 1,
    birthday: '2026-03-10',
    region: 'Shanghai',
    bio: 'hello',
    status: 1,
    last_login_at: '2026-03-28 09:30:00',
    last_login_ip: '127.0.0.9',
    created_at: '2026-03-01 08:00:00',
    updated_at: '2026-03-28 08:30:00',
    ...overrides,
  }
}

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

describe('UserDetailDrawer', () => {
  beforeEach(() => {
    getUserMock.mockReset()
    getUserRolesMock.mockReset()
    consoleErrorMock.mockReset()
    vi.spyOn(console, 'error').mockImplementation((...args: unknown[]) => {
      consoleErrorMock(...args)
    })
  })

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

  it('shows loading, renders fetched details, and closes the drawer', async () => {
    const user = userEvent.setup()
    const onClose = vi.fn()
    const userRequest = deferred<User>()
    const rolesRequest = deferred<Role[]>()

    getUserMock.mockReturnValue(userRequest.promise)
    getUserRolesMock.mockReturnValue(rolesRequest.promise)

    render(
      <UserDetailDrawer
        open
        userId={9}
        onClose={onClose}
      />,
    )

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

    userRequest.resolve(buildUser())
    rolesRequest.resolve(buildRoles())

    expect(await screen.findByText('最后登录时间')).toBeInTheDocument()
    expect(screen.getByText('Admin')).toBeInTheDocument()
    expect(screen.getByText('Auditor')).toBeInTheDocument()
    expect(screen.getByText('avatar:none')).toBeInTheDocument()
    expect(screen.getByText('bob@example.com')).toBeInTheDocument()
    expect(screen.getByText('13800000009')).toBeInTheDocument()
    expect(screen.getByText('2026-03-28 09:30:00')).toBeInTheDocument()

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

  it('renders the empty fallback when no user id is provided', () => {
    render(
      <UserDetailDrawer
        open
        onClose={vi.fn()}
      />,
    )

    expect(getUserMock).not.toHaveBeenCalled()
    expect(getUserRolesMock).not.toHaveBeenCalled()
    expect(screen.getByText('用户信息不存在')).toBeInTheDocument()
  })

  it('falls back after request failures', async () => {
    getUserMock.mockRejectedValueOnce(new Error('load detail failed'))
    getUserRolesMock.mockResolvedValueOnce([])

    render(
      <UserDetailDrawer
        open
        userId={10}
        onClose={vi.fn()}
      />,
    )

    expect(await screen.findByText('用户信息不存在')).toBeInTheDocument()
  })

  it('renders placeholders for missing optional fields and empty roles', async () => {
    getUserMock.mockResolvedValueOnce(buildUser({
      nickname: '',
      email: '',
      phone: '',
      avatar: 'https://cdn.example.com/avatar.png',
      gender: 0,
      birthday: '',
      region: '',
      bio: '',
      last_login_at: '',
      last_login_ip: '',
    }))
    getUserRolesMock.mockResolvedValueOnce([])

    render(
      <UserDetailDrawer
        open
        userId={11}
        onClose={vi.fn()}
      />,
    )

    expect(await screen.findByText('avatar:https://cdn.example.com/avatar.png')).toBeInTheDocument()
    expect(screen.getByText('avatar:https://cdn.example.com/avatar.png')).toBeInTheDocument()
    expect(screen.getAllByText('-').length).toBeGreaterThan(0)
    expect(screen.getByText('未知')).toBeInTheDocument()
  })
})
