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

describe('Settings screen (#93)', () => {
  afterEach(() => {
    cleanup();
    vi.useRealTimers();
    vi.restoreAllMocks();
    window.history.replaceState(null, '', '/');
  });

  beforeEach(() => {
    window.localStorage.clear();
    document.documentElement.removeAttribute('data-theme');
    window.history.replaceState(null, '', '/app/settings/general');
    vi.restoreAllMocks();
  });

  const openSettings = async () => {
    render(<SettingsScreen />);
    expect(
      await screen.findByText('Interface preferences. These apply to the legacy web UI; ChicoryTV theming lives in the theme switcher.')
    ).toBeInTheDocument();
  };

  it('renders each section on nav click', async () => {
    mockSettingsApi();
    render(<SettingsScreen />);

    expect(await screen.findByText('Interface preferences. These apply to the legacy web UI; ChicoryTV theming lives in the theme switcher.')).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: /Streaming/ }));
    expect(await screen.findByText('FFmpeg engine, transcoding defaults and HLS session tuning.')).toBeInTheDocument();
    expect(window.location.pathname).toBe('/app/settings/streaming');

    fireEvent.click(screen.getByRole('button', { name: /Playout/ }));
    expect(await screen.findByText('Defaults for how far ahead playouts are built and how gaps are handled.')).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: /Guide \(XMLTV\)/ }));
    expect(await screen.findByText('Shape of the XMLTV guide data served to Jellyfin and other clients.')).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: /Scanner/ }));
    expect(await screen.findByText('Background scanning of local libraries.')).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: /Logging/ }));
    expect(await screen.findByText('Minimum level written per category. Verbose and Debug are noisy — use for troubleshooting only.')).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: /^System/ }));
    expect(await screen.findByText('HDHomeRun emulation, connected media sources and server info.')).toBeInTheDocument();
  });

  it('supports deep-linking directly to a section path', async () => {
    mockSettingsApi();
    window.history.replaceState(null, '', '/app/settings/scanner');
    render(<SettingsScreen />);

    expect(await screen.findByText('Background scanning of local libraries.')).toBeInTheDocument();
  });

  it('loads values from the API into the General pane', async () => {
    mockSettingsApi({ uiSettings: defaultUiSettings({ isDarkMode: false, language: 'fr' }) });
    await openSettings();

    expect(await screen.findByDisplayValue('Light')).toBeInTheDocument();
    expect(screen.getByDisplayValue('fr')).toBeInTheDocument();
  });

  it('shows the save bar with the correct dirty count when editing a field, and Discard restores', async () => {
    mockSettingsApi();
    await openSettings();

    const languageInput = screen.getByPlaceholderText('en-US');
    fireEvent.change(languageInput, { target: { value: 'de-DE' } });

    expect(await screen.findByText('1 unsaved change')).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: 'Discard' }));

    expect(screen.queryByText('1 unsaved change')).not.toBeInTheDocument();
    expect(screen.getByDisplayValue('en-US')).toBeInTheDocument();
  });

  it('saves only the changed group(s) and shows a confirmation', async () => {
    vi.useFakeTimers({ shouldAdvanceTime: true });
    mockSettingsApi();
    await openSettings();

    const languageInput = screen.getByPlaceholderText('en-US');
    fireEvent.change(languageInput, { target: { value: 'de-DE' } });

    await screen.findByText('1 unsaved change');

    fireEvent.click(screen.getByRole('button', { name: /Save changes/ }));

    expect(await screen.findByText('Settings saved')).toBeInTheDocument();
    expect(requestBodyFor('/api/v1/settings/ui')).toMatchObject({ language: 'de-DE' });
    expect(window.fetch).not.toHaveBeenCalledWith('/api/v1/settings/ffmpeg', expect.objectContaining({ method: 'PUT' }));

    vi.advanceTimersByTime(2000);
    await waitFor(() => expect(screen.queryByText('Settings saved')).not.toBeInTheDocument());
  });

  it('renders the IPTV pane and saves an edited advertised base URL', async () => {
    mockSettingsApi();
    await openSettings();
    fireEvent.click(await screen.findByRole('button', { name: /^IPTV/ }));

    expect(
      await screen.findByText('How ErsatzTV advertises itself to IPTV clients in M3U playlists and stream URLs.')
    ).toBeInTheDocument();

    const baseUrlInput = screen.getByPlaceholderText('(use request origin)');
    fireEvent.change(baseUrlInput, { target: { value: 'https://tv.example.com/etv' } });

    await screen.findByText('1 unsaved change');
    fireEvent.click(screen.getByRole('button', { name: /Save changes/ }));

    expect(await screen.findByText('Settings saved')).toBeInTheDocument();
    expect(requestBodyFor('/api/v1/settings/iptv')).toMatchObject({ baseUrl: 'https://tv.example.com/etv' });
  });

  it('shows a warning callout when scanner refresh interval is 0', async () => {
    mockSettingsApi({ scannerSettings: defaultScannerSettings({ libraryRefreshInterval: 0 }) });
    await openSettings();
    fireEvent.click(await screen.findByRole('button', { name: /Scanner/ }));

    expect(await screen.findByText('Automatic scanning is disabled — libraries only update when scanned manually.')).toBeInTheDocument();
  });

  it('adds and deletes a custom resolution (delete goes through a confirm dialog)', async () => {
    mockSettingsApi();
    await openSettings();
    fireEvent.click(await screen.findByRole('button', { name: /Streaming/ }));
    await screen.findByText('Custom resolutions');

    fireEvent.change(screen.getByPlaceholderText('width'), { target: { value: '2560' } });
    fireEvent.change(screen.getByPlaceholderText('height'), { target: { value: '1080' } });
    fireEvent.click(screen.getByRole('button', { name: 'Add resolution' }));

    expect(await screen.findByText('2560 × 1080')).toBeInTheDocument();
    expect(requestBodyFor('/api/v1/settings/resolutions')).toMatchObject({ height: 1080, width: 2560 });

    const deleteButtons = screen.getAllByRole('button', { name: /Delete \d+×\d+/ });
    fireEvent.click(deleteButtons[deleteButtons.length - 1]);

    expect(await screen.findByRole('dialog')).toBeInTheDocument();
    fireEvent.click(screen.getByRole('button', { name: 'Delete' }));

    await waitFor(() => expect(screen.queryByText('2560 × 1080')).not.toBeInTheDocument());
  });

  it('does not show a delete button on non-custom resolutions', async () => {
    mockSettingsApi();
    await openSettings();
    fireEvent.click(await screen.findByRole('button', { name: /Streaming/ }));

    await screen.findByText('1920 × 1080');
    expect(screen.queryByRole('button', { name: 'Delete 1920×1080' })).not.toBeInTheDocument();
  });

  it('renders FFmpeg profiles read-only with a link to the FFmpeg Profiles editor, and media sources', async () => {
    mockSettingsApi({
      ffmpegProfiles: [ffmpegProfile({ id: 1, name: '1080p H.264' }), ffmpegProfile({ id: 2, name: '720p H.264' })],
      mediaSources: [mediaSource({ id: 30, kind: 'Local', name: 'Local' })]
    });
    await openSettings();
    fireEvent.click(await screen.findByRole('button', { name: /Streaming/ }));

    expect((await screen.findAllByText('1080p H.264')).length).toBeGreaterThan(0);
    expect(screen.getAllByText('720p H.264').length).toBeGreaterThan(0);
    expect(screen.getByRole('button', { name: /Manage FFmpeg profiles/ })).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: /^System/ }));
    expect(await screen.findByText('Local')).toBeInTheDocument();
  });

  it('no longer shows the dead Classic UI link (#147 removed by #164; Blazor gone since #91b)', async () => {
    // The legacy Blazor UI was removed in #91b and /system/health now 302s to /app, so the
    // old "Open Classic UI" escape hatch was a dead link; blocks/decos/templates/playouts all
    // live in the SPA now. The row was removed rather than repointed.
    mockSettingsApi();
    await openSettings();
    fireEvent.click(await screen.findByRole('button', { name: /^System/ }));

    // the About card's "Open Dashboard" button proves the pane rendered
    expect(await screen.findByRole('button', { name: /Open Dashboard/ })).toBeInTheDocument();
    expect(screen.queryByRole('link', { name: /Open Classic UI/ })).not.toBeInTheDocument();
  });

  it('renders and stays editable when media sources (tier-2 reference data) fail to load', async () => {
    mockSettingsApi({ mediaSourcesFailuresBeforeSuccess: 99 });
    await openSettings();

    fireEvent.click(await screen.findByRole('button', { name: /^System/ }));
    expect(await screen.findByText("Couldn't load media sources")).toBeInTheDocument();

    // The rest of the screen is fully functional: edit + save still work.
    fireEvent.click(screen.getByRole('button', { name: /General/ }));
    fireEvent.change(await screen.findByPlaceholderText('en-US'), { target: { value: 'nl-BE' } });
    await screen.findByText('1 unsaved change');
    fireEvent.click(screen.getByRole('button', { name: /Save changes/ }));

    expect(await screen.findByText('Settings saved')).toBeInTheDocument();
    expect(requestBodyFor('/api/v1/settings/ui')).toMatchObject({ language: 'nl-BE' });
  });

  it('shows the error state with a working retry when a settings group (tier-1) fails', async () => {
    mockSettingsApi({ settingsGetFailuresBeforeSuccess: { '/api/v1/settings/scanner': 1 } });
    render(<SettingsScreen />);

    const alert = await screen.findByRole('alert');
    expect(alert).toBeInTheDocument();
    expect(screen.queryByText('Loading settings…')).not.toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: 'Retry' }));

    expect(
      await screen.findByText('Interface preferences. These apply to the legacy web UI; ChicoryTV theming lives in the theme switcher.')
    ).toBeInTheDocument();
  });

  it('saves exactly the dirty groups when multiple groups are edited', async () => {
    mockSettingsApi();
    await openSettings();

    fireEvent.change(screen.getByPlaceholderText('en-US'), { target: { value: 'de-DE' } });

    fireEvent.click(screen.getByRole('button', { name: /Playout/ }));
    await screen.findByText('Defaults for how far ahead playouts are built and how gaps are handled.');
    fireEvent.change(screen.getByDisplayValue('2'), { target: { value: '5' } });

    await screen.findByText('2 unsaved changes');
    fireEvent.click(screen.getByRole('button', { name: /Save changes/ }));

    expect(await screen.findByText('Settings saved')).toBeInTheDocument();
    expect(requestBodyFor('/api/v1/settings/ui')).toMatchObject({ language: 'de-DE' });
    expect(requestBodyFor('/api/v1/settings/playout')).toMatchObject({ daysToBuild: 5 });

    const putCallCount = (path: string) =>
      vi.mocked(window.fetch).mock.calls.filter(([input, init]) => input.toString() === path && init?.method === 'PUT')
        .length;

    expect(putCallCount('/api/v1/settings/ui')).toBe(1);
    expect(putCallCount('/api/v1/settings/playout')).toBe(1);
    expect(putCallCount('/api/v1/settings/ffmpeg')).toBe(0);
    expect(putCallCount('/api/v1/settings/xmltv')).toBe(0);
    expect(putCallCount('/api/v1/settings/scanner')).toBe(0);
    expect(putCallCount('/api/v1/settings/logging')).toBe(0);
    expect(putCallCount('/api/v1/settings/hdhr')).toBe(0);
  });

  it('on partial save failure, keeps only the failed group dirty and surfaces its error', async () => {
    mockSettingsApi({
      settingsMutationFailures: { '/api/v1/settings/playout': { detail: 'Playout save failed', status: 500 } }
    });
    await openSettings();

    fireEvent.change(screen.getByPlaceholderText('en-US'), { target: { value: 'de-DE' } });

    fireEvent.click(screen.getByRole('button', { name: /Playout/ }));
    await screen.findByText('Defaults for how far ahead playouts are built and how gaps are handled.');
    fireEvent.change(screen.getByDisplayValue('2'), { target: { value: '5' } });

    await screen.findByText('2 unsaved changes');
    fireEvent.click(screen.getByRole('button', { name: /Save changes/ }));

    expect(await screen.findByText('1 unsaved change')).toBeInTheDocument();
    expect(screen.getByText(/Playout save failed/)).toBeInTheDocument();
    expect(screen.queryByText('Settings saved')).not.toBeInTheDocument();

    // The succeeded (ui) group's edit stuck even though the draft stayed on the Playout pane.
    fireEvent.click(screen.getByRole('button', { name: /General/ }));
    expect(await screen.findByDisplayValue('de-DE')).toBeInTheDocument();
  });

  it('surfaces an inline error (no unhandled rejection) when adding a duplicate resolution fails', async () => {
    mockSettingsApi({ resolutionCreateFailure: { detail: 'Resolution already exists', status: 422 } });
    await openSettings();
    fireEvent.click(await screen.findByRole('button', { name: /Streaming/ }));
    await screen.findByText('Custom resolutions');

    fireEvent.change(screen.getByPlaceholderText('width'), { target: { value: '2560' } });
    fireEvent.change(screen.getByPlaceholderText('height'), { target: { value: '1080' } });
    fireEvent.click(screen.getByRole('button', { name: 'Add resolution' }));

    expect(await screen.findByText('Resolution already exists')).toBeInTheDocument();
  });

  it('surfaces a resolution-delete failure inside the still-open confirm dialog', async () => {
    mockSettingsApi({ resolutionDeleteFailure: { detail: 'Resolution is in use', status: 409 } });
    await openSettings();
    fireEvent.click(await screen.findByRole('button', { name: /Streaming/ }));
    await screen.findByText('1920 × 1080');

    fireEvent.change(screen.getByPlaceholderText('width'), { target: { value: '2560' } });
    fireEvent.change(screen.getByPlaceholderText('height'), { target: { value: '1080' } });
    fireEvent.click(screen.getByRole('button', { name: 'Add resolution' }));
    await screen.findByText('2560 × 1080');

    const deleteButtons = screen.getAllByRole('button', { name: /Delete \d+×\d+/ });
    fireEvent.click(deleteButtons[deleteButtons.length - 1]);

    const dialog = await screen.findByRole('dialog');
    fireEvent.click(within(dialog).getByRole('button', { name: 'Delete' }));

    expect(await within(dialog).findByText('Resolution is in use')).toBeInTheDocument();
    // The dialog stays open with the resolution still present - a clean draft would
    // otherwise show nothing at all, since saveError only renders inside the save bar.
    expect(screen.getByText('2560 × 1080')).toBeInTheDocument();
  });

  it('disables Save and shows an inline error when a numeric field is cleared', async () => {
    mockSettingsApi();
    await openSettings();

    fireEvent.click(screen.getByRole('button', { name: /Playout/ }));
    await screen.findByText('Defaults for how far ahead playouts are built and how gaps are handled.');
    fireEvent.change(screen.getByDisplayValue('2'), { target: { value: '' } });

    await screen.findByText('1 unsaved change');
    expect(screen.getByText('Must be a whole number ≥ 0')).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Save changes/ })).toBeDisabled();
  });

  it('treats a tunerCount of 0 as invalid (backend rejects 0)', async () => {
    mockSettingsApi();
    await openSettings();

    fireEvent.click(screen.getByRole('button', { name: /^System/ }));
    await screen.findByText('HDHomeRun emulation, connected media sources and server info.');
    fireEvent.change(screen.getByDisplayValue('2'), { target: { value: '0' } });

    await screen.findByText('1 unsaved change');
    expect(screen.getByText('Must be a whole number ≥ 1')).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Save changes/ })).toBeDisabled();
  });

  it('shows the raw wire value as an extra HLS Direct option when it is outside the known set', async () => {
    mockSettingsApi({ ffmpegSettings: defaultFfmpegSettings({ hlsDirectOutputFormat: 'Hls' }) });
    await openSettings();
    fireEvent.click(await screen.findByRole('button', { name: /Streaming/ }));

    expect(await screen.findByDisplayValue('Hls')).toBeInTheDocument();
  });

  it('sends a non-null globalWatermarkId through in the ffmpeg save PUT body', async () => {
    mockSettingsApi({ watermarks: [{ id: 5, name: 'Bug' }] });
    await openSettings();
    fireEvent.click(await screen.findByRole('button', { name: /Streaming/ }));
    await screen.findByText('Global defaults');

    const watermarkSelect = screen
      .getAllByRole('combobox')
      .find((select) => within(select).queryByText('Bug')) as HTMLSelectElement;

    fireEvent.change(watermarkSelect, { target: { value: '5' } });

    await screen.findByText('1 unsaved change');
    fireEvent.click(screen.getByRole('button', { name: /Save changes/ }));

    expect(await screen.findByText('Settings saved')).toBeInTheDocument();
    expect(requestBodyFor('/api/v1/settings/ffmpeg')).toMatchObject({ globalWatermarkId: 5 });
  });

  it('renders the most recent last-scan time per media source', async () => {
    mockSettingsApi({
      mediaSources: [
        mediaSource({
          id: 30,
          kind: 'Local',
          libraries: [
            library({ id: 31, lastScan: '2026-07-01T00:00:00Z' }),
            library({ id: 32, lastScan: '2026-07-05T14:30:00Z' })
          ],
          name: 'Local'
        }),
        mediaSource({ id: 40, kind: 'Jellyfin', libraries: [], name: 'Jellyfin Server' })
      ]
    });
    await openSettings();
    fireEvent.click(await screen.findByRole('button', { name: /^System/ }));

    expect(await screen.findByText('Local')).toBeInTheDocument();
    expect(await screen.findByText('Jellyfin Server')).toBeInTheDocument();
    expect(screen.getByText(/Last scan/)).toBeInTheDocument();
  });
});

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

function library(overrides: Record<string, unknown> = {}): Record<string, unknown> {
  return {
    id: 31,
    itemCount: 0,
    lastScan: null,
    mediaKind: 'Movies',
    name: 'Movies',
    ...overrides
  };
}

function mediaSource(overrides: Record<string, unknown> = {}): Record<string, unknown> {
  return {
    connectionAddress: null,
    id: 30,
    kind: 'Local',
    libraries: [],
    name: 'Local',
    ...overrides
  };
}

function ffmpegProfile(overrides: Record<string, unknown> = {}): Record<string, unknown> {
  return { id: 100, name: '1080p H.264', ...overrides };
}

// Read the JSON body captured for a mutating request to `path`.
function requestBodyFor(path: string): Record<string, unknown> {
  const call = vi
    .mocked(window.fetch)
    .mock.calls.find(([input, init]) => input.toString() === path && init?.body != null);
  if (!call) {
    throw new Error(`no request captured for ${path}`);
  }
  return JSON.parse(call[1]?.body as string) as Record<string, unknown>;
}

function defaultFfmpegSettings(overrides: Record<string, unknown> = {}): Record<string, unknown> {
  return {
    defaultFFmpegProfileId: 1,
    defaultMpegTsScript: '',
    extractEmbeddedSubtitles: false,
    fFmpegPath: '/usr/bin/ffmpeg',
    fFprobePath: '/usr/bin/ffprobe',
    globalFallbackFillerId: null,
    globalWatermarkId: null,
    hlsDirectOutputFormat: 'MpegTs',
    hlsSegmenterIdleTimeout: 60,
    initialSegmentCount: 1,
    preferredAudioLanguageCode: 'eng',
    probeForInterlacedFrames: true,
    saveReports: false,
    useEmbeddedSubtitles: true,
    workAheadSegmenterLimit: 1,
    ...overrides
  };
}

function defaultPlayoutSettings(overrides: Record<string, unknown> = {}): Record<string, unknown> {
  return {
    daysToBuild: 2,
    scriptedScheduleTimeoutSeconds: 30,
    skipMissingItems: true,
    ...overrides
  };
}

function defaultXmltvSettings(overrides: Record<string, unknown> = {}): Record<string, unknown> {
  return {
    blockBehavior: 'SplitTimeEvenly',
    daysToBuild: 2,
    timeZone: 'Local',
    ...overrides
  };
}

function defaultScannerSettings(overrides: Record<string, unknown> = {}): Record<string, unknown> {
  return {
    libraryRefreshInterval: 6,
    ...overrides
  };
}

function defaultLoggingSettings(overrides: Record<string, unknown> = {}): Record<string, unknown> {
  return {
    defaultMinimumLogLevel: 'Information',
    httpMinimumLogLevel: 'Warning',
    scanningMinimumLogLevel: 'Information',
    schedulingMinimumLogLevel: 'Information',
    searchingMinimumLogLevel: 'Information',
    streamingMinimumLogLevel: 'Information',
    ...overrides
  };
}

function defaultUiSettings(overrides: Record<string, unknown> = {}): Record<string, unknown> {
  return {
    isDarkMode: true,
    language: 'en-US',
    ...overrides
  };
}

function defaultHdhrSettings(overrides: Record<string, unknown> = {}): Record<string, unknown> {
  return {
    tunerCount: 2,
    uuid: '6f1b0a2e-93c4-4d1e-b7aa-0e5f2c9d8a41',
    ...overrides
  };
}

function defaultIptvSettings(overrides: Record<string, unknown> = {}): Record<string, unknown> {
  return {
    baseUrl: '',
    ...overrides
  };
}

function defaultResolutions(): Array<Record<string, unknown>> {
  return [
    { height: 1080, id: 1, isCustom: false, name: '1920x1080', width: 1920 },
    { height: 720, id: 2, isCustom: false, name: '1280x720', width: 1280 }
  ];
}

function mockSettingsApi({
  ffmpegProfiles = [],
  ffmpegSettings = defaultFfmpegSettings(),
  hdhrSettings = defaultHdhrSettings(),
  iptvSettings = defaultIptvSettings(),
  loggingSettings = defaultLoggingSettings(),
  mediaSources = [],
  mediaSourcesFailuresBeforeSuccess = 0,
  playoutSettings = defaultPlayoutSettings(),
  resolutionCreateFailure = null,
  resolutionDeleteFailure = null,
  resolutions = defaultResolutions(),
  scannerSettings = defaultScannerSettings(),
  settingsGetFailuresBeforeSuccess = {},
  settingsMutationFailures = {},
  uiSettings = defaultUiSettings(),
  watermarks = [],
  xmltvSettings = defaultXmltvSettings()
}: {
  ffmpegProfiles?: unknown[];
  ffmpegSettings?: Record<string, unknown>;
  hdhrSettings?: Record<string, unknown>;
  iptvSettings?: Record<string, unknown>;
  loggingSettings?: Record<string, unknown>;
  mediaSources?: unknown[];
  mediaSourcesFailuresBeforeSuccess?: number;
  playoutSettings?: Record<string, unknown>;
  resolutionCreateFailure?: { detail?: string; status?: number; title?: string } | null;
  resolutionDeleteFailure?: { detail?: string; status?: number; title?: string } | null;
  resolutions?: Array<Record<string, unknown>>;
  scannerSettings?: Record<string, unknown>;
  settingsGetFailuresBeforeSuccess?: Record<string, number>;
  settingsMutationFailures?: Record<string, { detail?: string; status?: number; title?: string }>;
  uiSettings?: Record<string, unknown>;
  watermarks?: unknown[];
  xmltvSettings?: Record<string, unknown>;
} = {}) {
  let currentResolutions = resolutions;
  let remainingMediaSourcesFailures = mediaSourcesFailuresBeforeSuccess;
  const remainingSettingsGetFailures = { ...settingsGetFailuresBeforeSuccess };
  const currentSettings: Record<string, Record<string, unknown>> = {
    '/api/v1/settings/ffmpeg': ffmpegSettings,
    '/api/v1/settings/hdhr': hdhrSettings,
    '/api/v1/settings/iptv': iptvSettings,
    '/api/v1/settings/logging': loggingSettings,
    '/api/v1/settings/playout': playoutSettings,
    '/api/v1/settings/scanner': scannerSettings,
    '/api/v1/settings/ui': uiSettings,
    '/api/v1/settings/xmltv': xmltvSettings
  };

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

    if (path in currentSettings) {
      if (method === 'GET' && (remainingSettingsGetFailures[path] ?? 0) > 0) {
        remainingSettingsGetFailures[path] -= 1;
        return Promise.resolve(new Response(null, { status: 500 }));
      }

      if (method === 'PUT') {
        const failure = settingsMutationFailures[path];
        if (failure) {
          return Promise.resolve(jsonResponse(failure, failure.status ?? 422));
        }

        currentSettings[path] = {
          ...currentSettings[path],
          ...JSON.parse(String(init?.body ?? '{}')) as Record<string, unknown>
        };
      }

      return Promise.resolve(jsonResponse(currentSettings[path]));
    }

    if (path === '/api/v1/settings/resolutions') {
      if (method === 'POST') {
        if (resolutionCreateFailure) {
          return Promise.resolve(
            jsonResponse(resolutionCreateFailure, resolutionCreateFailure.status ?? 422)
          );
        }

        const body = JSON.parse(String(init?.body ?? '{}')) as { height: number; width: number };
        const created = {
          height: body.height,
          id: Math.max(0, ...currentResolutions.map((resolution) => Number(resolution.id))) + 1,
          isCustom: true,
          name: `${body.width}x${body.height}`,
          width: body.width
        };
        currentResolutions = [...currentResolutions, created];
        return Promise.resolve(jsonResponse(created, 201));
      }

      return Promise.resolve(jsonResponse(currentResolutions));
    }

    if (path.match(/^\/api\/v1\/settings\/resolutions\/\d+$/)) {
      if (resolutionDeleteFailure) {
        return Promise.resolve(
          jsonResponse(resolutionDeleteFailure, resolutionDeleteFailure.status ?? 422)
        );
      }

      const id = Number(path.split('/').at(-1));
      currentResolutions = currentResolutions.filter((resolution) => Number(resolution.id) !== id);
      return Promise.resolve(new Response(null, { status: 204 }));
    }

    if (path === '/api/v1/media-sources') {
      if (remainingMediaSourcesFailures > 0) {
        remainingMediaSourcesFailures -= 1;
        return Promise.resolve(new Response(null, { status: 500 }));
      }

      return Promise.resolve(jsonResponse(mediaSources));
    }

    if (path === '/api/v1/ffmpeg/profiles') {
      return Promise.resolve(jsonResponse(ffmpegProfiles));
    }

    if (path === '/api/v1/watermarks') {
      return Promise.resolve(jsonResponse(watermarks));
    }

    if (path.startsWith('/api/v1/filler-presets')) {
      return Promise.resolve(jsonResponse([]));
    }

    if (path === '/api/v1/version') {
      return Promise.resolve(jsonResponse({ apiVersion: 3, appVersion: '26.4.0' }));
    }

    if (path.startsWith('/api/v1/health')) {
      return Promise.resolve(jsonResponse([]));
    }

    return Promise.resolve(jsonResponse(null, 404));
  });
}
