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

import PostEditForm from '@/components/PostEditForm'
import { buildPost, buildTag } from '@/test/factories'

const postsApi = vi.hoisted (() => ({
  updatePost: vi.fn (),
}))

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

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

vi.mock ('@/lib/posts', () => postsApi)
vi.mock ('@/lib/api', () => api)
vi.mock ('@/components/ui/use-toast', () => toastApi)
vi.mock ('@/lib/dialogues/useDialogue', () => ({
  default: () => ({
    choice: vi.fn (),
  }),
}))

describe ('PostEditForm', () => {
  it ('submits edited post fields with the current base version', async () => {
    const onSave = vi.fn ()
    const post = buildPost ({
      id:        8,
      versionNo: 4,
      title:     'old',
      tags:      [
        buildTag ({ name: 'general-tag', category: 'general' }),
        buildTag ({ id: 2, name: 'nico-tag', category: 'nico' }),
      ],
      parentPosts: [buildPost ({ id: 2, title: 'parent' })],
    })
    postsApi.updatePost.mockResolvedValueOnce ({
      ...post,
      versionNo: 5,
      title:     'new',
      tags:      [buildTag ({ name: 'new-tag' })],
    })

    render (<PostEditForm post={post} onSave={onSave}/>)

    const [title, parentIds] = screen.getAllByRole ('textbox')
    fireEvent.change (title, { target: { value: 'new' } })
    fireEvent.change (parentIds, { target: { value: '3 4' } })
    fireEvent.submit (screen.getByRole ('button', { name: '更新' }).closest ('form')!)

    await waitFor (() => {
      expect (postsApi.updatePost).toHaveBeenCalledWith (
          expect.objectContaining ({
            id:            8,
            title:         'new',
            parentPostIds: '3 4',
            tags:          'general-tag',
          }),
          { baseVersionNo: 4 },
      )
    })
    expect (onSave).toHaveBeenCalledWith (expect.objectContaining ({ versionNo: 5 }))
    expect (toastApi.toast).toHaveBeenCalledWith ({ description: '更新しました．' })
  })

  it ('preserves duration while the video tag is temporarily removed', () => {
    const post = buildPost ({
      videoMs: 180_500,
      tags:    [
        buildTag ({ id: 1, name: '動画', category: 'general' }),
        buildTag ({ id: 2, name: 'general-tag', category: 'general' }),
      ],
    })

    render (<PostEditForm post={post} onSave={vi.fn ()}/>)

    expect (screen.getByText ('動画時間').parentElement?.querySelector ('input'))
	.toHaveValue ('180.5')

    const tags = screen.getAllByRole ('textbox')[2]
    fireEvent.change (tags, { target: { value: 'general-tag' } })
    expect (screen.queryByText ('動画時間')).not.toBeInTheDocument ()

    fireEvent.change (tags, {
      target: { value: '動画 general-tag' },
    })
    expect (screen.getByText ('動画時間').parentElement?.querySelector ('input'))
	.toHaveValue ('180.5')
  })

  it (
      'shows deduplicated original-created endpoint errors on the shared datetime field',
      async () => {
    const post = buildPost ()
    api.isApiError.mockReturnValue (true)
    postsApi.updatePost.mockRejectedValueOnce ({
      response: {
        status: 422,
        data:   {
          type: 'validation_error',
          errors: {
            original_created_at: ['日時を確認してください．'],
            original_created_from: ['日時を確認してください．'],
            original_created_before: ['終了を確認してください．'] },
        },
      },
    })

    render (<PostEditForm post={post} onSave={vi.fn ()}/>)
    fireEvent.submit (screen.getByRole ('button', { name: '更新' }).closest ('form')!)

    expect (await screen.findByText ('日時を確認してください．')).toBeInTheDocument ()
    expect (screen.getByText ('終了を確認してください．')).toBeInTheDocument ()
    expect (screen.getAllByText ('日時を確認してください．')).toHaveLength (1)
      })
})
