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

import PostDetailPage from '@/pages/posts/PostDetailPage'
import { buildPost, buildUser } from '@/test/factories'
import { renderWithProviders } from '@/test/render'

import type { ReactNode } from 'react'

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

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

vi.mock ('@/lib/posts', () => postsApi)
vi.mock ('@/lib/api', () => api)
vi.mock ('@/components/PostEmbed', () => ({
  default: ({ post }: { post: { url: string } }) => <div>Embed:{post.url}</div>,
}))
vi.mock ('@/components/TagDetailSidebar', () => ({
  default: () => <aside>Tag sidebar</aside>,
}))
vi.mock ('@/components/PostEditForm', () => ({
  default: () => <div>Post edit form</div>,
}))
vi.mock ('framer-motion', () => ({
  motion: {
    div: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
    main: ({ children }: { children?: ReactNode }) => <main>{children}</main>,
  },
}))

const renderPage = (user = buildUser ({ role: 'member' })) =>
  renderWithProviders (
      <Routes>
        <Route path="/posts/:id" element={<PostDetailPage user={user}/>}/>
      </Routes>,
      { route: '/posts/9' },
  )

describe ('PostDetailPage', () => {
  beforeEach (() => {
    vi.clearAllMocks ()
    postsApi.toggleViewedFlg.mockResolvedValue (undefined)
  })

  it ('loads and displays a post detail', async () => {
    postsApi.fetchPost.mockResolvedValue (
        buildPost ({
          id:            9,
          url:           'https://example.com/9',
          related:       [],
          thumbnail:     null,
          thumbnailBase: null,
        }),
    )

    renderPage ()

    await waitFor (() => {
      expect (postsApi.fetchPost).toHaveBeenCalledWith ('9')
    })
    expect (await screen.findByText ('Embed:https://example.com/9')).toBeInTheDocument ()
    expect (screen.getByRole ('button', { name: '未閲覧' })).toBeInTheDocument ()
    expect (screen.getByText ('まだないよ（笑）')).toBeInTheDocument ()
  })

  it ('toggles viewed state through the mutation', async () => {
    postsApi.fetchPost.mockResolvedValue (
        buildPost ({ id: 9, viewed: false, thumbnail: null, thumbnailBase: null }),
    )

    renderPage ()

    fireEvent.click (await screen.findByRole ('button', { name: '未閲覧' }))

    await waitFor (() => {
      expect (postsApi.toggleViewedFlg).toHaveBeenCalledWith ('9', true)
    })
  })

  it ('shows the edit tab for members', async () => {
    postsApi.fetchPost.mockResolvedValue (
        buildPost ({ id: 9, thumbnail: null, thumbnailBase: null }),
    )

    renderPage (buildUser ({ role: 'member' }))

    fireEvent.click (await screen.findByText ('編輯'))

    expect (screen.getByText ('Post edit form')).toBeInTheDocument ()
  })

  it ('hides the edit tab for guests', async () => {
    postsApi.fetchPost.mockResolvedValue (
        buildPost ({ id: 9, thumbnail: null, thumbnailBase: null }),
    )

    renderPage (buildUser ({ role: 'guest' }))

    expect (await screen.findByText ('関聯')).toBeInTheDocument ()
    expect (screen.queryByText ('編輯')).not.toBeInTheDocument ()
  })
})
