import { afterEach, describe, expect, test, vi } from 'vitest';
import { render, screen, waitFor, fireEvent } from '@testing-library/svelte';
import { mockQuery } from '$test-utils/query';
import { pageUrlModule } from '$test-utils/mocks/appState';
import { apiClientMock } from '$test-utils/mocks/client';
import { emptyLikesMock } from '$test-utils/mocks/likes';
import type { AlbumRef } from '$lib/api/types';

const pageState = vi.hoisted(() => ({
  pageUrl: new URL('http://localhost/library/years')
}));

vi.mock('$app/state', () => pageUrlModule(pageState));

vi.mock('$lib/api/browse', () => ({
  BROWSE_PAGE_SIZE: 2,
  createAlbumYearsQuery: vi.fn(),
  listAlbumsByYear: vi.fn()
}));

vi.mock('$lib/api/client', () => apiClientMock());
vi.mock('$lib/api/likes', () => emptyLikesMock());
vi.mock('$lib/player/store.svelte', () => ({
  playQueue: vi.fn(),
  playRadio: vi.fn(),
  enqueueTrack: vi.fn(),
  enqueueTracks: vi.fn(),
  player: { current: undefined }
}));

import YearsPage from './+page.svelte';
import { createAlbumYearsQuery, listAlbumsByYear } from '$lib/api/browse';

const asMock = (fn: unknown) => fn as ReturnType<typeof vi.fn>;

function album(id: string, title: string): AlbumRef {
  return {
    id,
    title,
    sort_title: title,
    artist_id: 'ar1',
    artist_name: 'Someone',
    year: 1999,
    track_count: 1,
    duration_sec: 100,
    cover_url: '',
    cover_art_source: null
  };
}

afterEach(() => {
  pageState.pageUrl = new URL('http://localhost/library/years');
  vi.clearAllMocks();
});

describe('/library/years index', () => {
  test('groups years into decades, newest decade first', () => {
    asMock(createAlbumYearsQuery).mockReturnValue(
      mockQuery({
        data: [
          { year: 2020, album_count: 3 },
          { year: 1995, album_count: 2 },
          { year: 1991, album_count: 1 }
        ]
      })
    );
    render(YearsPage);

    const headings = screen.getAllByRole('heading', { level: 2 }).map((h) => h.textContent ?? '');
    const decades = headings.map((t) => t.trim().split(/\s+/)[0]);
    expect(decades).toEqual(['2020s', '1990s']);
  });

  test('sums album counts per decade', () => {
    asMock(createAlbumYearsQuery).mockReturnValue(
      mockQuery({
        data: [
          { year: 1995, album_count: 2 },
          { year: 1991, album_count: 5 }
        ]
      })
    );
    render(YearsPage);

    // 2 + 5 across the decade, not per-year.
    const heading = screen.getByRole('heading', { level: 2 });
    expect(heading.textContent).toMatch(/1990s\s*·\s*7/);
  });

  test('years within a decade run newest first', () => {
    asMock(createAlbumYearsQuery).mockReturnValue(
      mockQuery({
        data: [
          { year: 1991, album_count: 1 },
          { year: 1997, album_count: 1 },
          { year: 1994, album_count: 1 }
        ]
      })
    );
    render(YearsPage);

    const links = screen.getAllByRole('link').map((a) => a.getAttribute('href'));
    expect(links).toEqual([
      '/library/years?y=1997',
      '/library/years?y=1994',
      '/library/years?y=1991'
    ]);
  });

  // Undated albums are excluded server-side rather than bucketed under a fake
  // year, so an entirely undated library legitimately lands on the empty state.
  test('empty index explains that undated albums are absent from this axis', () => {
    asMock(createAlbumYearsQuery).mockReturnValue(mockQuery({ data: [] }));
    render(YearsPage);

    expect(screen.getByText('No release years found')).toBeInTheDocument();
    expect(screen.getByText(/without one don't appear/i)).toBeInTheDocument();
  });
});

describe('/library/years drill-down', () => {
  test('requests the selected year as a degenerate range', async () => {
    pageState.pageUrl = new URL('http://localhost/library/years?y=1995');
    asMock(createAlbumYearsQuery).mockReturnValue(mockQuery({ data: [] }));
    asMock(listAlbumsByYear).mockResolvedValue({
      items: [album('a1', 'Mid Nineties')],
      total: 1,
      limit: 2,
      offset: 0
    });
    render(YearsPage);

    await waitFor(() => expect(listAlbumsByYear).toHaveBeenCalledWith(1995, 2, 0));
    expect(await screen.findByText('Mid Nineties')).toBeInTheDocument();
  });

  test('a non-numeric year is treated as no selection', () => {
    pageState.pageUrl = new URL('http://localhost/library/years?y=nineteen');
    asMock(createAlbumYearsQuery).mockReturnValue(
      mockQuery({ data: [{ year: 1999, album_count: 1 }] })
    );
    render(YearsPage);

    // Falls back to the index rather than fetching NaN.
    expect(listAlbumsByYear).not.toHaveBeenCalled();
    expect(screen.getByRole('heading', { level: 1, name: 'Years' })).toBeInTheDocument();
  });

  test('load more appends and then reports the end', async () => {
    pageState.pageUrl = new URL('http://localhost/library/years?y=1995');
    asMock(createAlbumYearsQuery).mockReturnValue(mockQuery({ data: [] }));
    asMock(listAlbumsByYear)
      .mockResolvedValueOnce({
        items: [album('a1', 'One'), album('a2', 'Two')],
        total: 3,
        limit: 2,
        offset: 0
      })
      .mockResolvedValueOnce({ items: [album('a3', 'Three')], total: 3, limit: 2, offset: 2 });
    render(YearsPage);

    await screen.findByText('One');
    await fireEvent.click(await screen.findByRole('button', { name: /Load more \(1 left\)/ }));

    await waitFor(() => expect(listAlbumsByYear).toHaveBeenLastCalledWith(1995, 2, 2));
    expect(await screen.findByText('Three')).toBeInTheDocument();
    expect(screen.getByText('One')).toBeInTheDocument();
    expect(await screen.findByText('End of year')).toBeInTheDocument();
  });

  test('a failed drill-down offers a retry', async () => {
    pageState.pageUrl = new URL('http://localhost/library/years?y=1995');
    asMock(createAlbumYearsQuery).mockReturnValue(mockQuery({ data: [] }));
    asMock(listAlbumsByYear).mockRejectedValue(new Error('nope'));
    render(YearsPage);

    expect(await screen.findByText(/Couldn't load albums for 1995/i)).toBeInTheDocument();
  });
});
