/**
 * Das Farbschema: führende Quelle und Zwischenspeicher.
 *
 * Die Wahl liegt in `einstellungen.json` – wie jede andere Einstellung. Der
 * `localStorage` ist ein **Zwischenspeicher**, kein zweiter Ablageort: Er
 * liefert den Startwert für den Augenblick, in dem der asynchrone
 * IPC-Aufruf noch unterwegs ist. Ohne ihn zeichnete die Anwendung eine Runde
 * lang das Standardthema und spränge dann um – ein Aufblitzen genau bei
 * denen, die auf ein blendarmes oder kontrastreiches Thema angewiesen sind.
 *
 * Damit der Zwischenspeicher nie gegen die Datei steht, gilt eine Regel: Er
 * wird nur geschrieben, wenn die Datei den Wert hat. Diese Datei prüft beide
 * Hälften – dass er den Start trägt, und dass er der Datei folgt statt ihr
 * vorauszueilen.
 */

import { act, renderHook, waitFor } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { useThema } from '../src/renderer/src/hooks/useThema';
import { EINSTELLUNGEN_STANDARD, type Einstellungen } from '../src/shared/ipc';
import type { ThemeAuswahl } from '../src/shared/theme';

const SCHLUESSEL = 'waffensachkunde.thema';

/** Bildet den Anwendungskern nach; `schreibfehler` stellt einen vollen Datenträger nach. */
function kern(
  gespeichert: ThemeAuswahl,
  { schreibfehler = false }: { schreibfehler?: boolean } = {},
): { readonly geschrieben: Partial<Einstellungen>[] } {
  let stand: Einstellungen = { ...EINSTELLUNGEN_STANDARD, thema: gespeichert };
  const geschrieben: Partial<Einstellungen>[] = [];

  vi.stubGlobal('lernApp', {
    einstellungenLesen: vi.fn(() => Promise.resolve(stand)),
    einstellungenSchreiben: vi.fn((aenderung: Partial<Einstellungen>) => {
      if (schreibfehler) {
        return Promise.reject(new Error('Die Einstellungen konnten nicht gespeichert werden.'));
      }
      geschrieben.push(aenderung);
      stand = { ...stand, ...aenderung };
      return Promise.resolve(stand);
    }),
  });

  return { geschrieben };
}

/** Was gerade wirklich gezeichnet wird. */
function gezeichnet(): string | undefined {
  return document.documentElement.dataset['thema'];
}

afterEach(() => {
  vi.unstubAllGlobals();
});

describe('Farbschema beim Start', () => {
  it('liegt schon in der ersten Renderrunde an – ohne Aufblitzen', () => {
    /*
      Der entscheidende Test: NICHT abgewartet. Die Antwort des
      Anwendungskerns steht zu diesem Zeitpunkt noch aus; was hier am
      Wurzelelement steht, ist das, was der Nutzer als Erstes sieht.
    */
    window.localStorage.setItem(SCHLUESSEL, 'dunkel');
    kern('dunkel');

    const { result } = renderHook(() => useThema());

    expect(result.current.auswahl).toBe('dunkel');
    expect(gezeichnet()).toBe('dunkel');
  });

  it('zeichnet ohne Zwischenspeicher zuerst die Vorgabe – die Lücke, die er schließt', () => {
    /* Die Gegenprobe zum Test darüber: Ohne den Startwert steht in der
       ersten Runde die Systemvorgabe (hier hell, siehe matchMedia-Attrappe)
       und springt danach um. Genau dieses Umspringen verhindert der
       Zwischenspeicher. */
    kern('dunkel');

    const { result } = renderHook(() => useThema());
    expect(gezeichnet()).toBe('hell');

    return waitFor(() => {
      expect(result.current.auswahl).toBe('dunkel');
      expect(gezeichnet()).toBe('dunkel');
    });
  });

  it('führt den Zwischenspeicher der Datei nach', async () => {
    /* Damit derselbe Start nur einmal misslingt: Nach dem ersten Lauf trägt
       der Zwischenspeicher, was in der Datei steht – auch dann, wenn diese
       von woanders kommt (übernommenes Benutzerverzeichnis, von Hand
       bearbeitet). */
    kern('hochkontrast');

    const erster = renderHook(() => useThema());
    await waitFor(() => {
      expect(window.localStorage.getItem(SCHLUESSEL)).toBe('hochkontrast');
    });
    erster.unmount();

    // Neustart: jetzt trägt der Zwischenspeicher die erste Runde.
    const { result } = renderHook(() => useThema());
    expect(result.current.auswahl).toBe('hochkontrast');
    expect(gezeichnet()).toBe('hochkontrast');
  });
});

describe('Farbschema umstellen', () => {
  it('schreibt in die Einstellungen und danach in den Zwischenspeicher', async () => {
    const { geschrieben } = kern('system');
    const { result } = renderHook(() => useThema());

    act(() => {
      result.current.auswahlSetzen('dunkel');
    });

    // Sofort wirksam – unabhängig davon, ob das Sichern schon gelungen ist.
    expect(gezeichnet()).toBe('dunkel');
    await waitFor(() => {
      expect(geschrieben).toContainEqual({ thema: 'dunkel' });
    });
    await waitFor(() => {
      expect(window.localStorage.getItem(SCHLUESSEL)).toBe('dunkel');
    });
  });

  it('vergiftet den Zwischenspeicher nicht, wenn das Sichern scheitert', async () => {
    /*
      Der Fall, der die Regel trägt: Läge die neue Wahl im Zwischenspeicher,
      obwohl die Datei sie nicht hat, zeichnete der nächste Start zuerst das
      neue Thema und spränge dann auf das alte zurück – ein Aufblitzen,
      erzeugt von dem Speicher, der es verhindern soll. Und das ausgerechnet
      bei jemandem, dem gerade gesagt wurde, dass nichts gesichert wurde.
    */
    vi.spyOn(console, 'warn').mockImplementation(() => undefined);
    window.localStorage.setItem(SCHLUESSEL, 'hell');
    kern('hell', { schreibfehler: true });
    const { result } = renderHook(() => useThema());

    act(() => {
      result.current.auswahlSetzen('dunkel');
    });

    expect(gezeichnet()).toBe('dunkel');
    await waitFor(() => {
      expect(result.current.ansage).toMatch(/nicht dauerhaft gespeichert/u);
    });
    expect(window.localStorage.getItem(SCHLUESSEL)).toBe('hell');
  });

  it('behält den Zwischenspeicher, wenn es gar keinen Anwendungskern gibt', () => {
    /* Ohne Preload (Vorschau im Browser) gibt es keine Datei, die führen
       könnte – dann ist der Zwischenspeicher alles, was bleibt. */
    const { result } = renderHook(() => useThema());

    act(() => {
      result.current.auswahlSetzen('hochkontrast');
    });

    expect(window.localStorage.getItem(SCHLUESSEL)).toBe('hochkontrast');
  });
});
