import { afterEach, describe, expect, test, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/svelte';
import DiscoverResultCard from './DiscoverResultCard.svelte';

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

describe('DiscoverResultCard', () => {
  test('requestable state renders Request button and calls onRequest on click', async () => {
    const onRequest = vi.fn();
    render(DiscoverResultCard, {
      props: {
        kind: 'artist',
        title: 'Boards of Canada',
        state: 'requestable',
        onRequest,
      },
    });
    // aria-label includes the title, so the accessible name is
    // "Request Boards of Canada".
    const btn = screen.getByRole('button', {
      name: /request boards of canada/i,
    });
    expect(btn).not.toBeDisabled();
    await fireEvent.click(btn);
    expect(onRequest).toHaveBeenCalledOnce();
  });

  test('kept state renders disabled "In library" button + Kept pill with role=status', () => {
    render(DiscoverResultCard, {
      props: {
        kind: 'album',
        title: 'Music Has The Right To Children',
        state: 'kept',
      },
    });
    expect(screen.getByRole('button', { name: /in library/i })).toBeDisabled();
    // Kept pill is exposed as a status region so SR users hear it when it appears.
    const status = screen.getByRole('status');
    expect(status).toBeInTheDocument();
    expect(status.textContent).toMatch(/kept/i);
  });

  test('requested state renders disabled "Requested" button', () => {
    render(DiscoverResultCard, {
      props: {
        kind: 'track',
        title: 'Roygbiv',
        state: 'requested',
      },
    });
    expect(screen.getByRole('button', { name: /requested/i })).toBeDisabled();
  });

  test('does not call onRequest when state is not requestable', async () => {
    const onRequest = vi.fn();
    render(DiscoverResultCard, {
      props: {
        kind: 'album',
        title: 'Geogaddi',
        state: 'kept',
        onRequest,
      },
    });
    // Button is disabled — the test still verifies onRequest isn't called even
    // if a click slips through (jsdom does not enforce :disabled at fireEvent level).
    const btn = screen.getByRole('button');
    await fireEvent.click(btn);
    expect(onRequest).not.toHaveBeenCalled();
  });

  // NOTE: previous versions of this file asserted .badge-row { min-height: 22px }
  // and .actions { margin-top: auto } via getComputedStyle, which only worked
  // because the component shipped inline style="" attributes — a layering
  // violation. Stylesheet introspection (document.styleSheets) was tried as a
  // replacement, but vitest's @testing-library/svelte renderer does not inject
  // the component's scoped <style> block into jsdom (styleSheets.length === 0).
  // Asserting "CSS exists in CSS" has weak value anyway, so the layout
  // discipline (reserved badge slot + bottom-anchored actions) is enforced
  // visually at the consumer page rather than as a unit test here.

  test('renders <img> when imageUrl is set', () => {
    const { container } = render(DiscoverResultCard, {
      props: {
        kind: 'artist',
        title: 'X',
        state: 'requestable',
        imageUrl: 'https://example.com/x.jpg',
      },
    });
    const img = container.querySelector('img') as HTMLImageElement;
    expect(img).toBeInTheDocument();
    expect(img.src).toBe('https://example.com/x.jpg');
  });

  test('renders fallback Lucide glyph (no <img>) when imageUrl is absent', () => {
    const { container } = render(DiscoverResultCard, {
      props: {
        kind: 'album',
        title: 'X',
        state: 'requestable',
      },
    });
    expect(container.querySelector('img')).not.toBeInTheDocument();
    // Lucide renders an inline <svg>; verify its presence as a proxy for "fallback rendered"
    expect(container.querySelector('svg')).toBeInTheDocument();
  });

  test('renders attribution line when prop is set', () => {
    render(DiscoverResultCard, {
      props: {
        kind: 'artist',
        title: 'Outsider',
        state: 'requestable',
        attribution: 'Because you liked Boards of Canada and played Aphex Twin.'
      }
    });
    expect(screen.getByTestId('attribution')).toHaveTextContent(
      'Because you liked Boards of Canada and played Aphex Twin.'
    );
  });

  test('omits attribution line when prop is absent', () => {
    render(DiscoverResultCard, {
      props: { kind: 'artist', title: 'Outsider', state: 'requestable' }
    });
    expect(screen.queryByTestId('attribution')).not.toBeInTheDocument();
  });

  // --- snooze affordance (#2375) ---

  test('snooze button appears only when onSnooze is supplied', () => {
    // The Lidarr search results reuse this card and have nothing to park, so
    // the affordance must not appear unconditionally.
    render(DiscoverResultCard, {
      props: { kind: 'artist', title: 'Outsider', state: 'requestable' }
    });
    expect(screen.queryByRole('button', { name: /not right now/i })).not.toBeInTheDocument();
  });

  test('snooze button calls onSnooze and reads as "not right now", never as a dislike', async () => {
    const onSnooze = vi.fn();
    render(DiscoverResultCard, {
      props: { kind: 'artist', title: 'Outsider', state: 'requestable', onSnooze }
    });
    const btn = screen.getByRole('button', { name: /not right now — hide outsider for a while/i });
    // Rule #101: the accessible name must carry no verdict on the music.
    expect(btn.getAttribute('aria-label')).not.toMatch(/dislike|not for me|never|hate/i);
    await fireEvent.click(btn);
    expect(onSnooze).toHaveBeenCalledOnce();
  });

  test('snoozed state swaps Request for Undo and shows a status pill', async () => {
    const onUnsnooze = vi.fn();
    const onRequest = vi.fn();
    render(DiscoverResultCard, {
      props: {
        kind: 'artist',
        title: 'Outsider',
        state: 'snoozed',
        onRequest,
        onUnsnooze
      }
    });
    expect(screen.queryByRole('button', { name: /request outsider/i })).not.toBeInTheDocument();
    const status = screen.getByRole('status');
    expect(status.textContent).toMatch(/not right now/i);
    await fireEvent.click(screen.getByRole('button', { name: /bring outsider back/i }));
    expect(onUnsnooze).toHaveBeenCalledOnce();
    expect(onRequest).not.toHaveBeenCalled();
  });
});
