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

import MaterialNewPage from '@/pages/materials/MaterialNewPage'
import { renderWithProviders } from '@/test/render'

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

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

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

const LocationView = () => {
  const location = useLocation ()
  return <div>{location.pathname + location.search}</div>
}

describe ('MaterialNewPage', () => {
  beforeEach (() => {
    vi.clearAllMocks ()
    api.apiGet.mockResolvedValue ([])
    api.isApiError.mockReturnValue (false)
  })

  it ('initializes tag from query and submits form data', async () => {
    api.apiPost.mockResolvedValueOnce ({})

    renderWithProviders (<MaterialNewPage/>, { route: '/materials/new?tag=%E8%99%B9%E5%A4%8F' })

    expect (screen.getAllByRole ('textbox')[0]).toHaveValue ('虹夏')
    fireEvent.change (screen.getAllByRole ('textbox')[1], {
      target: { value: 'https://example.com/ref' },
    })
    fireEvent.click (screen.getByRole ('button', { name: '追加' }))

    await waitFor (() => {
      expect (api.apiPost).toHaveBeenCalledWith ('/materials', expect.any (FormData))
    })
    const formData = api.apiPost.mock.calls[0]?.[1] as FormData
    expect (formData.get ('tag')).toBe ('虹夏')
    expect (formData.get ('url')).toBe ('https://example.com/ref')
    expect (toastApi.toast).toHaveBeenCalledWith ({ title: '送信成功!' })
  })

  it ('shows validation errors for file and url fields', async () => {
    api.isApiError.mockReturnValue (true)
    api.apiPost.mockRejectedValueOnce ({
      response: {
        status: 422,
        data:   {
          type:        'validation_error',
          message:     '入力内容を確認してください．',
          errors:      {
            file: ['ファイルまたは URL は必須です．'],
            url:  ['ファイルまたは URL は必須です．'],
          },
          base_errors: [],
        },
      },
    })

    renderWithProviders (<MaterialNewPage/>)

    fireEvent.change (screen.getAllByRole ('textbox')[0], { target: { value: '虹夏' } })
    fireEvent.click (screen.getByRole ('button', { name: '追加' }))

    expect (await screen.findAllByText ('ファイルまたは URL は必須です．')).toHaveLength (2)
    expect (screen.getAllByRole ('textbox')[1]).toHaveAttribute ('aria-invalid', 'true')
  })

  it ('returns only to safe material URLs after successful creation', async () => {
    api.apiPost.mockResolvedValueOnce ({})

    renderWithProviders (
        <Routes>
          <Route path="/materials/new" element={<MaterialNewPage/>}/>
          <Route path="/materials" element={<LocationView/>}/>
        </Routes>,
        {
          route:
              '/materials/new?tag=%E8%99%B9%E5%A4%8F'
              + '&return_to=%2Fmaterials%3Ftag_id%3D20',
        },
    )

    fireEvent.change (screen.getAllByRole ('textbox')[1], {
      target: { value: 'https://example.com/ref' },
    })
    fireEvent.click (screen.getByRole ('button', { name: '追加' }))

    expect (await screen.findByText ('/materials?tag_id=20')).toBeInTheDocument ()
  })

  it ('ignores unsafe return_to values after successful creation', async () => {
    api.apiPost.mockResolvedValueOnce ({})

    renderWithProviders (
        <Routes>
          <Route path="/materials/new" element={<MaterialNewPage/>}/>
          <Route path="/materials" element={<LocationView/>}/>
        </Routes>,
        {
          route:
              '/materials/new?tag=%E8%99%B9%E5%A4%8F'
              + '&return_to=https%3A%2F%2Fexample.com%2Fevil',
        },
    )

    fireEvent.change (screen.getAllByRole ('textbox')[1], {
      target: { value: 'https://example.com/ref' },
    })
    fireEvent.click (screen.getByRole ('button', { name: '追加' }))

    expect (await screen.findByText ('/materials')).toBeInTheDocument ()
  })
})
