import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/svelte';
import HistoryPage from './+page.svelte';
import type { HistoryEvent, HistoryResponse } from '$lib/api/history';
import { makeTrack } from '$test-utils/fixtures/track';

const mkTrack = (id: string) => makeTrack({ id: `track-${id}`, title: `Song ${id}` });

const mkEvent = (id: string, playedAt: Date): HistoryEvent => ({
  id: `event-${id}`,
  played_at: playedAt.toISOString(),
  track: mkTrack(id)
});

let mockData: { pages: HistoryResponse[] } = { pages: [] };
let mockHasNextPage = false;
let mockIsPending = false;
let mockIsError = false;
let mockIsFetchingNextPage = false;
const mockFetchNextPage = vi.fn();
const mockRefetch = vi.fn();

vi.mock('$lib/api/history', async () => {
  const actual = await vi.importActual<typeof import('$lib/api/history')>(
    '$lib/api/history'
  );
  return {
    ...actual,
    createHistoryQuery: () => ({
      subscribe: (run: (v: unknown) => void) => {
        run({
          data: mockData,
          isPending: mockIsPending,
          isError: mockIsError,
          error: mockIsError ? new Error('test') : null,
          hasNextPage: mockHasNextPage,
          isFetchingNextPage: mockIsFetchingNextPage,
          fetchNextPage: mockFetchNextPage,
          refetch: mockRefetch
        });
        return () => {};
      }
    })
  };
});

vi.mock('$lib/player/store.svelte', () => ({
  playQueue: vi.fn()
}));

vi.mock('$lib/branding', () => ({
  pageTitle: (s: string) => `Minstrel · ${s}`
}));

describe('library/history page', () => {
  beforeEach(() => {
    mockData = { pages: [] };
    mockHasNextPage = false;
    mockIsPending = false;
    mockIsError = false;
    mockIsFetchingNextPage = false;
    mockFetchNextPage.mockClear();
    mockRefetch.mockClear();
  });

  it('renders Today and Yesterday section headers across page boundary', () => {
    const today = new Date();
    const yesterday = new Date();
    yesterday.setDate(yesterday.getDate() - 1);
    mockData = {
      pages: [
        { events: [mkEvent('a', today), mkEvent('b', today)], has_more: true },
        { events: [mkEvent('c', yesterday)], has_more: false }
      ]
    };
    render(HistoryPage);
    expect(screen.getByText('Today')).toBeInTheDocument();
    expect(screen.getByText('Yesterday')).toBeInTheDocument();
    expect(screen.getByText('Song a')).toBeInTheDocument();
    expect(screen.getByText('Song b')).toBeInTheDocument();
    expect(screen.getByText('Song c')).toBeInTheDocument();
  });

  it('renders empty state when query data is empty', () => {
    mockData = { pages: [{ events: [], has_more: false }] };
    render(HistoryPage);
    expect(screen.getByText(/no listening history yet/i)).toBeInTheDocument();
  });

  it('renders error banner on isError', () => {
    mockIsError = true;
    render(HistoryPage);
    // ApiErrorBanner renders a role="alert" container. Assert by role
    // rather than by message text, since the message comes from the
    // error object's `.message` property and varies.
    expect(screen.getByRole('alert')).toBeInTheDocument();
  });

  it('does not render the InfiniteScrollSentinel when hasNextPage is false', () => {
    mockData = { pages: [{ events: [mkEvent('a', new Date())], has_more: false }] };
    mockHasNextPage = false;
    const { container } = render(HistoryPage);
    // InfiniteScrollSentinel renders <div aria-hidden="true" class="h-px">.
    // When hasNextPage is false, no such div should be in the DOM.
    expect(container.querySelector('div[aria-hidden="true"].h-px')).toBeNull();
    // Confirm the page rendered the row so we know we hit the success branch.
    expect(screen.getByText('Song a')).toBeInTheDocument();
  });

  it('renders the InfiniteScrollSentinel when hasNextPage is true', () => {
    mockData = { pages: [{ events: [mkEvent('a', new Date())], has_more: true }] };
    mockHasNextPage = true;
    mockIsFetchingNextPage = false;
    const { container } = render(HistoryPage);
    expect(container.querySelector('div[aria-hidden="true"].h-px')).not.toBeNull();
  });
});
