/**
 * Lizenzangaben für den Bereich „Über diese Software“.
 *
 * Das Anzeigen ist keine Höflichkeit, sondern Pflicht: MIT, BSD und Apache
 * verlangen, dass Lizenztext und Urheberrechtsvermerk mitgeliefert werden,
 * und die EUPL verlangt den Hinweis auf sich selbst. Der amtliche
 * Fragenkatalog bekommt bewusst keine Lizenz – er wird nur mit Quellenangabe
 * unverändert wiedergegeben.
 *
 * Ablageort der Dateien – dieselbe Logik wie beim Fragenkatalog:
 *  - Entwicklung: `<repo>/LICENSE.de.txt` und `<repo>/content/drittlizenzen.json`
 *  - Gepackt:     `<app>/resources/LICENSE.de.txt` und
 *                 `<app>/resources/drittlizenzen.json` (siehe `extraResources`
 *                 in electron-builder.yml)
 *
 * Grundsatz: eine fehlende oder beschädigte Datei darf die Anwendung nicht
 * anhalten. Das betroffene Feld bleibt leer, und im Protokoll steht, was
 * fehlt – die übrigen Angaben werden trotzdem gezeigt.
 */

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

import { app } from 'electron';

import type { Katalog } from '../shared/katalog';
import {
  EUPL_HINWEIS,
  EUPL_KENNUNG,
  KATALOG_HINWEIS,
  type Drittlizenz,
  type Lizenzangaben,
} from '../shared/lizenzen';
import { katalogLaden, sucheAufwaerts } from './katalog';

/** Deutsche Fassung der EUPL 1.2 im Projektwurzelverzeichnis. */
const LIZENZ_DATEI = 'LICENSE.de.txt';

/** Erzeugte Übersicht der mitgelieferten Fremdkomponenten. */
const DRITT_DATEI = 'drittlizenzen.json';

/** Pfad der Drittlizenzen relativ zum Projektwurzelverzeichnis. */
const DRITT_RELATIV = join('content', DRITT_DATEI);

const EUPL_NAME = 'European Union Public Licence, Version 1.2';

/** Obergrenze je Lizenztext – schützt die Anzeige vor einer verbogenen Datei. */
const MAX_TEXT_LAENGE = 200_000;

// ─── Quellen ────────────────────────────────────────────────────────────────

/**
 * Alles, was {@link lizenzenBilden} von außen braucht. Ausdrücklich als
 * Parameter und nicht als Modulzustand, damit sich die Zusammenstellung ohne
 * Electron und ohne echte Anwendungspfade prüfen lässt.
 */
export interface LizenzQuellen {
  /** Pfad zur deutschen EUPL-Fassung; `null`, wenn sie nicht gefunden wurde. */
  readonly lizenzPfad: string | null;
  /** Pfad zu `drittlizenzen.json`; `null`, wenn sie nicht gefunden wurde. */
  readonly drittPfad: string | null;
  /** Geladener Katalog; `null`, wenn er sich nicht laden ließ. */
  readonly katalog: Katalog | null;
  readonly warnen: (meldung: string) => void;
}

function standardWarnung(meldung: string): void {
  console.warn(`[lizenzen] ${meldung}`);
}

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

/**
 * Sucht eine Datei des Projektwurzelverzeichnisses.
 *
 * Gepackt liegt sie flach unter `resources/`, sonst wird vom
 * Anwendungspfad aus aufwärts gesucht – im Quellbaum ist nicht bekannt, aus
 * welchem Verzeichnis gestartet wurde.
 */
function ressourcePfad(gepacktName: string, relativ: string): string | null {
  if (app.isPackaged) {
    const pfad = join(process.resourcesPath, gepacktName);
    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;
}

// ─── Einzelteile ────────────────────────────────────────────────────────────

function lizenztextLesen(pfad: string | null, warnen: (meldung: string) => void): string | null {
  if (pfad === null) {
    warnen(
      `Die deutsche Lizenzfassung (${LIZENZ_DATEI}) wurde nicht gefunden. ` +
        'Der Lizenztext wird nicht angezeigt; die Lizenz selbst gilt unverändert.',
    );
    return null;
  }

  try {
    return readFileSync(pfad, 'utf8').slice(0, MAX_TEXT_LAENGE);
  } catch (fehler) {
    warnen(`Die Datei ${pfad} ist nicht lesbar: ${fehlerText(fehler)}`);
    return null;
  }
}

/** Nimmt einen Eintrag aus `drittlizenzen.json` an – oder verwirft ihn begründet. */
function drittlizenz(
  wert: unknown,
  stelle: string,
  warnen: (meldung: string) => void,
): Drittlizenz | null {
  if (typeof wert !== 'object' || wert === null || Array.isArray(wert)) {
    warnen(`${stelle} ist kein Objekt und wird übergangen.`);
    return null;
  }
  const roh = wert as Record<string, unknown>;

  const name = roh['name'];
  const lizenz = roh['lizenz'];
  if (typeof name !== 'string' || name.trim().length === 0) {
    warnen(`${stelle} hat keinen Namen und wird übergangen.`);
    return null;
  }
  if (typeof lizenz !== 'string' || lizenz.trim().length === 0) {
    warnen(`Der Eintrag „${name}“ hat keine Lizenzangabe und wird übergangen.`);
    return null;
  }

  const text = (feld: unknown): string | null =>
    typeof feld === 'string' ? feld.slice(0, MAX_TEXT_LAENGE) : null;

  const hinweis = roh['hinweis'];

  // `exactOptionalPropertyTypes`: `hinweis` nur setzen, wenn es ihn gibt.
  return {
    name,
    version: text(roh['version']),
    lizenz,
    url: text(roh['url']),
    lizenztext: text(roh['lizenztext']),
    ...(typeof hinweis === 'string' && hinweis.length > 0 ? { hinweis } : {}),
  };
}

function drittlizenzenLesen(pfad: string | null, warnen: (meldung: string) => void): Drittlizenz[] {
  if (pfad === null) {
    warnen(
      `Die Übersicht der Fremdkomponenten (${DRITT_DATEI}) wurde nicht gefunden. ` +
        'Es werden keine Drittlizenzen angezeigt.',
    );
    return [];
  }

  let roh: unknown;
  try {
    roh = JSON.parse(readFileSync(pfad, 'utf8'));
  } catch (fehler) {
    warnen(`Die Datei ${pfad} ist nicht lesbar: ${fehlerText(fehler)}`);
    return [];
  }

  if (typeof roh !== 'object' || roh === null || Array.isArray(roh)) {
    warnen(`Die Datei ${pfad} enthält kein Objekt mit dem Feld „pakete“.`);
    return [];
  }

  const pakete = (roh as Record<string, unknown>)['pakete'];
  if (!Array.isArray(pakete)) {
    warnen(`Die Datei ${pfad} enthält keine Liste „pakete“.`);
    return [];
  }

  const eintraege: Drittlizenz[] = [];
  (pakete as readonly unknown[]).forEach((eintrag, i) => {
    const geprueft = drittlizenz(eintrag, `pakete[${String(i)}]`, warnen);
    if (geprueft !== null) {
      eintraege.push(geprueft);
    }
  });
  return eintraege;
}

// ─── Zusammenstellung ───────────────────────────────────────────────────────

/** Baut die Lizenzangaben aus den übergebenen Quellen zusammen. */
export function lizenzenBilden(quellen: LizenzQuellen): Lizenzangaben {
  const { warnen } = quellen;

  if (quellen.katalog === null) {
    warnen(
      'Der Fragenkatalog ließ sich nicht laden; Quellenangabe und Quell-URL ' +
        'bleiben leer. Der Rechtshinweis zum Katalog wird trotzdem angezeigt.',
    );
  }

  return {
    eigene: {
      kennung: EUPL_KENNUNG,
      name: EUPL_NAME,
      hinweis: EUPL_HINWEIS,
      text: lizenztextLesen(quellen.lizenzPfad, warnen),
    },
    katalog: {
      quellenangabe: quellen.katalog?.meta.quellenangabe ?? '',
      quelleUrl: quellen.katalog?.meta.quelle_url ?? '',
      hinweis: KATALOG_HINWEIS,
    },
    dritte: drittlizenzenLesen(quellen.drittPfad, warnen),
  };
}

let speicher: Lizenzangaben | null = null;

/**
 * Liefert die Lizenzangaben; Dateien werden nur einmal gelesen.
 *
 * Die Angaben ändern sich zur Laufzeit nicht, und der EUPL-Text allein sind
 * rund 18 kB – ein zweites Lesen wäre reine Verschwendung.
 */
export function lizenzenLesen(): Lizenzangaben {
  if (speicher !== null) {
    return speicher;
  }

  let katalog: Katalog | null = null;
  try {
    katalog = katalogLaden();
  } catch (fehler) {
    standardWarnung(`Fragenkatalog nicht verfügbar: ${fehlerText(fehler)}`);
  }

  speicher = lizenzenBilden({
    lizenzPfad: ressourcePfad(LIZENZ_DATEI, LIZENZ_DATEI),
    drittPfad: ressourcePfad(DRITT_DATEI, DRITT_RELATIV),
    katalog,
    warnen: standardWarnung,
  });
  return speicher;
}

/** Leert den Zwischenspeicher – für Tests und einen sauberen Neustart. */
export function lizenzenZuruecksetzen(): void {
  speicher = null;
}
