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

import PostImportSourcePage from '@/pages/posts/PostImportSourcePage'
import { buildUser } from '@/test/factories'
import { buildPostImportRow } from '@/test/postImportFactories'
import { renderWithProviders } from '@/test/render'

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

const router = vi.hoisted (() => ({ navigate: vi.fn () }))

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

vi.mock ('@/lib/api', () => api)
vi.mock ('@/components/ui/use-toast', () => toastApi)
vi.mock ('react-router-dom', async importOriginal => ({
  ...await importOriginal<typeof import('react-router-dom')> (),
  useNavigate: () => router.navigate }))

describe ('PostImportSourcePage', () => {
  beforeEach (() => {
    sessionStorage.clear ()
    vi.clearAllMocks ()
    api.isApiError.mockReturnValue (false)
  })

  it ('shows no empty error initially and validates only after Next is pressed', () => {
    renderWithProviders (<PostImportSourcePage user={buildUser ()}/>)

    expect (screen.getByRole ('heading', { name: '広場に投稿を追加' })).toBeInTheDocument ()
    expect (screen.queryByText ('投稿インポート')).not.toBeInTheDocument ()
    expect (screen.queryByText ('URL を入力してください．')).not.toBeInTheDocument ()
    fireEvent.click (screen.getByRole ('button', { name: '次へ' }))
    expect (screen.getByText ('URL を入力してください．')).toBeInTheDocument ()
    expect (api.apiPost).not.toHaveBeenCalled ()
  })

  it ('shows frontend URL issues with line numbers without requesting preview', () => {
    renderWithProviders (<PostImportSourcePage user={buildUser ()}/>)
    const input = screen.getByRole ('textbox', { name: '' })

    fireEvent.change (input, {
      target: { value: '\nftp://example.com/file\nhttps://example.com/valid' } })
    expect (screen.queryByText (/2 行目/)).not.toBeInTheDocument ()
    fireEvent.click (screen.getByRole ('button', { name: '次へ' }))

    expect (screen.getByText (/2 行目: HTTP または HTTPS/)).toBeInTheDocument ()
    expect (screen.getByText ('ftp://example.com/file')).toBeInTheDocument ()
    expect (input).toHaveAttribute ('aria-invalid', 'true')
    expect (input.getAttribute ('aria-describedby')).toContain ('post-import-source-issues')
    expect (api.apiPost).not.toHaveBeenCalled ()
  })

  it ('shows backend URL errors against original input without a session', async () => {
    api.apiPost.mockResolvedValue ({
      rows: [buildPostImportRow ({
	sourceRow: 2,
	url: 'https://example.com/canonical',
	status: 'error',
	validationErrors: { url: ['URL が重複しています．'] } })] })
    renderWithProviders (<PostImportSourcePage user={buildUser ()}/>)

    fireEvent.change (screen.getByRole ('textbox', { name: '' }), {
      target: { value: '\nhttps://example.com/original' } })
    fireEvent.click (screen.getByRole ('button', { name: '次へ' }))

    expect (await screen.findByText ('2 行目: URL が重複しています．')).toBeInTheDocument ()
    expect (screen.getAllByText ('https://example.com/original')).toHaveLength (2)
    expect (router.navigate).not.toHaveBeenCalled ()
    expect (Array.from ({ length: sessionStorage.length }, (_, index) =>
	sessionStorage.key (index))).not.toContainEqual(
	    expect.stringMatching (/^post-import-session:/))
  })

  it ('navigates to /posts/new with query state after a successful preview', async () => {
    api.apiPost.mockResolvedValue ({ rows: [buildPostImportRow ()] })
    renderWithProviders (<PostImportSourcePage user={buildUser ()}/>)

    fireEvent.change (screen.getByRole ('textbox', { name: '' }), {
      target: { value: 'https://example.com/post' } })
    fireEvent.click (screen.getByRole ('button', { name: '次へ' }))

    await waitFor (() => {
      expect (router.navigate).toHaveBeenCalledWith (
          expect.stringMatching (/^\/posts\/new\?/))
    })
    expect (Array.from ({ length: sessionStorage.length }, (_, index) =>
	sessionStorage.key (index))).not.toContainEqual(
	    expect.stringMatching (/^post-import-session:/))
  })
})
