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

// Wrap useQueryClient so the page can call invalidateQueries() without a real
// QueryClient context. Everything else from svelte-query passes through.
vi.mock('$lib/api/admin', () => ({
  createAdminQuarantineQuery: vi.fn(),
  resolveQuarantine: vi.fn(),
  deleteQuarantineFile: vi.fn(),
  deleteQuarantineViaLidarr: vi.fn()
}));

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

import AdminQuarantinePage from './+page.svelte';
import {
  createAdminQuarantineQuery,
  resolveQuarantine,
  deleteQuarantineFile,
  deleteQuarantineViaLidarr
} from '$lib/api/admin';
import { playRadio } from '$lib/player/store.svelte';

const baseRow: AdminQuarantineRow = {
  track_id: 't-001',
  track_title: 'Roygbiv',
  artist_name: 'Boards of Canada',
  album_title: 'Geogaddi',
  album_id: 'al-001',
  lidarr_album_mbid: 'al-mbid-1',
  report_count: 3,
  latest_at: new Date(Date.now() - 2 * 3_600_000).toISOString(),
  reason_counts: { bad_rip: 2, wrong_tags: 1 },
  reports: [
    {
      user_id: 'u1',
      username: 'alice',
      reason: 'bad_rip',
      notes: 'crackles at 1:24',
      created_at: new Date(Date.now() - 2 * 3_600_000).toISOString()
    },
    {
      user_id: 'u2',
      username: 'bob',
      reason: 'bad_rip',
      notes: null,
      created_at: new Date(Date.now() - 5 * 3_600_000).toISOString()
    },
    {
      user_id: 'u3',
      username: 'carol',
      reason: 'wrong_tags',
      notes: 'wrong year',
      created_at: new Date(Date.now() - 7 * 3_600_000).toISOString()
    }
  ]
};

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

function setup(rows: AdminQuarantineRow[] = [baseRow]) {
  (createAdminQuarantineQuery as ReturnType<typeof vi.fn>).mockReturnValue(
    mockQuery({ data: rows })
  );
  return render(AdminQuarantinePage);
}

describe('/admin/quarantine', () => {
  test('Empty state shows the spec copy', () => {
    setup([]);
    expect(screen.getByText('Nothing to triage right now.')).toBeInTheDocument();
  });

  test('Aggregated row renders with reason distribution + report count', () => {
    setup();
    expect(screen.getByText('Roygbiv')).toBeInTheDocument();
    expect(
      screen.getByText(/Boards of Canada · Geogaddi · 3 reports — latest/i)
    ).toBeInTheDocument();
    const pills = screen.getAllByTestId('reason-pill').map((el) => el.textContent?.trim());
    expect(pills).toContain('2× Bad rip');
    expect(pills).toContain('1× Wrong tags');
    expect(screen.getByTestId('report-count-pill')).toHaveTextContent('3');
  });

  test('Resolve fires resolveQuarantine and invalidates the query', async () => {
    setup();
    (resolveQuarantine as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
      action_id: 'a1',
      affected_users: 3
    });
    await fireEvent.click(screen.getByRole('button', { name: /resolve roygbiv/i }));
    expect(resolveQuarantine).toHaveBeenCalledWith('t-001');
  });

  test('Delete file → modal-confirm → fires deleteQuarantineFile', async () => {
    setup();
    (deleteQuarantineFile as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
      action_id: 'a2',
      affected_users: 3
    });
    await fireEvent.click(
      screen.getByRole('button', { name: /delete file for roygbiv/i })
    );
    const dialog = await screen.findByRole('dialog');
    expect(dialog).toHaveTextContent(/Remove/i);
    expect(dialog).toHaveTextContent(/clear 3 reports/i);
    await fireEvent.click(
      screen.getByRole('button', { name: /confirm delete file/i })
    );
    expect(deleteQuarantineFile).toHaveBeenCalledWith('t-001');
  });

  test('Delete via Lidarr → typed-confirm "DELETE" → fires deleteQuarantineViaLidarr', async () => {
    setup();
    (deleteQuarantineViaLidarr as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
      action_id: 'a3',
      affected_users: 3,
      deleted_track_count: 12
    });
    await fireEvent.click(
      screen.getByRole('button', { name: /delete via lidarr for roygbiv/i })
    );
    await screen.findByRole('dialog');
    const confirmBtn = screen.getByRole('button', {
      name: /confirm delete via lidarr/i
    }) as HTMLButtonElement;
    expect(confirmBtn).toBeDisabled();
    const input = screen.getByLabelText(/type delete to confirm/i) as HTMLInputElement;
    await fireEvent.input(input, { target: { value: 'DELETE' } });
    expect(confirmBtn).not.toBeDisabled();
    await fireEvent.click(confirmBtn);
    expect(deleteQuarantineViaLidarr).toHaveBeenCalledWith('t-001');
  });

  test('Lidarr-unreachable error shows toast with spec copy', async () => {
    setup();
    (resolveQuarantine as ReturnType<typeof vi.fn>).mockRejectedValueOnce({
      code: 'lidarr_unreachable',
      message: 'unreachable',
      status: 503
    });
    await fireEvent.click(screen.getByRole('button', { name: /resolve roygbiv/i }));
    await waitFor(() =>
      expect(
        screen.getByText(
          'Lidarr is unreachable right now. Try again, or check Admin → Integrations.'
        )
      ).toBeInTheDocument()
    );
  });

  test('Dimmed Delete-via-Lidarr when lidarr_album_mbid is null', () => {
    setup([{ ...baseRow, lidarr_album_mbid: null }]);
    const disabled = screen.getByTestId('delete-lidarr-disabled');
    expect(disabled).toHaveClass('cursor-not-allowed');
    expect(disabled).toHaveClass('opacity-50');
    expect(disabled).toHaveAttribute(
      'title',
      'Local-only track — no Lidarr album to remove.'
    );
    expect(
      screen.queryByRole('button', { name: /delete via lidarr for roygbiv/i })
    ).not.toBeInTheDocument();
  });

  test('Inline play button calls playRadio with the track id', async () => {
    setup();
    await fireEvent.click(screen.getByRole('button', { name: /play roygbiv/i }));
    expect(playRadio).toHaveBeenCalledWith('t-001');
  });
});
