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

import PostImportRowForm from '@/components/posts/import/PostImportRowForm'
import { buildPostImportRow } from '@/test/postImportFactories'

import type { DialogueFormAction, DialogueFormControls } from '@/lib/dialogues/useDialogue'

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

vi.mock ('@/lib/api', () => api)

describe ('PostImportRowForm', () => {
  beforeEach (() => {
    vi.clearAllMocks ()
    globalThis.URL.createObjectURL = vi.fn (() => 'blob:preview')
    globalThis.URL.revokeObjectURL = vi.fn ()
    api.apiGet.mockResolvedValue (new Blob (['img'], { type: 'image/png' }))
  })

  it ('resets only the draft, then saves with resetRequested', async () => {
    const row = buildPostImportRow ()
    row.attributes.title = 'manual title'
    row.provenance.title = 'manual'
    const actions: DialogueFormAction[][] = []
    const controls: DialogueFormControls = {
      close: vi.fn (),
      confirm: vi.fn ().mockResolvedValue (true),
      setActions: next => actions.push (next) }
    const invalidRow = buildPostImportRow ({
      validationErrors: { title: ['タイトルを確認してください．'] } })
    const onSave = vi.fn ().mockResolvedValue ({ saved: false, row: invalidRow })

    render (<PostImportRowForm row={row} controls={controls} onSave={onSave}/>)
    const titleInput = screen.getByDisplayValue ('manual title')

    await waitFor (() => expect (actions.at (-1)?.length).toBe (2))
    const reset = actions.at (-1)?.find (action => action.label === '変更をリセット')
    expect (reset).toMatchObject ({ placement: 'start', variant: 'danger', disabled: false })
    await act (async () => {
      await reset?.onSelect ()
    })

    expect (controls.confirm).toHaveBeenCalled ()
    expect (titleInput).toHaveValue ('')
    expect (onSave).not.toHaveBeenCalled ()

    const save = actions.at (-1)?.find (action => action.label === '編輯内容を保存')
    await act (async () => {
      await save?.onSelect ()
    })

    expect (onSave).toHaveBeenCalledWith ({
      draft: expect.objectContaining ({ title: '' }),
      resetRequested: true })
    expect (screen.getByText ('タイトルを確認してください．')).toBeInTheDocument ()
  })

  it ('does not reset the draft when confirmation is cancelled', async () => {
    const row = buildPostImportRow ({ attributes: { title: 'manual title' } })
    let actions: DialogueFormAction[] = []
    const controls: DialogueFormControls = {
      close: vi.fn (),
      confirm: vi.fn ().mockResolvedValue (false),
      setActions: next => {
	actions = next
      } }

    render (
	<PostImportRowForm
	    row={row}
	    controls={controls}
	    onSave={vi.fn ()}/>)
    await waitFor (() => expect (actions.length).toBe (2))

    await act (async () => {
      await actions.find (action => action.label === '変更をリセット')?.onSelect ()
    })

    expect (screen.getByDisplayValue ('manual title')).toBeInTheDocument ()
  })

  it ('marks edited fields and areas invalid from field errors', () => {
    const row = buildPostImportRow ({
      validationErrors: { url: ['URL error'], tags: ['tag error'] },
      importErrors: { title: ['title error'] },
      fieldWarnings: { title: ['title warning'] } })

    render (
	<PostImportRowForm
	    row={row}
	    controls={{ close: vi.fn (), confirm: vi.fn (), setActions: vi.fn () }}
	    onSave={vi.fn ()}/>)

    expect (screen.getByText ('URL error')).toBeInTheDocument ()
    expect (screen.getByText ('tag error')).toBeInTheDocument ()
    expect (screen.getByText ('title error')).toBeInTheDocument ()
    expect (screen.getByText ('title warning')).toBeInTheDocument ()
    expect (screen.getAllByRole ('textbox').filter (
	textbox => textbox.getAttribute ('aria-invalid') === 'true')).toHaveLength (3)
  })

  it ('keeps untouched original created values unchanged in the save payload', async () => {
    let actions: DialogueFormAction[] = []
    const row = buildPostImportRow ({
      attributes: {
        originalCreatedFrom: '2024-01-01T12:34+09:00',
        originalCreatedBefore: '2024-01-01T12:35+09:00' } })
    const controls: DialogueFormControls = {
      close: vi.fn (),
      confirm: vi.fn (),
      setActions: next => {
        actions = next
      } }
    const onSave = vi.fn ().mockResolvedValue ({ saved: true, row: null })

    render (
        <PostImportRowForm
            row={row}
            controls={controls}
            onSave={onSave}/>)
    await waitFor (() => expect (actions.length).toBe (2))

    await act (async () => {
      void actions.find (action => action.label === '編輯内容を保存')?.onSelect ()
    })

    expect (onSave).toHaveBeenCalledWith ({
      draft: expect.objectContaining ({
        originalCreatedFrom: '2024-01-01T12:34+09:00',
        originalCreatedBefore: '2024-01-01T12:35+09:00' }),
      resetRequested: false })
  })

  it (
      'shows upload input only when the thumbnail URL is blank',
      async () => {
    let actions: DialogueFormAction[] = []
    const controls: DialogueFormControls = {
      close: vi.fn (),
      confirm: vi.fn (),
      setActions: next => {
        actions = next
      } }
    const onSave = vi.fn ().mockResolvedValue ({ saved: true, row: null })

    const { container } = render (
        <PostImportRowForm
            row={buildPostImportRow ({
              attributes: { duration: '2', tags: 'tag1' } })}
            controls={controls}
            onSave={onSave}/>)
    await waitFor (() => expect (actions.length).toBe (2))

    const labels = Array.from (container.querySelectorAll ('label'))
        .map (node => node.textContent?.trim ())

    expect (labels.slice (0, 6)).toEqual ([
      'URL',
      'サムネール',
      'タイトル',
      'オリジナルの作成日時',
      'タグ',
      '親投稿'])
    expect (container.querySelector ('input[type="file"]')).toHaveAttribute (
	'accept',
	'image/*')
    expect (screen.queryByPlaceholderText ('例: 2 / 2.5 / 1:23')).not.toBeInTheDocument ()
    expect (screen.getByDisplayValue ('tag1')).toBeInTheDocument ()

    await act (async () => {
      await actions.find (action => action.label === '編輯内容を保存')?.onSelect ()
    })

    expect (onSave).toHaveBeenCalledWith ({
      draft: expect.objectContaining ({
        tags: 'tag1' }),
      resetRequested: false })
      })

  it ('keeps reset enabled when the value matches but provenance still differs', async () => {
    const row = buildPostImportRow ({
      attributes: { title: 'same title' },
      provenance: { title: 'manual' },
      resetSnapshot: {
        url: 'https://example.com/post',
        attributes: {
          title: 'same title',
          thumbnailBase: '',
          originalCreatedFrom: '',
          originalCreatedBefore: '',
          tags: '',
          parentPostIds: '' },
        provenance: {
          url: 'manual',
          title: 'automatic',
          thumbnailBase: 'automatic',
          originalCreatedFrom: 'automatic',
          originalCreatedBefore: 'automatic',
          tags: 'automatic',
          parentPostIds: 'automatic' },
        tagSources: { automatic: '', manual: '' },
        fieldWarnings: { },
        baseWarnings: [] } })
    let actions: DialogueFormAction[] = []

    render (
        <PostImportRowForm
            row={row}
            controls={{
              close: vi.fn (),
              confirm: vi.fn (),
              setActions: next => {
                actions = next
              } }}
            onSave={vi.fn ()}/>)
    await waitFor (() => expect (actions.length).toBe (2))

    expect (actions.find (action => action.label === '変更をリセット')?.disabled).toBe (false)
  })

  it (
      'disables every field while save validation is pending and re-enables them afterwards',
      async () => {
    let actions: DialogueFormAction[] = []
    let resolveSave:
        ((value: { saved: boolean
		   row:   ReturnType<typeof buildPostImportRow> | null }) => void) | null
        = null
    const onSave = vi.fn (() =>
        new Promise<{ saved: boolean
		      row:   ReturnType<typeof buildPostImportRow> | null }> (resolve => {
          resolveSave = resolve
        }))

    render (
        <PostImportRowForm
            row={buildPostImportRow ({ attributes: { title: 'draft title' } })}
            controls={{
              close: vi.fn (),
              confirm: vi.fn (),
              setActions: next => {
                actions = next
              } }}
            onSave={onSave}/>)
    await waitFor (() => expect (actions.length).toBe (2))

    let savePromise: Promise<boolean | void> | undefined
    await act (async () => {
      savePromise = actions.find (action => action.label === '編輯内容を保存')?.onSelect ()
    })

    await waitFor (() => {
      screen.getAllByRole ('textbox').forEach (textbox => {
        expect (textbox).toBeDisabled ()
      })
    })

    resolveSave?.({
      saved: false,
      row: buildPostImportRow ({
        attributes: { title: 'draft title' },
        validationErrors: { title: ['タイトルを確認してください．'] } }) })
    await act (async () => {
      await savePromise
    })

    await waitFor (() => {
      screen.getAllByRole ('textbox').forEach (textbox => {
        expect (textbox).not.toBeDisabled ()
      })
    })
    expect (screen.getByDisplayValue ('draft title')).toBeInTheDocument ()
    expect (screen.getByText ('タイトルを確認してください．')).toBeInTheDocument ()
      })
})
