import { QueryClient } from '@tanstack/react-query'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import { prefetchForURL } from '@/lib/prefetchers'

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

const tagsApi = vi.hoisted (() => ({
  fetchNicoTags:   vi.fn (),
  fetchTag:        vi.fn (),
  fetchTagByName:  vi.fn (),
  fetchTagChanges: vi.fn (),
  fetchTags:       vi.fn (),
}))

const wikiApi = vi.hoisted (() => ({
  fetchWikiPage:        vi.fn (),
  fetchWikiPageByTitle: vi.fn (),
  fetchWikiPages:       vi.fn (),
}))

vi.mock ('@/lib/posts', () => postsApi)
vi.mock ('@/lib/tags', () => tagsApi)
vi.mock ('@/lib/wiki', () => wikiApi)

const qc = () => new QueryClient ({
  defaultOptions: { queries: { retry: false } },
})

describe ('prefetchForURL', () => {
  beforeEach (() => {
    vi.clearAllMocks ()
    postsApi.fetchPosts.mockResolvedValue ({ posts: [], count: 0 })
    postsApi.fetchPost.mockResolvedValue ({ id: 1 })
    postsApi.fetchPostChanges.mockResolvedValue ({ versions: [], count: 0 })
    tagsApi.fetchTags.mockResolvedValue ({ tags: [], count: 0 })
    tagsApi.fetchNicoTags.mockResolvedValue ({ tags: [], count: 0 })
    tagsApi.fetchTag.mockResolvedValue ({ id: 1 })
    tagsApi.fetchTagByName.mockResolvedValue (null)
    tagsApi.fetchTagChanges.mockResolvedValue ({ versions: [], count: 0 })
    wikiApi.fetchWikiPages.mockResolvedValue ([])
    wikiApi.fetchWikiPage.mockResolvedValue ({ id: 1 })
    wikiApi.fetchWikiPageByTitle.mockResolvedValue (null)
  })

  it ('prefetches post indexes from query parameters', async () => {
    await prefetchForURL (
        qc (),
        'http://localhost/posts?tags=a+b&match=any&page=2&limit=5&order=title%3Aasc',
    )

    expect (postsApi.fetchPosts).toHaveBeenCalledWith (
        expect.objectContaining ({
          tags:  'a b',
          match: 'any',
          page:  2,
          limit: 5,
          order: 'title:asc',
        }),
    )
  })

  it ('prefetches post detail pages', async () => {
    await prefetchForURL (qc (), 'http://localhost/posts/12')

    expect (postsApi.fetchPost).toHaveBeenCalledWith ('12')
  })

  it ('prefetches tag indexes from query parameters', async () => {
    await prefetchForURL (
        qc (),
        'http://localhost/tags?post=9&name=x&category=general&page=4&post_count_lte=10',
    )

    expect (tagsApi.fetchTags).toHaveBeenCalledWith (
        expect.objectContaining ({
          post:         9,
          name:         'x',
          category:     'general',
          page:         4,
          postCountLTE: 10,
        }),
    )
  })

  it ('prefetches nico tag indexes and their alias from query parameters', async () => {
    await prefetchForURL (
        qc (),
        'http://localhost/tags/nico?name=source&linked_tag=destination'
	    + '&link_status=linked&page=3&limit=10',
    )
    await prefetchForURL (qc (), 'http://localhost/nico/tags?page=2')

    expect (tagsApi.fetchNicoTags).toHaveBeenNthCalledWith (1, {
      name:       'source',
      linkedTag:  'destination',
      linkStatus: 'linked',
      page:       3,
      limit:      10,
      order:      'updated_at:desc',
    })
    expect (tagsApi.fetchNicoTags).toHaveBeenNthCalledWith (2, {
      name:       '',
      linkedTag:  '',
      linkStatus: 'all',
      page:       2,
      limit:      20,
      order:      'updated_at:desc',
    })
  })

  it ('prefetches wiki show pages and related tag/post data', async () => {
    wikiApi.fetchWikiPageByTitle.mockResolvedValueOnce ({
      id:    3,
      title: 'Actual',
      body:  'body',
    })

    await prefetchForURL (qc (), 'http://localhost/wiki/Alias')

    expect (wikiApi.fetchWikiPageByTitle).toHaveBeenCalledWith ('Alias', { version: undefined })
    expect (wikiApi.fetchWikiPage).toHaveBeenCalledWith ('3', {})
    expect (tagsApi.fetchTagByName).toHaveBeenCalledWith ('Actual')
    expect (postsApi.fetchPosts).toHaveBeenCalledWith (
        expect.objectContaining ({ tags: 'Actual', limit: 8 }),
    )
  })

  it ('ignores routes without a prefetcher', async () => {
    await prefetchForURL (qc (), 'http://localhost/unknown')

    expect (postsApi.fetchPosts).not.toHaveBeenCalled ()
    expect (tagsApi.fetchTags).not.toHaveBeenCalled ()
    expect (wikiApi.fetchWikiPages).not.toHaveBeenCalled ()
  })
})
