// @vitest-environment node
/**
 * Die Verlustanzeige – was ginge beim Ersetzen verloren?
 *
 * Reine Rechnerei ohne Datenbank. Geprüft wird der Fall, der die Anzeige
 * überhaupt nötig macht: Zwei Gesamtzahlen nebeneinander können „mehr“
 * sagen, während ein ganzes Profil verschwindet.
 */

import { describe, expect, it } from 'vitest';

import type { Kennzahlen, Profilkennzahlen } from '../src/shared/sicherung';
import { OHNE_BEFUND_SATZ, verlustbefund, verlustsaetze } from '../src/shared/verlust';

function profil(
  name: string,
  antworten: number,
  gemerkt = 0,
  pruefungslaeufe = 0,
): Profilkennzahlen {
  return { name, antworten, gemerkt, pruefungslaeufe, letzteAntwort: null };
}

function stand(profile: readonly Profilkennzahlen[], offenerBogen = false): Kennzahlen {
  return {
    profilnamen: profile.map((p) => p.name),
    jeProfil: profile,
    antworten: profile.reduce((summe, p) => summe + p.antworten, 0),
    letzteAntwort: null,
    gemerkt: profile.reduce((summe, p) => summe + p.gemerkt, 0),
    pruefungslaeufe: profile.reduce((summe, p) => summe + p.pruefungslaeufe, 0),
    offenerBogen,
    schemafassung: 8,
  };
}

describe('verlustbefund', () => {
  /*
    Der Fall, der die ganze Anzeige rechtfertigt: Die Datei trägt in der Summe
    MEHR Antworten, und trotzdem geht ein vollständiges Profil verloren. Wer
    nur die beiden Gesamtzahlen sieht, gibt das gutgläubig frei.
  */
  it('erkennt ein verschwindendes Profil, obwohl die Datei mehr Antworten hat', () => {
    const jetzt = stand([profil('Olaf', 600), profil('Anna', 400)]);
    const ausDatei = stand([profil('Olaf', 1200)]);

    expect(ausDatei.antworten).toBeGreaterThan(jetzt.antworten);

    const befund = verlustbefund(jetzt, ausDatei);

    expect(befund.ohneBefund).toBe(false);
    expect(befund.fehlendeProfile).toEqual([{ name: 'Anna', antworten: 400 }]);
    expect(verlustsaetze(befund)[0]).toContain('Das Profil „Anna“ kommt in der Datei nicht vor');
    expect(verlustsaetze(befund)[0]).toContain('400 Antworten');
  });

  it('erkennt ein Profil, das in der Datei weniger enthält', () => {
    const befund = verlustbefund(
      stand([profil('Olaf', 600, 12, 3)]),
      stand([profil('Olaf', 500, 4, 3)]),
    );

    expect(befund.geschrumpfteProfile).toEqual([
      { name: 'Olaf', antwortenWeniger: 100, gemerktWeniger: 8, laeufeWeniger: 0 },
    ]);
    const satz = verlustsaetze(befund)[0] ?? '';
    expect(satz).toContain('100 Antworten');
    expect(satz).toContain('8 gemerkte Fragen');
    expect(satz).not.toContain('Prüfungslauf');
  });

  it('meldet nichts, wenn die Datei überall mehr oder gleich viel enthält', () => {
    const befund = verlustbefund(
      stand([profil('Olaf', 500, 4, 1)]),
      stand([profil('Olaf', 600, 12, 3), profil('Anna', 20)]),
    );

    expect(befund.ohneBefund).toBe(true);
    expect(verlustsaetze(befund)).toEqual([]);
  });

  it('nennt den unterbrochenen Bogen als Verlust', () => {
    const befund = verlustbefund(stand([profil('Olaf', 10)], true), stand([profil('Olaf', 10)]));

    expect(befund.ohneBefund).toBe(false);
    expect(befund.offenerBogenGehtVerloren).toBe(true);
    expect(verlustsaetze(befund)[0]).toContain('unterbrochener Prüfungsbogen');
  });

  it('kennt den Singular', () => {
    const befund = verlustbefund(
      stand([profil('Olaf', 11, 1, 1), profil('Anna', 1)]),
      stand([profil('Olaf', 10)]),
    );
    const saetze = verlustsaetze(befund);

    expect(saetze.some((s) => s.includes('1 Antwort verloren'))).toBe(true);
    expect(saetze.some((s) => s.includes('1 gemerkte Frage'))).toBe(true);
    expect(saetze.some((s) => s.includes('1 Prüfungslauf'))).toBe(true);
    expect(saetze.every((s) => !s.includes('1 Antworten'))).toBe(true);
  });

  it('vergleicht über den Namen, nicht über die Nummer', () => {
    /* Die Profilnummern werden auf jedem Gerät unabhängig vergeben. Ein
       Vergleich über sie führte geradewegs ins Profil einer anderen Person –
       derselbe Grund, aus dem die Oberfläche nach dem Ersetzen zwingend ins
       erste Profil der Datei wechselt. */
    const befund = verlustbefund(stand([profil('Anna', 50)]), stand([profil('Olaf', 50)]));

    expect(befund.fehlendeProfile).toHaveLength(1);
    expect(befund.fehlendeProfile[0]?.name).toBe('Anna');
  });

  /*
    Der Satz für den Fall ohne Befund darf nicht „alles stimmt“ sagen.
    Prüfungstermin und Kapitelabwahl werden mitersetzt und sind nicht
    verglichen; die Kapitelabwahl verschiebt den Nenner des Reifegrads.
  */
  it('sagt im Satz ohne Befund, was verglichen wurde und was nicht', () => {
    expect(OHNE_BEFUND_SATZ).toContain('Verglichen wurden Profile, Antworten');
    expect(OHNE_BEFUND_SATZ).toContain('Nicht verglichen wurden Prüfungstermin und');
    expect(OHNE_BEFUND_SATZ).not.toMatch(/[Aa]lles,? was auf diesem Rechner steht/u);
  });
});
