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

import MaterialDetailPage from '@/pages/materials/MaterialDetailPage'
import { buildMaterial, buildTag } from '@/test/factories'
import { renderWithProviders } from '@/test/render'

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

const wikiApi = vi.hoisted (() => ({
  fetchWikiPages: vi.fn (),
}))

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

vi.mock ('@/lib/api', () => api)
vi.mock ('@/lib/wiki', () => wikiApi)
vi.mock ('@/components/ui/use-toast', () => toastApi)

const renderPage = () =>
  renderWithProviders (
      <Routes>
        <Route path="/materials/:id" element={<MaterialDetailPage user={null}/>}/>
      </Routes>,
      { route: '/materials/8' },
  )

describe ('MaterialDetailPage', () => {
  beforeEach (() => {
    vi.clearAllMocks ()
    api.apiGet.mockImplementation (async (path: string) =>
      path === '/tags/autocomplete'
      ? []
      : buildMaterial ({ id: 8 }))
    api.isApiError.mockReturnValue (false)
    wikiApi.fetchWikiPages.mockResolvedValue ([])
    vi.stubGlobal ('fetch', vi.fn (async () => ({
      blob: async () => new Blob (['image'], { type: 'image/png' }),
    })))
  })

  it ('loads and displays material detail', async () => {
    api.apiGet.mockResolvedValueOnce (
        buildMaterial ({
          id:          8,
          tag:         buildTag ({ name: '素材タグ' }),
          file:        'image.png',
          contentType: 'image/png',
        }),
    )

    renderPage ()

    await waitFor (() => {
      expect (api.apiGet).toHaveBeenCalledWith ('/materials/8')
    })
    expect (await screen.findByAltText ('素材タグ')).toHaveAttribute ('src', 'image.png')
  })

  it ('submits edited material fields', async () => {
    api.apiGet.mockResolvedValueOnce (
        buildMaterial ({ id: 8, tag: buildTag ({ name: 'old' }), url: '' }),
    )
    api.apiPut.mockResolvedValueOnce (
        buildMaterial ({ id: 8, tag: buildTag ({ name: 'new' }) }),
    )

    renderPage ()

    fireEvent.click (await screen.findByText ('編輯'))
    const textboxes = screen.getAllByRole ('textbox')
    fireEvent.change (textboxes[0], { target: { value: 'new' } })
    fireEvent.change (textboxes[1], { target: { value: 'https://example.com/ref' } })
    fireEvent.change (textboxes[2], { target: { value: '素材/new.png' } })
    fireEvent.click (screen.getByRole ('button', { name: '更新' }))

    await waitFor (() => {
      expect (api.apiPut).toHaveBeenCalledWith ('/materials/8', expect.any (FormData))
    })
    const formData = api.apiPut.mock.calls[0]?.[1] as FormData
    expect (formData.get ('tag')).toBe ('new')
    expect (formData.get ('url')).toBe ('https://example.com/ref')
    expect (formData.get ('export_paths[legacy_drive]')).toBe ('素材/new.png')
    expect (toastApi.toast).toHaveBeenCalledWith ({ title: '更新成功!' })
  })

  it ('shows not found when material API returns 404', async () => {
    api.isApiError.mockReturnValue (true)
    api.apiGet.mockRejectedValueOnce ({ response: { status: 404 } })

    renderPage ()

    expect (await screen.findByText ('素材が見つかりませんでした．')).toBeInTheDocument ()
  })

  it ('shows an error for non-404 material API failures', async () => {
    api.isApiError.mockReturnValue (false)
    api.apiGet.mockRejectedValueOnce (new Error ('network error'))

    renderPage ()

    expect (await screen.findByText ('素材の取得に失敗しました．')).toBeInTheDocument ()
  })
})
