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

// $app/state / $app/navigation must be mocked or rendering the page
// pulls in SvelteKit's client runtime (`notifiable_store is not a
// function`) at module load. Same pattern as the other route tests.
const pageState = vi.hoisted(() => ({
  pageUrl: new URL('http://localhost/requests')
}));
vi.mock('$app/state', () => pageUrlModule(pageState));
vi.mock('$app/navigation', () => ({ goto: vi.fn() }));

// Capture invalidateQueries on the mocked QueryClient so the cancel test can
// assert against the same instance the page consumed. Per-file mock OVERRIDES
// the vitest.setup default so assertions see this named spy.
const invalidateQueries = vi.fn().mockResolvedValue(undefined);
vi.mock('@tanstack/svelte-query', async (orig) => {
  const actual = (await orig()) as Record<string, unknown>;
  return { ...actual, useQueryClient: () => ({ invalidateQueries }) };
});

vi.mock('$lib/api/requests', () => ({
  createMyRequestsQuery: vi.fn(),
  cancelRequest: vi.fn().mockResolvedValue({ id: 'r1' })
}));

vi.mock('$lib/api/queries', () => ({
  qk: {
    myRequests: () => ['myRequests']
  }
}));

vi.mock('$lib/api/client', () => apiClientMock());

import RequestsPage from './+page.svelte';
import { createMyRequestsQuery, cancelRequest } from '$lib/api/requests';
import { qk } from '$lib/api/queries';

const mockedCreateMyRequestsQuery = createMyRequestsQuery as ReturnType<typeof vi.fn>;
const mockedCancelRequest = cancelRequest as ReturnType<typeof vi.fn>;

function req(over: Partial<LidarrRequest> = {}): LidarrRequest {
  return {
    id: 'r1',
    user_id: 'u1',
    status: 'pending',
    kind: 'album',
    lidarr_artist_mbid: 'art-mbid',
    lidarr_album_mbid: 'al-mbid',
    lidarr_track_mbid: null,
    artist_name: 'Boards of Canada',
    album_title: 'Music Has The Right To Children',
    track_title: null,
    quality_profile_id: null,
    root_folder_path: null,
    decided_at: null,
    decided_by: null,
    notes: null,
    completed_at: null,
    matched_track_id: null,
    matched_album_id: null,
    matched_artist_id: null,
    requested_at: '2026-04-28T12:00:00Z',
    updated_at: '2026-04-28T12:00:00Z',
    imported_album_count: 0,
    imported_track_count: 0,
    ...over
  };
}

afterEach(() => {
  vi.clearAllMocks();
});

describe('Requests page', () => {
  test('renders one row per request from the API', () => {
    const rows: LidarrRequest[] = [
      req({ id: 'r1', kind: 'artist', artist_name: 'Boards of Canada' }),
      req({ id: 'r2', kind: 'album',  album_title: 'Geogaddi', status: 'completed' }),
      req({ id: 'r3', kind: 'track',  track_title: 'Roygbiv', status: 'rejected', notes: 'Already in library' })
    ];
    mockedCreateMyRequestsQuery.mockReturnValue(mockQuery<LidarrRequest[]>({ data: rows }));
    render(RequestsPage);
    expect(screen.getAllByTestId('request-row')).toHaveLength(3);
    expect(screen.getByText('Boards of Canada')).toBeInTheDocument();
    expect(screen.getByText('Geogaddi')).toBeInTheDocument();
    expect(screen.getByText('Roygbiv')).toBeInTheDocument();
  });

  test('pending row exposes Cancel; clicking calls cancelRequest and invalidates myRequests', async () => {
    const rows: LidarrRequest[] = [
      req({ id: 'r-pending', status: 'pending', kind: 'album', album_title: 'Geogaddi' })
    ];
    mockedCreateMyRequestsQuery.mockReturnValue(mockQuery<LidarrRequest[]>({ data: rows }));
    render(RequestsPage);
    const cancelBtn = screen.getByRole('button', { name: /cancel request for geogaddi/i });
    await fireEvent.click(cancelBtn);
    await waitFor(() => {
      expect(mockedCancelRequest).toHaveBeenCalledWith('r-pending');
      expect(invalidateQueries).toHaveBeenCalledWith({ queryKey: qk.myRequests() });
    });
  });

  test('completed row with matched_track_id renders Listen link to /tracks/<id>', () => {
    const rows: LidarrRequest[] = [
      req({
        id: 'r-track',
        status: 'completed',
        kind: 'track',
        track_title: 'Roygbiv',
        matched_track_id: 'tr-9',
        matched_album_id: 'al-9',
        matched_artist_id: 'art-9'
      })
    ];
    mockedCreateMyRequestsQuery.mockReturnValue(mockQuery<LidarrRequest[]>({ data: rows }));
    render(RequestsPage);
    const link = screen.getByRole('link', { name: /listen to roygbiv/i });
    expect(link).toHaveAttribute('href', '/tracks/tr-9');
  });

  test('completed row with only matched_album_id renders Listen link to /albums/<id>', () => {
    const rows: LidarrRequest[] = [
      req({
        id: 'r-album',
        status: 'completed',
        kind: 'album',
        album_title: 'Geogaddi',
        matched_track_id: null,
        matched_album_id: 'al-7',
        matched_artist_id: 'art-7'
      })
    ];
    mockedCreateMyRequestsQuery.mockReturnValue(mockQuery<LidarrRequest[]>({ data: rows }));
    render(RequestsPage);
    const link = screen.getByRole('link', { name: /listen to geogaddi/i });
    expect(link).toHaveAttribute('href', '/albums/al-7');
  });

  test('rejected row renders notes and hides Cancel and Listen', () => {
    const rows: LidarrRequest[] = [
      req({
        id: 'r-rej',
        status: 'rejected',
        kind: 'album',
        album_title: 'Geogaddi',
        notes: 'Already kept in the library.'
      })
    ];
    mockedCreateMyRequestsQuery.mockReturnValue(mockQuery<LidarrRequest[]>({ data: rows }));
    render(RequestsPage);
    expect(screen.getByTestId('rejection-notes')).toHaveTextContent('Already kept in the library.');
    expect(screen.queryByRole('button', { name: /cancel request/i })).not.toBeInTheDocument();
    expect(screen.queryByRole('link', { name: /listen to/i })).not.toBeInTheDocument();
  });

  test('empty list shows the voice-rule "Nothing requested yet." copy', () => {
    mockedCreateMyRequestsQuery.mockReturnValue(mockQuery<LidarrRequest[]>({ data: [] }));
    render(RequestsPage);
    expect(screen.getByText(/nothing requested yet\./i)).toBeInTheDocument();
    expect(screen.queryAllByTestId('request-row')).toHaveLength(0);
  });
});
