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

import type { Role } from '@/types/auth'
import type { CurrentUserProfile } from '@/services/profile'
import { ProfilePage } from './ProfilePage'

const useAuthMock = vi.fn()
const getCurrentProfileMock = vi.fn()
const updateProfileMock = vi.fn()
const messageSuccessMock = vi.fn()
const messageErrorMock = vi.fn()
const formApi = {
  setFieldsValue: vi.fn(),
}

let submittedFormValues: Record<string, unknown> = {}

vi.mock('antd', () => {
  const Form = ({
    children,
    onFinish,
  }: {
    children?: ReactNode
    onFinish?: (values: Record<string, unknown>) => void
  }) => (
    <form
      onSubmit={(event) => {
        event.preventDefault()
        onFinish?.(submittedFormValues)
      }}
    >
      {children}
    </form>
  )

  return {
    Alert: ({
      message,
      description,
    }: {
      message?: ReactNode
      description?: ReactNode
    }) => (
      <div>
        <div>{message}</div>
        <div>{description}</div>
      </div>
    ),
    Button: ({
      children,
      htmlType,
      onClick,
      icon,
      type: buttonType,
      loading,
      ...props
    }: {
      children?: ReactNode
      htmlType?: 'button' | 'submit' | 'reset'
      onClick?: () => void
      [key: string]: unknown
    }) => {
      void icon
      void buttonType
      void loading

      return (
        <button type={htmlType ?? 'button'} onClick={onClick} {...props}>
          {children}
        </button>
      )
    },
    Col: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
    DatePicker: ({ placeholder }: { placeholder?: string }) => <input placeholder={placeholder} />,
    Form: Object.assign(Form, {
      useForm: () => [formApi],
      Item: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
    }),
    Input: Object.assign(({
      placeholder,
    }: {
      placeholder?: string
    }) => <input placeholder={placeholder} />, {
      TextArea: ({ placeholder }: { placeholder?: string }) => <textarea placeholder={placeholder} />,
    }),
    Row: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
    Select: ({
      options = [],
      placeholder,
    }: {
      options?: Array<{ value: number | string, label: ReactNode }>
      placeholder?: string
    }) => (
      <select aria-label={placeholder ?? 'select'}>
        {options.map((option) => (
          <option key={String(option.value)} value={String(option.value)}>
            {option.label}
          </option>
        ))}
      </select>
    ),
    Space: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
    Spin: () => <div>loading</div>,
    Tag: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
    Typography: {
      Text: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
    },
    message: {
      success: (content: string) => messageSuccessMock(content),
      error: (content: string) => messageErrorMock(content),
    },
  }
})

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

vi.mock('react-router-dom', () => ({
  Link: ({
    to,
    children,
  }: {
    to: string
    children?: ReactNode
  }) => <a href={to}>{children}</a>,
}))

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

vi.mock('@/services/profile', () => ({
  getCurrentProfile: (userId: number) => getCurrentProfileMock(userId),
  updateProfile: (userId: number, payload: unknown) => updateProfileMock(userId, payload),
}))

vi.mock('@/components/layout', () => ({
  PageLayout: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
  ContentCard: ({
    title,
    children,
  }: {
    title?: ReactNode
    children?: ReactNode
  }) => (
    <section>
      <h2>{title}</h2>
      {children}
    </section>
  ),
}))

vi.mock('@/components/common', () => ({
  PageHeader: ({
    title,
    description,
  }: {
    title: ReactNode
    description?: ReactNode
  }) => (
    <section>
      <h1>{title}</h1>
      <p>{description}</p>
    </section>
  ),
}))

function buildProfile(): CurrentUserProfile {
  const roles: Role[] = [
    {
      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: 1,
    },
  ]

  return {
    user: {
      id: 8,
      username: 'alice',
      email: 'alice@example.com',
      phone: '13800000008',
      nickname: 'Alice',
      avatar: '',
      gender: 2,
      birthday: '2026-03-01',
      region: 'Shanghai',
      bio: 'Hello world',
      status: 1,
      last_login_at: '2026-03-27 10:30:00',
      last_login_ip: '127.0.0.1',
      created_at: '2026-03-20 09:00:00',
      updated_at: '2026-03-27 09:00:00',
    },
    roles,
  }
}

describe('ProfilePage', () => {
  let refreshUserMock: ReturnType<typeof vi.fn>

  beforeEach(() => {
    refreshUserMock = vi.fn().mockResolvedValue(undefined)
    submittedFormValues = {}

    useAuthMock.mockReturnValue({
      user: {
        id: 8,
        username: 'alice',
        email: 'alice@example.com',
        phone: '13800000008',
        nickname: 'Alice',
        avatar: '',
        status: 1,
      },
      refreshUser: refreshUserMock,
      roles: [],
      isAdmin: false,
      isAuthenticated: true,
      isLoading: false,
      onLoginSuccess: vi.fn(),
      logout: vi.fn(),
    })

    getCurrentProfileMock.mockReset()
    updateProfileMock.mockReset()
    messageSuccessMock.mockReset()
    messageErrorMock.mockReset()
    formApi.setFieldsValue.mockReset()
  })

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

  it('loads profile data, hydrates the form, and submits updates successfully', async () => {
    const user = userEvent.setup()
    const profile = buildProfile()

    getCurrentProfileMock.mockResolvedValue(profile)
    updateProfileMock.mockResolvedValue(profile.user)

    submittedFormValues = {
      nickname: 'Alice Updated',
      gender: 1,
      birthday: dayjs('2026-03-15'),
      region: 'Beijing',
      bio: 'Updated bio',
    }

    render(<ProfilePage />)

    expect(await screen.findByText('alice')).toBeInTheDocument()
    expect(screen.getByText('alice@example.com')).toBeInTheDocument()
    expect(screen.getByText('Admin')).toBeInTheDocument()
    expect(screen.getByText('Auditor')).toBeInTheDocument()
    expect(formApi.setFieldsValue).toHaveBeenCalledWith(expect.objectContaining({
      nickname: 'Alice',
      gender: 2,
      region: 'Shanghai',
      bio: 'Hello world',
    }))

    await user.click(screen.getByRole('button', { name: '保存修改' }))

    await waitFor(() => expect(updateProfileMock).toHaveBeenCalledWith(8, {
      nickname: 'Alice Updated',
      gender: 1,
      birthday: '2026-03-15',
      region: 'Beijing',
      bio: 'Updated bio',
    }))
    await waitFor(() => expect(getCurrentProfileMock).toHaveBeenCalledTimes(2))
    expect(refreshUserMock).toHaveBeenCalledTimes(1)
    expect(messageSuccessMock).toHaveBeenCalledWith('资料更新成功')
  })

  it('surfaces profile fetch failures and still renders the page shell', async () => {
    getCurrentProfileMock.mockRejectedValueOnce(new Error('profile failed'))

    render(<ProfilePage />)

    await waitFor(() => expect(messageErrorMock).toHaveBeenCalledWith('profile failed'))
    expect(await screen.findByText('个人资料')).toBeInTheDocument()
    expect(screen.getByText('暂无角色')).toBeInTheDocument()
  })

  it('surfaces update failures without refreshing the user session', async () => {
    const user = userEvent.setup()
    const profile = buildProfile()

    getCurrentProfileMock.mockResolvedValue(profile)
    updateProfileMock.mockRejectedValueOnce(new Error('update failed'))

    submittedFormValues = {
      nickname: 'Broken Update',
      gender: 0,
      birthday: dayjs('2026-03-16'),
      region: 'Hangzhou',
      bio: 'Broken bio',
    }

    render(<ProfilePage />)

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

    await user.click(screen.getByRole('button', { name: '保存修改' }))

    await waitFor(() => expect(messageErrorMock).toHaveBeenCalledWith('update failed'))
    expect(refreshUserMock).not.toHaveBeenCalled()
  })
})
