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

import TagListPage from '@/pages/tags/TagListPage'
import { buildTag } from '@/test/factories'
import { renderWithProviders } from '@/test/render'
import { CLIENT_SETTINGS_STORAGE_KEY } from '@/lib/settings'

const tagsApi = vi.hoisted (() => ({
  fetchTags: vi.fn (),
}))

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

describe ('TagListPage', () => {
  beforeEach (() => {
    localStorage.clear ()
    vi.clearAllMocks ()
  })

  it ('loads tags from URL filters and renders the results table', async () => {
    tagsApi.fetchTags.mockResolvedValueOnce ({
      tags: [buildTag ({
        id: 7,
        name: '虹夏',
        category: 'character',
        postCount: 99,
        deprecatedAt: '2026-06-01T00:00:00.000Z',
      })],
      count: 1,
    })

    renderWithProviders (
        <TagListPage/>,
        {
          route:
              '/tags?name=%E8%99%B9&category=character&page=3&post_count_gte=5&deprecated=1',
        },
    )

    await waitFor (() => {
      expect (tagsApi.fetchTags).toHaveBeenCalledWith (
          expect.objectContaining ({
            name:         '虹',
            category:     'character',
            page:         3,
            limit:        20,
            order:        'post_count:desc',
            postCountGTE: 5,
            deprecated:   true,
          }),
      )
    })
    expect (await screen.findByRole ('link', { name: '虹夏' })).toHaveAttribute (
        'href',
        '/tags/7',
    )
    expect (screen.getAllByText ('キャラクター').length).toBeGreaterThan (0)
    expect (screen.getAllByRole ('combobox')[2]).toHaveValue ('1')
    expect (screen.getAllByText ('廃止')).toHaveLength (2)
  })

  it ('navigates to a normalized search URL on submit', async () => {
    tagsApi.fetchTags.mockResolvedValue ({ tags: [], count: 0 })

    renderWithProviders (<TagListPage/>, { route: '/tags' })

    fireEvent.change (screen.getByRole ('textbox'), { target: { value: '虹夏' } })
    fireEvent.change (screen.getAllByRole ('combobox')[1], {
      target: { value: 'character' },
    })
    fireEvent.submit (screen.getByRole ('button', { name: '検索' }).closest ('form')!)

    await waitFor (() => {
      expect (tagsApi.fetchTags).toHaveBeenLastCalledWith (
          expect.objectContaining ({
            name:     '虹夏',
            category: 'character',
            page:     1,
          }),
      )
    })
    expect (screen.getByRole ('textbox')).toHaveValue ('虹夏')
  })

  it ('shows the no-result message', async () => {
    tagsApi.fetchTags.mockResolvedValueOnce ({ tags: [], count: 0 })

    renderWithProviders (<TagListPage/>, { route: '/tags' })

    expect (await screen.findByText ('結果ないよ（笑）')).toBeInTheDocument ()
  })

  it ('uses URL limit before localStorage and preserves the query order', async () => {
    localStorage.setItem (
        CLIENT_SETTINGS_STORAGE_KEY,
        JSON.stringify ({ lists: { tagList: { limit: 100, order: 'name:asc' } } }))
    tagsApi.fetchTags.mockResolvedValueOnce ({ tags: [], count: 0 })

    renderWithProviders (
        <TagListPage/>,
        { route: '/tags?limit=50&order=updated_at%3Adesc' },
    )

    await waitFor (() => {
      expect (tagsApi.fetchTags).toHaveBeenCalledWith (
          expect.objectContaining ({ limit: 50, order: 'updated_at:desc' }),
      )
    })
    expect (JSON.parse (localStorage.getItem (CLIENT_SETTINGS_STORAGE_KEY) ?? '{}'))
        .toMatchObject ({ lists: { tagList: { limit: 50, order: 'updated_at:desc' } } })
  })

  it ('keeps the compact limit control outside the search form and resets page on change', async () => {
    tagsApi.fetchTags.mockResolvedValue ({ tags: [], count: 0 })

    renderWithProviders (<TagListPage/>, { route: '/tags?page=4' })

    const limitSelect = await screen.findByLabelText ('件数')
    expect (limitSelect.closest ('form')).toBeNull ()

    fireEvent.change (limitSelect, { target: { value: '100' } })

    await waitFor (() => {
      expect (tagsApi.fetchTags).toHaveBeenLastCalledWith (
          expect.objectContaining ({ page: 1, limit: 100 }),
      )
    })
  })
})
