import { describe, expect, test, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte';
import PlaylistCard from './PlaylistCard.svelte';
import type { Playlist, PlaylistDetail } from '$lib/api/types';

vi.mock('$lib/auth/store.svelte', () => ({
  user: { value: { id: 'u-self', username: 'me', is_admin: false } }
}));

vi.mock('$lib/api/playlists', () => ({
  getPlaylist: vi.fn().mockResolvedValue({
    id: 'p-1',
    user_id: 'u-self',
    owner_username: 'me',
    name: 'Saturday morning',
    description: '',
    is_public: false,
    kind: 'user',
    system_variant: null,
    refreshable: false,
    seed_artist_id: null,
    cover_url: '',
    track_count: 1,
    duration_sec: 60,
    created_at: '2026-01-01T00:00:00Z',
    updated_at: '2026-01-01T00:00:00Z',
    tracks: [
      {
        position: 0,
        track_id: 't-1',
        album_id: 'al-1',
        artist_id: 'ar-1',
        title: 'Test Track',
        artist_name: 'Test Artist',
        album_title: 'Test Album',
        duration_sec: 60,
        stream_url: '/s/t-1',
        added_at: '2026-01-01T00:00:00Z'
      }
    ]
  } as PlaylistDetail),
  systemShuffle: vi.fn().mockResolvedValue({
    id: 'p-sys',
    user_id: 'u-self',
    owner_username: 'me',
    name: 'For You',
    description: '',
    is_public: false,
    kind: 'system',
    system_variant: 'for_you',
    refreshable: true,
    seed_artist_id: null,
    cover_url: '',
    track_count: 1,
    duration_sec: 60,
    created_at: '2026-01-01T00:00:00Z',
    updated_at: '2026-01-01T00:00:00Z',
    tracks: [
      {
        position: 0,
        track_id: 't-9',
        album_id: 'al-1',
        artist_id: 'ar-1',
        title: 'Rotation Track',
        artist_name: 'Test Artist',
        album_title: 'Test Album',
        duration_sec: 60,
        stream_url: '/s/t-9',
        added_at: '2026-01-01T00:00:00Z'
      }
    ]
  } as PlaylistDetail),
  refreshSystem: vi.fn().mockResolvedValue({ playlist_id: 'p-1', track_count: 5, track_ids: ['t-1'] })
}));

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

vi.mock('$lib/api/queries', () => ({
  qk: {
    playlist: (id: string) => ['playlist', id],
    playlists: (kind?: string) => ['playlists', { kind: kind ?? 'user' }]
  }
}));

const base: Playlist = {
  id: 'p-1',
  user_id: 'u-self',
  owner_username: 'me',
  name: 'Saturday morning',
  description: '',
  is_public: false,
  kind: 'user',
  system_variant: null,
  refreshable: false,
  seed_artist_id: null,
  cover_url: '',
  track_count: 12,
  duration_sec: 0,
  created_at: '',
  updated_at: ''
};

describe('PlaylistCard', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  test('renders own playlist without owner attribution', () => {
    render(PlaylistCard, { props: { playlist: base } });
    expect(screen.getByText('Saturday morning')).toBeInTheDocument();
    expect(screen.getByText(/12\s+tracks/i)).toBeInTheDocument();
    expect(screen.queryByText(/by /)).not.toBeInTheDocument();
  });

  test('navigates to playlist when clicking the card', () => {
    render(PlaylistCard, { props: { playlist: base } });
    const link = screen.getByRole('link');
    expect(link).toHaveAttribute('href', '/playlists/p-1');
  });

  test('renders cover image when cover_url is set', () => {
    // The <img> has alt="" (decorative — the playlist name is in the
    // sibling text below, so duplicating it for screen readers would be
    // noise). Empty alt makes Testing Library expose role="presentation"
    // instead of role="img"; query the DOM directly so the assertion
    // doesn't fight the (correct) a11y choice.
    const { container } = render(PlaylistCard, { props: { playlist: { ...base, cover_url: '/api/playlists/p-1/cover' } } });
    const img = container.querySelector('img');
    expect(img).not.toBeNull();
    expect(img!.getAttribute('src')).toBe('/api/playlists/p-1/cover');
  });

  test('renders glyph fallback when cover_url is empty', () => {
    render(PlaylistCard, { props: { playlist: base } });
    expect(screen.getByText(/no tracks yet/i)).toBeInTheDocument();
  });

  test("attributes other users' playlists to the owner", () => {
    render(PlaylistCard, { props: { playlist: { ...base, user_id: 'u-bob', owner_username: 'bob' } } });
    expect(screen.getByText(/by bob/i)).toBeInTheDocument();
  });

  test('singular "track" when count is 1', () => {
    render(PlaylistCard, { props: { playlist: { ...base, track_count: 1 } } });
    expect(screen.getByText(/1\s+track\b/i)).toBeInTheDocument();
  });

  test('play button exists with correct aria label', () => {
    render(PlaylistCard, { props: { playlist: base } });
    const playButton = screen.getByLabelText(/Play Saturday morning/i);
    expect(playButton).toBeInTheDocument();
  });

  test('play button is disabled when track_count is 0', () => {
    render(PlaylistCard, { props: { playlist: { ...base, track_count: 0 } } });
    const playButton = screen.getByLabelText(/Play Saturday morning/i);
    expect(playButton).toBeDisabled();
  });

  test('play button calls playQueue with tracks when clicked', async () => {
    const { getPlaylist } = await import('$lib/api/playlists');
    const { playQueue } = await import('$lib/player/store.svelte');

    render(PlaylistCard, { props: { playlist: base } });
    const playButton = screen.getByLabelText(/Play Saturday morning/i);

    await fireEvent.click(playButton);

    // Allow promises to resolve
    await new Promise(resolve => setTimeout(resolve, 10));

    expect(getPlaylist).toHaveBeenCalledWith('p-1');
    expect(playQueue).toHaveBeenCalled();
  });

  test('shows kebab on refreshable (singleton system) playlists', () => {
    const discoverPlaylist: Playlist = {
      ...base,
      kind: 'system',
      system_variant: 'discover',
      refreshable: true,
      name: 'Discover'
    };
    render(PlaylistCard, { props: { playlist: discoverPlaylist } });
    expect(screen.getByLabelText(/playlist actions/i)).toBeInTheDocument();
  });

  test('does not show kebab on user playlists', () => {
    render(PlaylistCard, { props: { playlist: base } });
    expect(screen.queryByLabelText(/playlist actions/i)).not.toBeInTheDocument();
  });

  test('does not show kebab on a non-refreshable system playlist', () => {
    // e.g. songs_like_artist — system but multi-per-user, server
    // reports refreshable:false.
    const songsLike: Playlist = {
      ...base,
      kind: 'system',
      system_variant: 'songs_like_artist',
      refreshable: false,
      name: 'Songs like X'
    };
    render(PlaylistCard, { props: { playlist: songsLike } });
    expect(screen.queryByLabelText(/playlist actions/i)).not.toBeInTheDocument();
  });

  test('Refresh item appears in the kebab, labelled by name', async () => {
    const discoverPlaylist: Playlist = {
      ...base,
      kind: 'system',
      system_variant: 'discover',
      refreshable: true,
      name: 'Discover'
    };
    render(PlaylistCard, { props: { playlist: discoverPlaylist } });
    await fireEvent.click(screen.getByLabelText(/playlist actions/i));
    expect(screen.getByRole('menuitem', { name: /refresh discover/i })).toBeInTheDocument();
  });

  test('clicking Refresh calls refreshSystem with the raw variant', async () => {
    const { refreshSystem } = await import('$lib/api/playlists');
    const discoverPlaylist: Playlist = {
      ...base,
      kind: 'system',
      system_variant: 'discover',
      refreshable: true,
      name: 'Discover'
    };
    render(PlaylistCard, { props: { playlist: discoverPlaylist } });
    await fireEvent.click(screen.getByLabelText(/playlist actions/i));
    await fireEvent.click(screen.getByRole('menuitem', { name: /refresh discover/i }));
    await waitFor(() => expect(refreshSystem).toHaveBeenCalledWith('discover'));
  });

  test('For-You play calls systemShuffle, not getPlaylist', async () => {
    const { getPlaylist, systemShuffle } = await import('$lib/api/playlists');
    const { playQueue } = await import('$lib/player/store.svelte');
    const forYouPlaylist: Playlist = {
      ...base,
      kind: 'system',
      system_variant: 'for_you',
      refreshable: true,
      name: 'For You',
      track_count: 25
    };

    render(PlaylistCard, { props: { playlist: forYouPlaylist } });
    const playButton = screen.getByLabelText(/Play For You/i);
    await fireEvent.click(playButton);
    await new Promise(resolve => setTimeout(resolve, 10));

    // #415/#411: system play hits the rotation-aware shuffle endpoint
    // (raw variant), not the cached detail GET, and never refreshes.
    expect(systemShuffle).toHaveBeenCalledWith('for_you');
    expect(getPlaylist).not.toHaveBeenCalled();
    expect(playQueue).toHaveBeenCalled();
  });

  test('For-You play passes source:for_you to playQueue (no client shuffle)', async () => {
    const { playQueue } = await import('$lib/player/store.svelte');
    const forYouPlaylist: Playlist = {
      ...base,
      kind: 'system',
      system_variant: 'for_you',
      name: 'For You'
    };
    render(PlaylistCard, { props: { playlist: forYouPlaylist } });
    const playButton = screen.getByLabelText(/Play For You/i);
    await fireEvent.click(playButton);
    await new Promise(resolve => setTimeout(resolve, 10));

    expect(playQueue).toHaveBeenCalledWith(
      expect.any(Array),
      0,
      expect.objectContaining({ source: 'for_you' })
    );
  });

  test('user-playlist play uses getPlaylist + plain playQueue (no source)', async () => {
    const { getPlaylist, systemShuffle } = await import('$lib/api/playlists');
    const { playQueue } = await import('$lib/player/store.svelte');
    render(PlaylistCard, { props: { playlist: base } });
    const playButton = screen.getByLabelText(/Play Saturday morning/i);
    await fireEvent.click(playButton);
    await new Promise(resolve => setTimeout(resolve, 10));

    expect(systemShuffle).not.toHaveBeenCalled();
    expect(getPlaylist).toHaveBeenCalledWith('p-1');
    expect(playQueue).toHaveBeenCalledWith(expect.any(Array), 0);
  });

  test('user-playlist play does not refresh anything', async () => {
    const { refreshSystem, getPlaylist } = await import('$lib/api/playlists');
    const { playQueue } = await import('$lib/player/store.svelte');

    render(PlaylistCard, { props: { playlist: base } });
    const playButton = screen.getByLabelText(/Play Saturday morning/i);
    await fireEvent.click(playButton);
    await new Promise(resolve => setTimeout(resolve, 10));

    expect(refreshSystem).not.toHaveBeenCalled();
    expect(getPlaylist).toHaveBeenCalledWith('p-1');
    expect(playQueue).toHaveBeenCalled();
  });
});
