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

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

const groups = [{ id: 2, name: 'Prime', decoCount: 1 }];
const decoList = [{ id: 4, decoGroupId: 2, decoGroupName: 'Prime', name: 'Movie Night' }];
const watermarks = [{ id: 1, name: 'Logo' }];
const playlistGroups = [{ id: 3, name: 'Idents', playlistCount: 1, isSystem: false }];
const playlists = [{ id: 9, playlistGroupId: 3, name: 'Idents Playlist', isSystem: false }];

function fullDeco(overrides: Record<string, unknown> = {}) {
  return {
    id: 4,
    decoGroupId: 2,
    decoGroupName: 'Prime',
    name: 'Movie Night',
    watermarkMode: 'Override',
    watermarkIds: [1],
    useWatermarkDuringFiller: true,
    graphicsElementsMode: 'Inherit',
    graphicsElementIds: [],
    useGraphicsElementsDuringFiller: false,
    breakContentMode: 'Override',
    breakContent: [
      {
        id: 7,
        collectionType: 'Playlist',
        collectionId: null,
        mediaItemId: null,
        multiCollectionId: null,
        smartCollectionId: null,
        playlistId: 9,
        playlistGroupId: 3,
        selectionName: 'Idents Playlist',
        placement: 'BlockStart'
      }
    ],
    defaultFillerMode: 'Override',
    defaultFillerCollectionType: 'Collection',
    defaultFillerCollectionId: 5,
    defaultFillerMediaItemId: null,
    defaultFillerMultiCollectionId: null,
    defaultFillerSmartCollectionId: null,
    defaultFillerSelectionName: 'Shorts',
    defaultFillerTrimToFit: true,
    deadAirFallbackMode: 'Inherit',
    deadAirFallbackCollectionType: 'Collection',
    deadAirFallbackCollectionId: null,
    deadAirFallbackMediaItemId: null,
    deadAirFallbackMultiCollectionId: null,
    deadAirFallbackSmartCollectionId: null,
    deadAirFallbackSelectionName: null,
    ...overrides
  };
}

interface MockOptions {
  deco?: Record<string, unknown>;
}

function mockApi(options: MockOptions = {}) {
  const deco = fullDeco(options.deco);

  return vi.spyOn(window, 'fetch').mockImplementation((input: RequestInfo | URL, init?: RequestInit) => {
    const url = input.toString();
    const method = (init?.method ?? 'GET').toUpperCase();

    if (url === '/api/v1/decos/groups' && method === 'GET') {
      return Promise.resolve(jsonResponse(groups));
    }
    if (url === '/api/v1/decos' && method === 'GET') {
      return Promise.resolve(jsonResponse(decoList));
    }
    if (url === '/api/v1/decos/4' && method === 'GET') {
      return Promise.resolve(jsonResponse(deco));
    }
    if (url === '/api/v1/decos/4' && method === 'PUT') {
      return Promise.resolve(jsonResponse(deco));
    }
    if (url === '/api/v1/watermarks' && method === 'GET') {
      return Promise.resolve(jsonResponse(watermarks));
    }
    if (url === '/api/v1/graphics-elements' && method === 'GET') {
      return Promise.resolve(jsonResponse([]));
    }
    if (url === '/api/v1/playlists/groups' && method === 'GET') {
      return Promise.resolve(jsonResponse(playlistGroups));
    }
    if (url.startsWith('/api/v1/playlists?') && method === 'GET') {
      return Promise.resolve(jsonResponse(playlists));
    }

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

describe('DecosScreen', () => {
  afterEach(() => {
    cleanup();
    vi.restoreAllMocks();
    window.history.pushState({}, '', '/app/decos');
  });

  beforeEach(() => {
    window.localStorage.clear();
    window.history.pushState({}, '', '/app/decos');
  });

  it('renders deco groups and their decos', async () => {
    mockApi();
    render(<DecosScreen />);

    expect(await screen.findByText('Prime')).toBeInTheDocument();
    expect(screen.getByText('Movie Night')).toBeInTheDocument();
  });

  it('loads the editor with all six sections from a full deco', async () => {
    window.history.pushState({}, '', '/app/decos/4');
    mockApi();
    render(<DecosScreen />);

    await waitFor(() => expect(screen.getByDisplayValue('Movie Night')).toBeInTheDocument());

    // Each of the six sections renders its (uniquely-labelled) mode row.
    expect(screen.getByText('Watermark mode')).toBeInTheDocument();
    expect(screen.getByText('Graphics elements mode')).toBeInTheDocument();
    expect(screen.getByText('Break content mode')).toBeInTheDocument();
    expect(screen.getByText('Default filler mode')).toBeInTheDocument();
    expect(screen.getByText('Dead air fallback mode')).toBeInTheDocument();

    // Break content (Override) renders its row with the playlist selection and placement.
    expect(screen.getByRole('cell', { name: 'Idents Playlist' })).toBeInTheDocument();
    expect(screen.getByRole('cell', { name: 'Block Start' })).toBeInTheDocument();

    // Default filler (Override) exposes its picker with the current selection.
    expect(screen.getByText('Shorts')).toBeInTheDocument();
  });

  it('reveals section detail only when the mode is Override', async () => {
    window.history.pushState({}, '', '/app/decos/4');
    mockApi();
    const { unmount } = render(<DecosScreen />);

    await waitFor(() => expect(screen.getByDisplayValue('Movie Night')).toBeInTheDocument());

    // Break content Override -> Add button shown; default filler Override -> Trim to fit shown.
    expect(screen.getByRole('button', { name: /Add break content/ })).toBeInTheDocument();
    expect(screen.getByText('Trim to fit')).toBeInTheDocument();
    // Dead air fallback is Inherit, so only the default-filler collection-type row exists.
    expect(screen.getAllByText('Collection type')).toHaveLength(1);

    unmount();

    // With break content Disable + default filler Inherit, neither detail renders.
    mockApi({ deco: { breakContentMode: 'Disable', defaultFillerMode: 'Inherit' } });
    render(<DecosScreen />);
    await waitFor(() => expect(screen.getByDisplayValue('Movie Night')).toBeInTheDocument());
    expect(screen.queryByRole('button', { name: /Add break content/ })).not.toBeInTheDocument();
    expect(screen.queryByText('Trim to fit')).not.toBeInTheDocument();
  });

  it('saves the full deco state via PUT', async () => {
    window.history.pushState({}, '', '/app/decos/4');
    const fetchMock = mockApi();
    render(<DecosScreen />);

    await waitFor(() => expect(screen.getByDisplayValue('Movie Night')).toBeInTheDocument());
    fireEvent.click(screen.getByRole('button', { name: /Save deco/ }));

    await waitFor(() => {
      const call = fetchMock.mock.calls.find(([u, init]) => u === '/api/v1/decos/4' && init?.method === 'PUT');
      expect(call).toBeTruthy();
    });

    const putCall = fetchMock.mock.calls.find(([u, init]) => u === '/api/v1/decos/4' && init?.method === 'PUT');
    const sent = JSON.parse(String(putCall?.[1]?.body));
    expect(sent.name).toBe('Movie Night');
    expect(sent.watermarkMode).toBe('Override');
    expect(sent.watermarkIds).toEqual([1]);
    expect(sent.graphicsElementsMode).toBe('Inherit');
    expect(sent.breakContentMode).toBe('Override');
    expect(sent.breakContent).toHaveLength(1);
    expect(sent.breakContent[0].playlistId).toBe(9);
    expect(sent.breakContent[0].placement).toBe('BlockStart');
    expect(sent.defaultFillerMode).toBe('Override');
    expect(sent.defaultFillerCollectionId).toBe(5);
    expect(sent.defaultFillerTrimToFit).toBe(true);
    expect(sent.deadAirFallbackMode).toBe('Inherit');
  });

  it('assigns a distinct placement to each added break content row', async () => {
    window.history.pushState({}, '', '/app/decos/4');
    mockApi();
    render(<DecosScreen />);

    await waitFor(() => expect(screen.getByDisplayValue('Movie Night')).toBeInTheDocument());

    // Existing row uses Block Start; adding one takes the next unused placement (Block Finish).
    fireEvent.click(screen.getByRole('button', { name: /Add break content/ }));
    expect(await screen.findByRole('cell', { name: 'Block Finish' })).toBeInTheDocument();
  });

  it('blocks save when two break content items share a placement', async () => {
    window.history.pushState({}, '', '/app/decos/4');
    mockApi({
      deco: {
        breakContent: [
          {
            id: 7,
            collectionType: 'Playlist',
            collectionId: null,
            mediaItemId: null,
            multiCollectionId: null,
            smartCollectionId: null,
            playlistId: 9,
            playlistGroupId: 3,
            selectionName: 'Idents Playlist',
            placement: 'BlockStart'
          },
          {
            id: 8,
            collectionType: 'Playlist',
            collectionId: null,
            mediaItemId: null,
            multiCollectionId: null,
            smartCollectionId: null,
            playlistId: 9,
            playlistGroupId: 3,
            selectionName: 'Idents Playlist',
            placement: 'BlockStart'
          }
        ]
      }
    });
    render(<DecosScreen />);

    await waitFor(() => expect(screen.getByDisplayValue('Movie Night')).toBeInTheDocument());

    expect(await screen.findByText(/placement may be used only once/)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Save deco/ })).toBeDisabled();
  });
});
