// @vitest-environment node
/**
 * Jede aufgenommene Inhaltsdatei braucht eine Lizenzangabe.
 *
 * Anlass ist ein selbst verursachter Rückschritt: Mit `content/glossar.json`
 * kam eine neue Inhaltsdatei ins Projekt, ohne Eintrag in `REUSE.toml`. Damit
 * war die Zusage aus einem früheren Stand – „reuse lint ist konform“ – nicht
 * mehr wahr, und niemand hat es bemerkt. Aufgefallen ist es erst, als jemand
 * für einen ganz anderen Zweck nachgesehen hat.
 *
 * `reuse lint` selbst läuft hier nicht: Es ist ein Python-Werkzeug und keine
 * Abhängigkeit dieses Pakets; ein Test, der es aufruft, wäre auf einem
 * Rechner ohne Python rot, ohne dass etwas falsch wäre. Geprüft wird deshalb
 * genau die eine Regel, an der es gescheitert ist – dass keine aufgenommene
 * Inhaltsdatei ohne Eintrag bleibt. Das ersetzt `reuse lint` nicht, es fängt
 * den Fall ab, der wirklich passiert ist.
 *
 * Gefragt wird `git ls-files`, nicht das Dateisystem: `reuse lint` sieht nur
 * aufgenommene Dateien, und unter `content/gesetze/` liegen heruntergeladene
 * Gesetzesarchive, die niemand einchecken will.
 */

import { execFileSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';

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

const wurzel = join(fileURLToPath(new URL('..', import.meta.url)), '..');

/** Aufgenommene Dateien unter `content/`, oder `null` ohne Git. */
function aufgenommeneInhalte(): string[] | null {
  try {
    return execFileSync('git', ['ls-files', 'content'], {
      cwd: wurzel,
      encoding: 'utf8',
      stdio: ['ignore', 'pipe', 'ignore'],
    })
      .split('\n')
      .map((zeile) => zeile.trim())
      .filter((zeile) => zeile.length > 0);
  } catch {
    return null;
  }
}

/**
 * Aufgenommene Dateien unmittelbar im Projektverzeichnis, oder `null` ohne Git.
 *
 * `git ls-files` liefert ohne Pfadangabe den ganzen Baum; hier interessieren
 * nur die Dateien ohne Schrägstrich im Namen. Der Anlass ist derselbe wie
 * oben, eine Ebene höher: `CHANGELOG.md` kam als neue Datei in die Wurzel,
 * und dort greift kein `**`-Muster – sie hätte ohne Lizenzangabe bleiben
 * können, ohne dass es auffällt.
 */
function aufgenommeneWurzeldateien(): string[] | null {
  try {
    return execFileSync('git', ['ls-files'], {
      cwd: wurzel,
      encoding: 'utf8',
      stdio: ['ignore', 'pipe', 'ignore'],
    })
      .split('\n')
      .map((zeile) => zeile.trim())
      .filter((zeile) => zeile.length > 0 && !zeile.includes('/'));
  } catch {
    return null;
  }
}

/**
 * Die Pfadmuster aus den `path = [...]`-Angaben von REUSE.toml.
 *
 * Gelesen werden gezielt diese Listen und nicht jede Zeichenkette der Datei.
 * Der erste Versuch nahm alle Anführungszeichen – und verrutschte an
 * `SPDX-PackageDownloadLocation = ""`: Die leere Zeichenkette fiel durch das
 * Muster, danach war jedes Paar um eins verschoben und die Liste bestand aus
 * Bruchstücken. Aufgefallen ist das nur, weil der Test unten die Ausbeute
 * gegenprüft.
 */
function pfadmuster(): string[] {
  const toml = readFileSync(join(wurzel, 'REUSE.toml'), 'utf8')
    .split('\n')
    .filter((zeile) => !zeile.trimStart().startsWith('#'))
    .join('\n');

  return [...toml.matchAll(/path\s*=\s*\[([^\]]*)\]/gu)].flatMap((liste) =>
    [...(liste[1] ?? '').matchAll(/"([^"]*)"/gu)]
      .map((treffer) => treffer[1] ?? '')
      .filter((pfad) => pfad.length > 0),
  );
}

/**
 * Deckt ein Muster aus REUSE.toml diesen Pfad ab?
 *
 * Nachgebildet wird nur, was das Projekt tatsächlich verwendet: der genaue
 * Pfad und ein abschließendes `**`. Ein vollständiger Glob-Vergleich wäre
 * mehr Code als die Regel, die er prüfen soll.
 */
function abgedeckt(pfad: string, muster: readonly string[]): boolean {
  return muster.some((m) => (m.endsWith('/**') ? pfad.startsWith(m.slice(0, -2)) : m === pfad));
}

describe('Lizenzauszeichnung der Inhalte', () => {
  const muster = pfadmuster();

  it('liest überhaupt Pfade aus REUSE.toml', () => {
    /* Ohne diese Gegenprobe wäre der Test unten immer grün und damit wertlos
       – dieselbe Falle wie eine leere Prüfliste. Genau hier ist der
       Verschiebefehler des ersten Versuchs aufgefallen. */
    expect(muster).toContain('app/**');
    expect(muster).toContain('content/katalog/**');
    expect(muster.every((m) => !m.includes('\n'))).toBe(true);
  });

  it('zeichnet jede aufgenommene Datei unter content/ aus', () => {
    const dateien = aufgenommeneInhalte();
    if (dateien === null) {
      /* Kein Git – dann ist nicht feststellbar, was `reuse lint` sähe. */
      return;
    }

    expect(dateien.length).toBeGreaterThan(3);

    const ohne = dateien.filter((datei) => !abgedeckt(datei, muster));

    expect(
      ohne,
      'Neue Inhaltsdateien gehören in REUSE.toml – sonst ist ihre Herkunft nicht ausgewiesen.',
    ).toEqual([]);
  });

  it('zeichnet jede aufgenommene Datei im Projektverzeichnis aus', () => {
    /*
      Eine Ebene höher als der Test darüber, und aus demselben Anlass: In der
      Wurzel greift kein `**`-Muster, jede Datei dort braucht einen eigenen
      Eintrag. Als `CHANGELOG.md` dazukam, fiel das nur auf, weil jemand
      danach gesucht hat.
    */
    const dateien = aufgenommeneWurzeldateien();
    if (dateien === null) {
      return;
    }

    expect(dateien).toContain('README.md');

    const ohne = dateien.filter((datei) => !abgedeckt(datei, muster));

    expect(
      ohne,
      'Neue Dateien im Projektverzeichnis gehören einzeln in REUSE.toml – ' +
        'ein `**`-Muster deckt die Wurzel nicht ab.',
    ).toEqual([]);
  });

  it('nennt für den amtlichen Katalog eine eigene Lizenzkennung', () => {
    /* Der Katalog darf nie unter die eigene Lizenz des Projekts geraten: Auf
       ein amtliches Werk kann niemand eine Lizenz erteilen. */
    const toml = readFileSync(join(wurzel, 'REUSE.toml'), 'utf8');
    const abKatalog = toml.slice(toml.indexOf('"content/katalog/**"'));
    const bisKennung = abKatalog.slice(0, abKatalog.indexOf('SPDX-License-Identifier') + 60);

    expect(bisKennung).toContain('LicenseRef-Amtliches-Werk-BVA');
    expect(bisKennung).not.toContain('EUPL-1.2');
  });

  /*
    Die genauere Angabe muss hinter der allgemeinen stehen.

    Treffen mehrere Einträge auf dieselbe Datei, gilt der **letzte**. Bis
    Fassung 0.24.1 stand `docs/**` hinter der Aufzählung der Bilder mit
    amtlichem Wortlaut und überschrieb sie vollständig: Am `reuse lint --json`
    nachgemessen trugen alle neun Dateien und der Prüfbericht `EUPL-1.2` –
    also genau die Zuordnung, die die Aufzählung ausschließen sollte. Zwölf
    Zeilen Begründung ohne jede Wirkung, und niemand hat es gesehen, weil
    `reuse lint` in keinem Torwächter läuft.

    Geprüft wird die Reihenfolge und nicht das Ergebnis: `reuse` ist ein
    Python-Werkzeug und keine Abhängigkeit dieses Pakets – dieselbe Überlegung
    wie im Kopf dieser Datei.
  */
  it('stellt die Ausnahmen hinter die pauschale docs-Regel', () => {
    const toml = readFileSync(join(wurzel, 'REUSE.toml'), 'utf8');

    const pauschal = toml.indexOf('path = ["docs/**"]');
    const ausnahme = toml.indexOf('"docs/katalog-pruefbericht.html"');

    expect(pauschal, 'Die pauschale docs-Regel fehlt.').toBeGreaterThanOrEqual(0);
    expect(ausnahme, 'Die Aufzählung der Bilder mit amtlichem Wortlaut fehlt.').toBeGreaterThan(0);
    expect(
      ausnahme,
      'Die Aufzählung steht vor `docs/**` und wird davon überschrieben – ' +
        'bei mehreren Treffern gilt der letzte Eintrag.',
    ).toBeGreaterThan(pauschal);
  });

  it('führt jedes Bildschirmfoto mit amtlichem Wortlaut auf', () => {
    /* Die Liste hier ist die Gegenprobe zur Liste dort: Wer ein Bild
       hinzunimmt, auf dem eine Frage im Wortlaut steht, muss beide anfassen. */
    const toml = readFileSync(join(wurzel, 'REUSE.toml'), 'utf8');
    const block = toml.slice(toml.indexOf('"docs/katalog-pruefbericht.html"'));
    const bisKennung = block.slice(0, block.indexOf('SPDX-License-Identifier') + 60);

    for (const datei of [
      'docs/bildschirmfotos/04-lernsitzung.png',
      'docs/bildschirmfotos/05-lernsitzung-feedback.png',
      'docs/bildschirmfotos/08-pruefungslauf.png',
      'docs/bildschirmfotos/10-erklaerung.png',
      'docs/bildschirmfotos/13-suche.png',
      'docs/store-bilder/01-frage-mit-antwortmoeglichkeiten.png',
      'docs/store-bilder/02-rueckmeldung-mit-begruendung.png',
      'docs/store-bilder/05-fragen-durchsuchen.png',
      'app/v2-frage.png',
    ]) {
      expect(bisKennung, `${datei} fehlt in der Aufzählung.`).toContain(`"${datei}"`);
    }
    expect(bisKennung).toContain('LicenseRef-Amtliches-Werk-BVA');
  });
});
