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

import MaterialListPage from '@/pages/materials/MaterialListPage'
import { buildMaterial, buildTag } from '@/test/factories'
import { renderWithProviders } from '@/test/render'

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

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

describe ('MaterialListPage', () => {
  beforeEach (() => {
    vi.clearAllMocks ()
    api.apiGet.mockResolvedValue ({ materials: [], count: 0 })
  })

  it ('loads the material index on the initial page', async () => {
    renderWithProviders (<MaterialListPage/>, { route: '/materials' })

    await waitFor (() => {
      expect (api.apiGet).toHaveBeenCalledWith (
          '/materials',
          { params: {
              tag_state:  'all',
              media_kind: 'all',
              sort:       'created_at',
              direction:  'desc',
              page:       1,
              limit:      20,
          } },
      )
    })
    expect (await screen.findByText ('素材はありません。')).toBeInTheDocument ()
  })

  it ('shows materials in the default card view', async () => {
    api.apiGet.mockResolvedValueOnce ({
      materials: [
        buildMaterial ({
          id:          8,
          tag:         buildTag ({ name: '素材タグ', category: 'material' }),
          thumbnail:   'thumb.png',
          mediaKind:   'image',
          contentType: 'image/png',
        }),
      ],
      count: 1,
    })

    renderWithProviders (<MaterialListPage/>, { route: '/materials' })

    expect ((await screen.findByText ('素材タグ')).closest ('a'))
      .toHaveAttribute ('href', '/materials/8')
    expect (screen.getByText (/画像 \/ 令和8年1月2日/)).toBeInTheDocument ()
  })

  it ('submits list filters as material index params', async () => {
    renderWithProviders (<MaterialListPage/>, { route: '/materials' })

    fireEvent.change (screen.getByPlaceholderText ('タグ名 / URL / ファイル名'), {
      target: { value: '虹夏' },
    })
    const selects = screen.getAllByRole ('combobox')
    fireEvent.change (selects[0], {
      target: { value: 'untagged' },
    })
    fireEvent.change (selects[1], {
      target: { value: 'image' },
    })
    fireEvent.click (screen.getByRole ('button', { name: '検索' }))

    await waitFor (() => {
      expect (api.apiGet).toHaveBeenCalledWith (
          '/materials',
          expect.objectContaining ({ params: expect.objectContaining ({
            q:          '虹夏',
            tag_state:  'untagged',
            media_kind: 'image',
          }) }),
      )
    })
  })

  it ('keeps a route back to the material search top for untagged materials', async () => {
    renderWithProviders (
        <MaterialListPage/>,
        { route: '/materials?tag_state=untagged&material_filter=any' },
    )

    expect (await screen.findByRole (
        'link',
        { name: '素材検索トップへ戻る' },
    )).toHaveAttribute ('href', '/materials?material_filter=any')
  })

  it ('uses list view for detailed material rows', async () => {
    api.apiGet.mockResolvedValueOnce ({
      materials: [
        buildMaterial ({
          id:           9,
          tag:          buildTag ({ name: '詳細素材', category: 'material' }),
          mediaKind:    'file_other',
          fileByteSize: 2048,
        }),
      ],
      count: 1,
    })

    renderWithProviders (<MaterialListPage/>, { route: '/materials?view=list' })

    expect (await screen.findByRole ('link', { name: '詳細素材' })).toHaveAttribute (
        'href',
        '/materials/9',
    )
    expect (screen.getByText ('サイズ:')).toBeInTheDocument ()
    expect (screen.getByText ('2.0 KB')).toBeInTheDocument ()
  })

  it ('shows the selected tag scope and tag-specific add links', async () => {
    api.apiGet.mockResolvedValueOnce ({
      materials: [
        buildMaterial ({
          id:  10,
          tag: buildTag ({ id: 30, name: '泣き', category: 'material' }),
        }),
      ],
      count:    1,
      tagScope: {
        tag:                { id: 20, name: '伊地知ニジカ', category: 'material' },
        includeDescendants: true,
      },
      groups: [
        {
          key:         'tag:30',
          tag:         { id: 30, name: '泣き', category: 'material' },
          materialIds: [10],
          count:       1,
        },
      ],
    })

    renderWithProviders (
        <MaterialListPage/>,
        { route: '/materials?tag_id=20&include_descendants=1&group_by=parent_tag' },
    )

    expect (await screen.findByText (
        (_, element) => element?.textContent === '伊地知ニジカ 配下の素材を表示中',
    )).toBeInTheDocument ()

    expect (screen.getByRole ('link', { name: '泣き' })).toHaveAttribute (
        'href',
        '/materials?tag_id=30&include_descendants=1&group_by=parent_tag&material_filter=present',
    )
    expect (screen.getByRole ('link', { name: 'タグ選択を解除' })).toHaveAttribute (
        'href',
        '/materials?material_filter=present',
    )
  })

  it ('shows a clear link when tag_id does not resolve to tag_scope', async () => {
    api.apiGet.mockResolvedValueOnce ({
      materials: [],
      count:     0,
      tagScope:  null,
      groups:    [],
    })

    renderWithProviders (<MaterialListPage/>, { route: '/materials?tag_id=999' })

    expect (await screen.findByText ('選択中タグが見つかりません．')).toBeInTheDocument ()
    expect (screen.getByRole ('link', { name: 'タグ選択を解除' })).toHaveAttribute (
        'href',
        '/materials?material_filter=present',
    )
  })
})
