import { describe, it, expect, vi, beforeEach } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { MemoryRouter } from 'react-router-dom'
import LibraryPage from '../src/pages/LibraryPage.js'
import type { Item } from '../src/types.js'

vi.mock('../src/api.js', async (importOriginal) => {
  const actual = await importOriginal<typeof import('../src/api.js')>()
  return { ...actual, api: { ...actual.api, listCollection: vi.fn() } }
})

import { api } from '../src/api.js'

function item(overrides: Partial<Item>): Item {
  return {
    id: 1,
    discogsReleaseId: 100,
    title: 'Motion',
    artist: 'The Cinematic Orchestra',
    year: 1999,
    formats: ['CD'],
    genres: [],
    labels: [],
    tracklist: [],
    catno: null,
    country: null,
    artworkUrl: null,
    barcodes: [],
    dateAdded: '2026-08-29',
    ripOverride: null,
    ripStatus: 'not_ripped',
    ...overrides,
  }
}

const data = {
  items: [
    item({ id: 1, ripStatus: 'not_ripped' }),
    item({ id: 2, title: 'Blue Lines', artist: 'Massive Attack', formats: ['Vinyl'], ripStatus: 'ripped' }),
    item({ id: 3, title: 'Mezzanine', artist: 'Massive Attack', formats: ['Cassette'], ripStatus: 'not_ripped' }),
  ],
  counts: { total: 3, ripped: 1, notRipped: 2 },
}

beforeEach(() => {
  vi.mocked(api.listCollection).mockReset()
  vi.mocked(api.listCollection).mockResolvedValue(data as never)
})

function renderLibrary() {
  return render(
    <MemoryRouter>
      <LibraryPage />
    </MemoryRouter>
  )
}

describe('LibraryPage', () => {
  it('renders covers with artist and rip badges', async () => {
    renderLibrary()
    await waitFor(() => expect(screen.getAllByRole('link', { name: /motion/i })).toHaveLength(1))
    expect(screen.getByRole('link', { name: /blue lines/i })).toBeTruthy()
    expect(screen.getByRole('link', { name: /mezzanine/i })).toBeTruthy()
  })

  it('passes format and rip filters to the api', async () => {
    renderLibrary()
    await waitFor(() => expect(screen.getByRole('button', { name: /^vinyl$/i })).toBeTruthy())
    await userEvent.click(screen.getByRole('button', { name: /^vinyl$/i }))
    await waitFor(() =>
      expect(api.listCollection).toHaveBeenLastCalledWith(expect.objectContaining({ format: 'Vinyl' }))
    )
    await userEvent.click(screen.getByRole('button', { name: /^ripped$/i }))
    await waitFor(() =>
      expect(api.listCollection).toHaveBeenLastCalledWith(
        expect.objectContaining({ format: 'Vinyl', ripped: 'ripped' })
      )
    )
  })

  it('searches by title/artist via the q filter', async () => {
    renderLibrary()
    await waitFor(() => expect(screen.getByPlaceholderText(/search/i)).toBeTruthy())
    await userEvent.type(screen.getByPlaceholderText(/search/i), 'mezz')
    await waitFor(() =>
      expect(api.listCollection).toHaveBeenLastCalledWith(expect.objectContaining({ q: 'mezz' }))
    )
  })

  it('shows collection counts', async () => {
    renderLibrary()
    await waitFor(() => expect(screen.getByText(/3 in collection/i)).toBeTruthy())
    expect(screen.getByText(/1 ripped/i)).toBeTruthy()
    expect(screen.getByText(/2 not ripped/i)).toBeTruthy()
  })

  it('artist index sets the search box to the artist name', async () => {
    renderLibrary()
    const artistBtn = await screen.findByRole('button', { name: /massive attack/i })
    await userEvent.click(artistBtn)
    await waitFor(() =>
      expect(api.listCollection).toHaveBeenLastCalledWith(expect.objectContaining({ q: 'Massive Attack' }))
    )
  })

  it('shows an empty state when the collection is empty', async () => {
    vi.mocked(api.listCollection).mockResolvedValue({ items: [], counts: { total: 0, ripped: 0, notRipped: 0 } } as never)
    renderLibrary()
    await waitFor(() => expect(screen.getByText(/nothing here yet/i)).toBeTruthy())
    expect(screen.getByRole('link', { name: /add your first record/i }).getAttribute('href')).toBe('/add')
  })
})
