import { fireEvent, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import SettingPage from '@/pages/users/SettingPage'
import { buildUser } from '@/test/factories'
import { renderWithProviders } from '@/test/render'

const api = vi.hoisted (() => ({
  apiGet:     vi.fn (),
  apiPut:     vi.fn (),
  isApiError: vi.fn (),
}))

const toastApi = vi.hoisted (() => ({
  toast: vi.fn (),
}))

vi.mock ('@/lib/api', () => api)
vi.mock ('@/components/ui/use-toast', () => toastApi)
vi.mock ('@/components/users/UserCodeDialogue', () => ({
  default: () => null,
}))
vi.mock ('@/components/users/InheritDialogue', () => ({
  default: () => null,
}))

describe ('SettingPage', () => {
  beforeEach (() => {
    vi.clearAllMocks ()
    api.isApiError.mockReturnValue (false)
    api.apiGet.mockImplementation ((path: string) => {
      if (path === '/users/settings')
	return Promise.resolve ({
	    theme: 'system',
	    auto_fetch_title: 'manual',
	    auto_fetch_thumbnail: 'manual',
	    wiki_editor_mode: 'split' })
      if (path === '/users/theme_slots')
	return Promise.resolve ([])
      return Promise.resolve ({})
    })
  })

  it ('shows loading when user is absent', () => {
    renderWithProviders (<SettingPage user={null} setUser={vi.fn ()}/>)

    expect (screen.getAllByText ('Loading...').length).toBeGreaterThan (0)
  })

  it ('updates the current user name', async () => {
    const user = buildUser ({ id: 11, name: 'old' })
    const setUser = vi.fn ()
    api.apiPut.mockResolvedValueOnce ({ ...user, name: 'new' })

    renderWithProviders (<SettingPage user={user} setUser={setUser}/>)

    const nameInput = (await screen.findAllByRole ('textbox'))[0]
    fireEvent.change (nameInput, { target: { value: 'new' } })
    fireEvent.click (screen.getAllByRole ('button', { name: '表示名を更新' })[0])

    await waitFor (() => {
      expect (api.apiPut).toHaveBeenCalledWith (
          '/users/11',
          expect.any (FormData),
          { headers: { 'Content-Type': 'multipart/form-data' } },
      )
    })
    const formData = api.apiPut.mock.calls[0]?.[1] as FormData
    expect (formData.get ('name')).toBe ('new')
    expect (setUser).toHaveBeenCalled ()
    expect (toastApi.toast).toHaveBeenCalledWith ({ title: '表示名を更新しました．' })
  })

  it ('shows validation errors returned for the name field', async () => {
    const user = buildUser ({ id: 11, name: 'old' })
    api.isApiError.mockReturnValue (true)
    api.apiPut.mockRejectedValueOnce ({
      response: {
        status: 422,
        data:   {
          type:        'validation_error',
          message:     '入力内容を確認してください．',
          errors:      { name: ['名前は必須です．'] },
          base_errors: [],
        },
      },
    })

    renderWithProviders (<SettingPage user={user} setUser={vi.fn ()}/>)

    const nameInput = (await screen.findAllByRole ('textbox'))[0]
    fireEvent.change (nameInput, { target: { value: '' } })
    fireEvent.click (screen.getAllByRole ('button', { name: '表示名を更新' })[0])

    expect ((await screen.findAllByText ('名前は必須です．')).length).toBeGreaterThan (0)
    expect (nameInput).toHaveAttribute ('aria-invalid', 'true')
  })
})
