# -*- coding: utf-8 -*-
"""Erzeugt die Lizenzübersicht der mitgelieferten Fremdsoftware.

Die Anwendung zeigt diese Übersicht unter „Über diese Software“ an. Das ist
keine Höflichkeit, sondern Pflicht: Die verwendeten Lizenzen (MIT, BSD,
Apache-2.0, ISC …) verlangen sämtlich, dass Lizenztext und Urheberrechtsvermerk
mitgeliefert werden.

Ausgabe: content/drittlizenzen.json – wird beim Bauen in die Anwendung kopiert.

Aufruf:
    python tools/lizenzen_erzeugen.py
"""
from __future__ import annotations

import io
import json
import sys
from datetime import date
from pathlib import Path

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")

WURZEL = Path(__file__).resolve().parent.parent
APP = WURZEL / "app"
NODE_MODULES = APP / "node_modules"
ZIEL = WURZEL / "content" / "drittlizenzen.json"

PAKET_SPERRE = APP / "package-lock.json"

# Pakete, die zwar Entwicklungsabhängigkeiten sind, aber im Bündel landen:
# React wird in den Renderer hineinkompiliert, Electron liefert die Laufzeit.
# Sie stehen deshalb fest in der Liste – aus dem Abhängigkeitsbaum ließe sich
# das nicht ableiten.
GEBUENDELT = [
    "electron",
    "react",
    "react-dom",
]


def produktionsabhaengigkeiten() -> list[str]:
    """Alle Pakete, die electron-builder in das Paket kopiert.

    Fest verdrahtet war diese Liste einmal – und lief prompt auseinander:
    better-sqlite3 zieht node-addon-api nach, das mit ausgeliefert wurde, in
    der Anzeige aber fehlte. Abgeleitet wird sie deshalb aus package-lock.json,
    genau wie electron-builder es tut.
    """
    if not PAKET_SPERRE.exists():
        return []

    daten = json.loads(PAKET_SPERRE.read_text(encoding="utf-8"))
    namen = []
    for pfad, eintrag in (daten.get("packages") or {}).items():
        if not pfad.startswith("node_modules/"):
            continue
        if eintrag.get("dev") or eintrag.get("devOptional"):
            continue
        namen.append(pfad[len("node_modules/"):])
    return sorted(namen)

# Dateinamen, unter denen Pakete ihren Lizenztext ablegen.
LIZENZDATEIEN = [
    "LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "LICENCE.md",
    "License", "license", "LICENSE-MIT", "COPYING",
]

# Bestandteile, die Electron mitbringt und die eigene Lizenzen tragen.
ZUSAETZLICH = [
    {
        "name": "Chromium",
        "lizenz": "BSD-3-Clause und weitere",
        "hinweis": (
            "In Electron enthaltene Browser-Engine. Der vollständige Lizenztext "
            "liegt der Installation als LICENSES.chromium.html bei."
        ),
        "url": "https://www.chromium.org/",
    },
    {
        "name": "Node.js",
        "lizenz": "MIT",
        "hinweis": "In Electron enthaltene Laufzeitumgebung.",
        "url": "https://nodejs.org/",
    },
    {
        "name": "SQLite",
        "lizenz": "Public Domain",
        "hinweis": (
            "Datenbank hinter better-sqlite3. SQLite ist gemeinfrei und "
            "unterliegt keinen Lizenzbedingungen."
        ),
        "url": "https://www.sqlite.org/copyright.html",
    },
]


def lies_lizenztext(ordner: Path) -> str | None:
    for name in LIZENZDATEIEN:
        pfad = ordner / name
        if pfad.exists() and pfad.is_file():
            text = pfad.read_text(encoding="utf-8", errors="replace").strip()
            return text if text else None
    return None


def lizenzkennung(meta: dict) -> str:
    lizenz = meta.get("license")
    if isinstance(lizenz, dict):
        return lizenz.get("type", "unbekannt")
    if not lizenz and meta.get("licenses"):
        return ", ".join(l.get("type", "?") for l in meta["licenses"])
    return lizenz or "unbekannt"


def herkunft(meta: dict) -> str | None:
    repo = meta.get("repository")
    if isinstance(repo, dict):
        repo = repo.get("url")
    return meta.get("homepage") or repo


def main() -> int:
    if not NODE_MODULES.exists():
        print(f"FEHLER: {NODE_MODULES} fehlt. Zuerst 'npm install' im Ordner app/ ausführen.")
        return 1

    produktion = produktionsabhaengigkeiten()
    if not produktion:
        print(f"FEHLER: {PAKET_SPERRE} fehlt oder nennt keine Produktionsabhängigkeit.")
        return 1

    # Reihenfolge: erst die gebündelten, dann der Produktionsbaum.
    ausgeliefert = GEBUENDELT + [p for p in produktion if p not in GEBUENDELT]

    eintraege: list[dict] = []
    fehlend: list[str] = []

    for paket in ausgeliefert:
        ordner = NODE_MODULES / paket
        pkg = ordner / "package.json"
        if not pkg.exists():
            fehlend.append(paket)
            continue
        meta = json.loads(pkg.read_text(encoding="utf-8"))
        text = lies_lizenztext(ordner)
        if text is None:
            fehlend.append(f"{paket} (kein Lizenztext gefunden)")
        eintraege.append({
            "name": paket,
            "version": meta.get("version"),
            "lizenz": lizenzkennung(meta),
            "url": herkunft(meta),
            "lizenztext": text,
        })

    for extra in ZUSAETZLICH:
        eintraege.append({
            "name": extra["name"],
            "version": None,
            "lizenz": extra["lizenz"],
            "url": extra["url"],
            "lizenztext": None,
            "hinweis": extra["hinweis"],
        })

    # Jede Produktionsabhängigkeit muss in der Liste stehen. Ohne diese Sperre
    # bliebe eine Verschiebung im Abhängigkeitsbaum still, bis jemand die
    # Anzeige mit dem Paketinhalt vergleicht.
    genannt = {e["name"] for e in eintraege}
    uebersehen = [p for p in produktion if p not in genannt]
    if uebersehen:
        print(f"FEHLER: nicht aufgeführte Produktionsabhängigkeiten: {', '.join(uebersehen)}")
        return 1

    anwendungsversion = json.loads((APP / "package.json").read_text(encoding="utf-8")).get(
        "version", "unbekannt"
    )

    ZIEL.parent.mkdir(parents=True, exist_ok=True)
    ZIEL.write_text(
        json.dumps(
            {
                "stand": date.today().isoformat(),
                "anwendungsversion": anwendungsversion,
                "pakete": eintraege,
            },
            ensure_ascii=False,
            indent=2,
        ),
        encoding="utf-8",
        # Das Archiv verlangt LF (.gitattributes). Ohne diese Angabe
        # schriebe Python auf Windows CRLF: Der Arbeitsbaum sähe
        # geändert aus, ohne dass sich am Inhalt etwas geändert hätte.
        newline="\n",
    )

    print(f"Geschrieben: {ZIEL}")
    for e in eintraege:
        laenge = len(e["lizenztext"]) if e.get("lizenztext") else 0
        version = f" {e['version']}" if e["version"] else ""
        print(f"  {e['name']}{version}: {e['lizenz']}"
              + (f" ({laenge} Zeichen Lizenztext)" if laenge else " (Sammelhinweis)"))
    if fehlend:
        print(f"\nOhne Lizenztext: {', '.join(fehlend)}")
        print("Diese Pakete vor der Auslieferung prüfen.")
        return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
