import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { AddToCollectionDialog } from './AddToCollectionDialog';
import type { LibraryBrowseItem } from '../../api';

function jsonResponse(body: unknown, status = 200): Response {
  return new Response(JSON.stringify(body), { headers: { 'Content-Type': 'application/json' }, status });
}

const collections = [
  { collectionType: 'Collection', id: 1, name: 'Favorites', useCustomPlaybackOrder: false },
  { collectionType: 'Collection', id: 2, name: 'Kids', useCustomPlaybackOrder: false }
];

const movieItem = { artwork: '', id: 12, mediaType: 'Movie', title: 'Blade Runner' } as unknown as LibraryBrowseItem;

const expectedAddBody = {
  artistIds: [],
  episodeIds: [],
  imageIds: [],
  movieIds: [12],
  musicVideoIds: [],
  otherVideoIds: [],
  remoteStreamIds: [],
  seasonIds: [],
  showIds: [],
  songIds: []
};

interface Recorded {
  url: string;
  method: string;
  body: unknown;
}

function mockApi(onCreate?: (body: unknown) => Response) {
  const calls: Recorded[] = [];
  const fetchMock = vi.spyOn(window, 'fetch').mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
    const url = input.toString();
    const method = (init?.method ?? 'GET').toUpperCase();
    const body = init?.body ? JSON.parse(String(init.body)) : undefined;
    calls.push({ body, method, url });

    if (url === '/api/v1/collections' && method === 'GET') {
      return Promise.resolve(jsonResponse(collections));
    }

    if (url === '/api/v1/collections' && method === 'POST') {
      return Promise.resolve(onCreate ? onCreate(body) : jsonResponse({ collectionType: 'Collection', id: 99, name: (body as { name: string }).name, useCustomPlaybackOrder: false }, 201));
    }

    return Promise.resolve(new Response(null, { status: 204 }));
  });

  return { calls, fetchMock };
}

describe('AddToCollectionDialog', () => {
  beforeEach(() => {
    window.localStorage.clear();
    vi.restoreAllMocks();
  });

  afterEach(() => {
    cleanup();
  });

  it('renders nothing when closed', () => {
    mockApi();
    const { container } = render(<AddToCollectionDialog items={[movieItem]} onClose={() => {}} open={false} />);
    expect(container).toBeEmptyDOMElement();
  });

  it('loads existing collections into the select', async () => {
    mockApi();
    render(<AddToCollectionDialog items={[movieItem]} onClose={() => {}} open />);

    await waitFor(() => expect(screen.getByRole('option', { name: 'Favorites' })).toBeTruthy());
    expect(screen.getByRole('option', { name: 'Kids' })).toBeTruthy();
    expect(screen.getByRole('option', { name: '(New collection)' })).toBeTruthy();
  });

  it('adds the items to the selected existing collection', async () => {
    const onAdded = vi.fn();
    const onClose = vi.fn();
    const { calls } = mockApi();
    render(<AddToCollectionDialog items={[movieItem]} onAdded={onAdded} onClose={onClose} open />);

    await waitFor(() => expect(screen.getByRole('option', { name: 'Kids' })).toBeTruthy());
    fireEvent.change(screen.getByRole('combobox'), { target: { value: '2' } });
    fireEvent.click(screen.getByRole('button', { name: /Add to collection/ }));

    await waitFor(() => expect(onClose).toHaveBeenCalled());
    const addCall = calls.find((call) => call.url === '/api/v1/collections/2/items');
    expect(addCall?.method).toBe('POST');
    expect(addCall?.body).toEqual(expectedAddBody);
    expect(onAdded).toHaveBeenCalledWith('Kids');
  });

  it('creates a new collection first, then adds the items to it', async () => {
    const onAdded = vi.fn();
    const { calls } = mockApi();
    render(<AddToCollectionDialog items={[movieItem]} onAdded={onAdded} onClose={() => {}} open />);

    await waitFor(() => expect(screen.getByRole('option', { name: '(New collection)' })).toBeTruthy());
    fireEvent.change(screen.getByRole('combobox'), { target: { value: '__new__' } });
    fireEvent.change(screen.getByPlaceholderText('Collection name'), { target: { value: 'Sci-Fi' } });
    fireEvent.click(screen.getByRole('button', { name: /Add to collection/ }));

    await waitFor(() => expect(onAdded).toHaveBeenCalledWith('Sci-Fi'));
    const createCall = calls.find((call) => call.url === '/api/v1/collections' && call.method === 'POST');
    expect(createCall?.body).toEqual({ name: 'Sci-Fi' });
    const addCall = calls.find((call) => call.url === '/api/v1/collections/99/items');
    expect(addCall?.body).toEqual(expectedAddBody);
  });

  it('supports a request override (query-based add-all) instead of item ids', async () => {
    const override = { ...expectedAddBody, movieIds: [1, 2, 3] };
    const { calls } = mockApi();
    render(<AddToCollectionDialog items={{ requestOverride: override }} onClose={() => {}} open />);

    await waitFor(() => expect(screen.getByRole('option', { name: 'Favorites' })).toBeTruthy());
    fireEvent.click(screen.getByRole('button', { name: /Add to collection/ }));

    await waitFor(() => expect(calls.some((call) => call.url === '/api/v1/collections/1/items')).toBe(true));
    const addCall = calls.find((call) => call.url === '/api/v1/collections/1/items');
    expect(addCall?.body).toEqual(override);
  });
});
