import { MemoryRouter } from 'react-router-dom'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { message } from 'antd'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import type { AuthCapabilities, RegisterResponse } from '@/types'
import { RegisterPage } from './RegisterPage'

const TEXT = {
  usernamePlaceholder: '用户名',
  nicknamePlaceholder: '昵称（选填）',
  emailPlaceholder: '邮箱地址（选填）',
  phonePlaceholder: '手机号（选填）',
  passwordPlaceholder: '密码',
  confirmPasswordPlaceholder: '确认密码',
  createAccount: '创建账号',
  returnLogin: '返回登录',
  forgotPassword: '忘记密码？',
  sendCode: '获取验证码',
  resendActivation: '重新发送激活邮件',
  adminBootstrapTitle: '当前仍需管理员初始化',
  adminBootstrapAction: '初始化管理员',
  phoneCodeValidation: '请输入 6 位短信验证码',
  activeSummary: '账号已创建，现在可以返回登录页使用新账号登录。',
  inactiveSummaryNoEmail: '账号已创建，请按页面提示完成激活后再登录。',
  smsDisabledDescription: '支持用户名和邮箱注册；当前环境未启用短信能力，因此手机号注册暂不可用。',
}

const getAuthCapabilitiesMock = vi.fn<() => Promise<AuthCapabilities>>()
const registerMock = vi.fn<(payload: unknown) => Promise<RegisterResponse>>()
const sendSmsCodeMock = vi.fn<(payload: unknown) => Promise<void>>()

const defaultCapabilities: AuthCapabilities = {
  password: true,
  email_activation: false,
  email_code: false,
  sms_code: false,
  password_reset: false,
  admin_bootstrap_required: false,
  oauth_providers: [],
}

const activeRegisterResponse: RegisterResponse = {
  id: 2,
  username: 'new-user',
  email: 'new-user@example.com',
  phone: '',
  nickname: 'New User',
  avatar: '',
  status: 1,
}

vi.mock('@/services/auth', () => ({
  getAuthCapabilities: () => getAuthCapabilitiesMock(),
  register: (payload: unknown) => registerMock(payload),
  sendSmsCode: (payload: unknown) => sendSmsCodeMock(payload),
}))

function renderRegisterPage() {
  return render(
    <MemoryRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }} initialEntries={['/register']}>
      <RegisterPage />
    </MemoryRouter>,
  )
}

function fillBaseRegistrationFields(values?: {
  username?: string
  nickname?: string
  email?: string
  password?: string
  confirmPassword?: string
}) {
  fireEvent.change(screen.getByPlaceholderText(TEXT.usernamePlaceholder), {
    target: { value: values?.username ?? 'new-user' },
  })

  if (values?.nickname !== undefined || screen.queryByPlaceholderText(TEXT.nicknamePlaceholder)) {
    fireEvent.change(screen.getByPlaceholderText(TEXT.nicknamePlaceholder), {
      target: { value: values?.nickname ?? 'New User' },
    })
  }

  if (values?.email !== undefined || screen.queryByPlaceholderText(TEXT.emailPlaceholder)) {
    fireEvent.change(screen.getByPlaceholderText(TEXT.emailPlaceholder), {
      target: { value: values?.email ?? 'new-user@example.com' },
    })
  }

  fireEvent.change(screen.getByPlaceholderText(TEXT.passwordPlaceholder), {
    target: { value: values?.password ?? 'SecurePass123!' },
  })
  fireEvent.change(screen.getByPlaceholderText(TEXT.confirmPasswordPlaceholder), {
    target: { value: values?.confirmPassword ?? values?.password ?? 'SecurePass123!' },
  })
}

describe('RegisterPage', () => {
  beforeEach(() => {
    getAuthCapabilitiesMock.mockReset()
    registerMock.mockReset()
    sendSmsCodeMock.mockReset()

    getAuthCapabilitiesMock.mockResolvedValue(defaultCapabilities)
    registerMock.mockResolvedValue(activeRegisterResponse)
    sendSmsCodeMock.mockResolvedValue(undefined)

    vi.spyOn(message, 'success').mockImplementation(() => undefined as never)
    vi.spyOn(message, 'error').mockImplementation(() => undefined as never)
  })

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

  it('renders username registration fields and hides phone registration when sms is disabled', async () => {
    renderRegisterPage()

    await waitFor(() => expect(getAuthCapabilitiesMock).toHaveBeenCalledTimes(1))

    expect(screen.getByPlaceholderText(TEXT.usernamePlaceholder)).toBeInTheDocument()
    expect(screen.getByPlaceholderText(TEXT.nicknamePlaceholder)).toBeInTheDocument()
    expect(screen.getByPlaceholderText(TEXT.emailPlaceholder)).toBeInTheDocument()
    expect(screen.queryByPlaceholderText(TEXT.phonePlaceholder)).not.toBeInTheDocument()
    expect(screen.getByRole('link', { name: /返回登录/ })).toBeInTheDocument()
  })

  it('falls back to default capabilities when loading capabilities fails', async () => {
    getAuthCapabilitiesMock.mockRejectedValue(new Error('capabilities unavailable'))

    renderRegisterPage()

    await waitFor(() => expect(screen.getByPlaceholderText(TEXT.usernamePlaceholder)).toBeInTheDocument())

    expect(screen.getByText(TEXT.smsDisabledDescription)).toBeInTheDocument()
    expect(screen.queryByPlaceholderText(TEXT.phonePlaceholder)).not.toBeInTheDocument()
    expect(screen.queryByRole('link', { name: TEXT.forgotPassword })).not.toBeInTheDocument()
  })

  it('shows the forgot-password entry when the capability is enabled', async () => {
    getAuthCapabilitiesMock.mockResolvedValue({
      ...defaultCapabilities,
      password_reset: true,
    })

    renderRegisterPage()

    await waitFor(() => expect(screen.getByRole('link', { name: TEXT.forgotPassword })).toBeInTheDocument())
  })

  it('sends register sms codes with the normalized purpose payload and starts the countdown', async () => {
    getAuthCapabilitiesMock.mockResolvedValue({
      ...defaultCapabilities,
      sms_code: true,
    })

    renderRegisterPage()

    await waitFor(() => expect(screen.getByPlaceholderText(TEXT.phonePlaceholder)).toBeInTheDocument())

    fireEvent.change(screen.getByPlaceholderText(TEXT.phonePlaceholder), {
      target: { value: '13812345678' },
    })
    fireEvent.click(screen.getByRole('button', { name: TEXT.sendCode }))

    await waitFor(() =>
      expect(sendSmsCodeMock).toHaveBeenCalledWith({
        phone: '13812345678',
        purpose: 'register',
      }),
    )

    expect(message.success).toHaveBeenCalledTimes(1)
    expect(screen.getByRole('button', { name: '60s' })).toBeInTheDocument()
  })

  it('ignores form validation errors when the phone format is invalid', async () => {
    getAuthCapabilitiesMock.mockResolvedValue({
      ...defaultCapabilities,
      sms_code: true,
    })

    renderRegisterPage()

    await waitFor(() => expect(screen.getByPlaceholderText(TEXT.phonePlaceholder)).toBeInTheDocument())

    fireEvent.change(screen.getByPlaceholderText(TEXT.phonePlaceholder), {
      target: { value: '123' },
    })
    fireEvent.click(screen.getByRole('button', { name: TEXT.sendCode }))

    await waitFor(() => expect(sendSmsCodeMock).not.toHaveBeenCalled())

    expect(message.error).not.toHaveBeenCalled()
    expect(screen.queryByRole('button', { name: '60s' })).not.toBeInTheDocument()
  })

  it('surfaces sms code send failures from the backend', async () => {
    getAuthCapabilitiesMock.mockResolvedValue({
      ...defaultCapabilities,
      sms_code: true,
    })
    sendSmsCodeMock.mockRejectedValue(new Error('sms send failed'))

    renderRegisterPage()

    await waitFor(() => expect(screen.getByPlaceholderText(TEXT.phonePlaceholder)).toBeInTheDocument())

    fireEvent.change(screen.getByPlaceholderText(TEXT.phonePlaceholder), {
      target: { value: '13812345678' },
    })
    fireEvent.click(screen.getByRole('button', { name: TEXT.sendCode }))

    await waitFor(() => expect(message.error).toHaveBeenCalledWith('sms send failed'))

    expect(screen.queryByRole('button', { name: '60s' })).not.toBeInTheDocument()
  })

  it('blocks sms-backed registration when the phone code is missing', async () => {
    getAuthCapabilitiesMock.mockResolvedValue({
      ...defaultCapabilities,
      sms_code: true,
    })

    renderRegisterPage()

    await waitFor(() => expect(screen.getByPlaceholderText(TEXT.phonePlaceholder)).toBeInTheDocument())

    fillBaseRegistrationFields()
    fireEvent.change(screen.getByPlaceholderText(TEXT.phonePlaceholder), {
      target: { value: '13812345678' },
    })
    fireEvent.click(screen.getByRole('button', { name: TEXT.createAccount }))

    await waitFor(() => expect(registerMock).not.toHaveBeenCalled())

    expect(message.error).not.toHaveBeenCalled()
    expect(screen.getByRole('button', { name: TEXT.createAccount })).toBeInTheDocument()
  })

  it('submits the self-service registration payload and shows the success state', async () => {
    renderRegisterPage()

    await waitFor(() => expect(screen.getByPlaceholderText(TEXT.usernamePlaceholder)).toBeInTheDocument())

    fillBaseRegistrationFields()
    fireEvent.click(screen.getByRole('button', { name: TEXT.createAccount }))

    await waitFor(() =>
      expect(registerMock).toHaveBeenCalledWith({
        username: 'new-user',
        password: 'SecurePass123!',
        nickname: 'New User',
        email: 'new-user@example.com',
        phone: undefined,
        phone_code: undefined,
      }),
    )

    expect(await screen.findByRole('button', { name: TEXT.returnLogin })).toBeInTheDocument()
    expect(screen.getByText(TEXT.activeSummary)).toBeInTheDocument()
  })

  it('submits sms-backed registration payload with trimmed username, nickname, and phone code', async () => {
    getAuthCapabilitiesMock.mockResolvedValue({
      ...defaultCapabilities,
      sms_code: true,
    })

    renderRegisterPage()

    await waitFor(() => expect(screen.getByPlaceholderText(TEXT.phonePlaceholder)).toBeInTheDocument())

    fillBaseRegistrationFields({
      username: '  new-user  ',
      nickname: '  New User  ',
      email: 'new-user@example.com',
    })
    fireEvent.change(screen.getByPlaceholderText(TEXT.phonePlaceholder), {
      target: { value: '13812345678' },
    })

    fireEvent.change(screen.getAllByRole('textbox')[4], {
      target: { value: ' 654321 ' },
    })
    fireEvent.click(screen.getByRole('button', { name: TEXT.createAccount }))

    await waitFor(() =>
      expect(registerMock).toHaveBeenCalledWith({
        username: 'new-user',
        password: 'SecurePass123!',
        nickname: 'New User',
        email: 'new-user@example.com',
        phone: '13812345678',
        phone_code: '654321',
      }),
    )
  })

  it('surfaces self-service registration failures from the backend', async () => {
    registerMock.mockRejectedValue(new Error('register failed'))

    renderRegisterPage()

    await waitFor(() => expect(screen.getByPlaceholderText(TEXT.usernamePlaceholder)).toBeInTheDocument())

    fillBaseRegistrationFields()
    fireEvent.click(screen.getByRole('button', { name: TEXT.createAccount }))

    await waitFor(() => expect(message.error).toHaveBeenCalledWith('register failed'))

    expect(screen.getByRole('button', { name: TEXT.createAccount })).toBeInTheDocument()
  })

  it('shows a resend-activation entry for newly created inactive email accounts', async () => {
    getAuthCapabilitiesMock.mockResolvedValue({
      ...defaultCapabilities,
      email_activation: true,
    })
    registerMock.mockResolvedValue({
      id: 3,
      username: 'inactive-user',
      email: 'inactive-user@example.com',
      phone: '',
      nickname: 'Inactive User',
      avatar: '',
      status: 0,
    })

    renderRegisterPage()

    await waitFor(() => expect(screen.getByPlaceholderText(TEXT.usernamePlaceholder)).toBeInTheDocument())

    fillBaseRegistrationFields({
      username: 'inactive-user',
      nickname: '',
      email: 'inactive-user@example.com',
    })
    fireEvent.click(screen.getByRole('button', { name: TEXT.createAccount }))

    const resendLink = await screen.findByRole('link', { name: TEXT.resendActivation })
    expect(resendLink).toHaveAttribute('href', '/activate-account?email=inactive-user%40example.com')
  })

  it('shows the generic activation summary when the new inactive account has no email address', async () => {
    registerMock.mockResolvedValue({
      id: 4,
      username: 'inactive-without-email',
      email: '',
      phone: '',
      nickname: '',
      avatar: '',
      status: 0,
    })

    renderRegisterPage()

    await waitFor(() => expect(screen.getByPlaceholderText(TEXT.usernamePlaceholder)).toBeInTheDocument())

    fillBaseRegistrationFields({
      username: 'inactive-without-email',
      nickname: '',
      email: '',
    })
    fireEvent.click(screen.getByRole('button', { name: TEXT.createAccount }))

    expect(await screen.findByText(TEXT.inactiveSummaryNoEmail)).toBeInTheDocument()
    expect(screen.queryByRole('link', { name: TEXT.resendActivation })).not.toBeInTheDocument()
  })

  it('shows an admin bootstrap entry when the system still has no active admin', async () => {
    getAuthCapabilitiesMock.mockResolvedValue({
      ...defaultCapabilities,
      admin_bootstrap_required: true,
    })

    renderRegisterPage()

    expect(await screen.findByText(TEXT.adminBootstrapTitle)).toBeInTheDocument()
    expect(screen.getByRole('button', { name: TEXT.adminBootstrapAction }).closest('a')).toHaveAttribute('href', '/bootstrap-admin')
  })
})
