/**
 * Lädt die Datenschutzerklärung aus der mitgelieferten Datei.
 *
 * ## Warum genau wie die Lizenztexte
 *
 * Die Anwendung liefert bereits zwei lange Rechtstexte mit und zeigt sie an —
 * den EUPL-Volltext und die Übersicht der Fremdkomponenten. Der Weg dorthin
 * ist erprobt und wird gegen das **gepackte** Paket geprüft
 * (`e2e/gepackt.spec.ts`, „lädt die Lizenztexte aus resources/“): Datei über
 * `extraResources` ins Paket, im Hauptprozess über `process.resourcesPath`
 * gelesen, im Quellbaum aufwärts gesucht. Diese Datei macht dasselbe und
 * erfindet nichts daneben.
 *
 * ## Was bei fehlender Datei geschieht
 *
 * Die Anwendung läuft weiter und sagt, was fehlt. Ein leerer Bereich ohne
 * Begründung wäre die schlechteste Auskunft — der Nutzende hielte die
 * Erklärung für nicht vorhanden statt für nicht ladbar, und ausgerechnet
 * beim Datenschutz ist das der falsche Eindruck.
 *
 * ## Die Prüfsumme wird hier **nicht** geprüft
 *
 * Sie steht in der Datei und wird von `tests/datenschutz.test.ts` gegen
 * `docs/datenschutz.md` gerechnet — also beim Bauen, nicht beim Starten. Zur
 * Laufzeit liegt die Quelldatei gar nicht vor: `docs/` wird nicht
 * mitgeliefert. Eine Prüfung, die im Paket nie etwas zu vergleichen hat,
 * wäre eine Zusage ohne Deckung.
 */

import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';

import { app } from 'electron';

import type {
  Datenschutzangaben,
  Datenschutzblock,
  Datenschutzerklaerung,
} from '../shared/datenschutz';
import { sucheAufwaerts } from './katalog';

const DATEI = 'datenschutz.json';
const RELATIV = join('content', DATEI);

/**
 * Obergrenze beim Lesen.
 *
 * Nachgemessen ist die Datei 80.304 Byte groß. Eine Grenze bei zwei Megabyte
 * lässt jedes denkbare Wachstum zu und fängt zugleich den Fall ab, dass
 * jemand versehentlich etwas anderes unter diesen Namen legt.
 */
const HOECHSTGROESSE = 2 * 1024 * 1024;

let speicher: Datenschutzangaben | null = null;

function fehlerText(fehler: unknown): string {
  return fehler instanceof Error ? fehler.message : String(fehler);
}

function pfadSuchen(): string | null {
  if (app.isPackaged) {
    const pfad = join(process.resourcesPath, DATEI);
    return existsSync(pfad) ? pfad : null;
  }

  for (const start of [app.getAppPath(), __dirname, process.cwd()]) {
    const treffer = sucheAufwaerts(start, RELATIV);
    if (treffer !== null) {
      return treffer;
    }
  }
  return null;
}

/** Prüft die Form so weit, dass die Oberfläche sich darauf verlassen kann. */
function istBlock(wert: unknown): wert is Datenschutzblock {
  if (typeof wert !== 'object' || wert === null) {
    return false;
  }
  const art = (wert as { art?: unknown }).art;
  return art === 'ueberschrift' || art === 'absatz' || art === 'liste' || art === 'code';
}

/**
 * Baut die Angaben aus dem Dateiinhalt.
 *
 * Getrennt von {@link datenschutzLesen}, damit Tests sie ohne Electron und
 * ohne Dateisystem prüfen können – dieselbe Aufteilung wie bei
 * `lizenzenBilden`.
 */
export function datenschutzBilden(roh: string | null, warnung: string | null): Datenschutzangaben {
  if (roh === null) {
    return { erklaerung: null, warnung };
  }

  let gelesen: unknown;
  try {
    gelesen = JSON.parse(roh);
  } catch (fehler) {
    return {
      erklaerung: null,
      warnung: `Die Datenschutzerklärung ist nicht lesbar: ${fehlerText(fehler)}`,
    };
  }

  const daten = gelesen as Partial<Datenschutzerklaerung>;
  const bloecke = Array.isArray(daten.bloecke) ? daten.bloecke.filter(istBlock) : [];

  if (
    typeof daten.fassung !== 'string' ||
    typeof daten.stand !== 'string' ||
    typeof daten.quellpruefsumme !== 'string' ||
    bloecke.length === 0
  ) {
    return {
      erklaerung: null,
      warnung:
        'Die mitgelieferte Datenschutzerklärung ist unvollständig. ' +
        'Die veröffentlichte Fassung gilt unverändert.',
    };
  }

  return {
    erklaerung: {
      quellpruefsumme: daten.quellpruefsumme,
      fassung: daten.fassung,
      stand: daten.stand,
      bloecke,
    },
    warnung: null,
  };
}

/** Liest die Erklärung, einmal je Programmlauf. */
export function datenschutzLesen(): Datenschutzangaben {
  if (speicher !== null) {
    return speicher;
  }

  const pfad = pfadSuchen();
  if (pfad === null) {
    speicher = datenschutzBilden(
      null,
      `Die Datenschutzerklärung (${DATEI}) wurde nicht gefunden. ` +
        'Bitte fordern Sie sie unter der Adresse an, die unter „Rückmeldung und ' +
        'Kontakt“ steht.',
    );
    return speicher;
  }

  let roh: string | null = null;
  let warnung: string | null = null;
  try {
    roh = readFileSync(pfad, 'utf8').slice(0, HOECHSTGROESSE);
  } catch (fehler) {
    warnung = `Die Datei ${DATEI} ist nicht lesbar: ${fehlerText(fehler)}`;
  }

  speicher = datenschutzBilden(roh, warnung);
  return speicher;
}

/** Leert den Zwischenspeicher – Gegenstück für Tests. */
export function datenschutzZuruecksetzen(): void {
  speicher = null;
}
