"""Baut ``content/themen.json`` – die Feingliederung der Kapitel II bis IV.

Warum es diese Datei gibt
-------------------------
Der amtliche Katalog gliedert **nur Kapitel I** in Abschnitte. Für 230 Fragen
aus den Kapiteln II, III und IV steht ``abschnitt`` auf ``null`` – und alles,
was auf Bereichen aufsetzt (Kapitelwahl, Lernstand je Bereich, Themenanalyse
der Prüfungsauswertung, Fragenlisten-Druck), arbeitet für sie zwangsläufig auf
Kapitelebene. Wer in der Simulation an Munitionskunde scheitert, sieht nur
„Kapitel II schwach“ und kann nur das ganze Kapitel nachüben: 92 Fragen.

Diese Gliederung ist **redaktionell**, nicht amtlich. Sie steht deshalb in
einer eigenen Datei und nicht im Katalog – genau wie Erklärungen und Glossar.
Der amtliche Katalog bleibt unangetastet.

Was das Skript garantiert
-------------------------
1. **Vollständigkeit.** Jede Frage der Kapitel II bis IV liegt in genau einer
   Gruppe. Eine unvollständige Gliederung wäre schlimmer als keine: Themen zu
   üben hieße dann, Fragen stillschweigend zu übergehen.
2. **Keine Doppelung.** Keine Frage steht in zwei Gruppen.
3. **Kapiteltreue.** Keine Gruppe mischt Kapitel.
4. **Mindestgröße.** Eine Gruppe mit einer Frage ist kein Thema.

Scheitert eine dieser Prüfungen, wird nichts geschrieben.

Aufruf
------
    python data-pipeline/themen_bauen.py
    python data-pipeline/themen_bauen.py --selbsttest
"""

from __future__ import annotations

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

WURZEL = Path(__file__).resolve().parent.parent
KATALOG = WURZEL / 'content' / 'katalog' / 'katalog.json'
ZIEL = WURZEL / 'content' / 'themen.json'

#: Kapitel ohne amtliche Abschnittsgliederung. Kapitel I bleibt außen vor –
#: dort gibt es die Abschnitte des Bundesverwaltungsamtes, und eine zweite
#: Gliederung daneben wäre eine Zahl, die auseinanderläuft.
KAPITEL = ('II', 'III', 'IV')

#: Weniger als so viele Fragen sind kein Thema, sondern ein Einzelfall.
MINDESTGROESSE = 3

HINWEIS = (
    'Redaktionelle Feingliederung dieser Software, nicht Teil des amtlichen '
    'Fragenkatalogs des Bundesverwaltungsamtes. Der amtliche Katalog gliedert nur '
    'Kapitel I in Abschnitte; fuer die 230 Fragen der Kapitel II bis IV gibt es dort '
    'keine Untergliederung. Nicht von Hand aendern - neu erzeugen mit: '
    'python data-pipeline/themen_bauen.py'
)


def n(kapitel: str, *nummern: int) -> list[str]:
    """Fragen-IDs aus Nummern – die Kapitel II bis IV zählen zweistellig."""
    return [f'{kapitel}-{nummer:02d}' for nummer in nummern]


#: Die Gliederung. Von Hand geschrieben und bewusst grob: Zwölf Gruppen für
#: 92 Fragen sind lernbare Portionen; dreißig wären ein zweites Inhaltsverzeichnis.
GRUPPEN: list[dict] = [
    # ── Kapitel II: Waffentechnik ─────────────────────────────────────────
    {
        'id': 'II.1',
        'titel': 'Waffenarten und Funktionsprinzipien',
        'fragen': n('II', 1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 16, 18, 19, 45, 46, 47, 56, 58),
    },
    {
        'id': 'II.2',
        'titel': 'Wesentliche Teile und Baugruppen',
        'fragen': n('II', 5, 13, 14, 15, 55, 88, 89),
    },
    {
        'id': 'II.3',
        'titel': 'Lauf, Züge und Felder, Zubehör',
        'fragen': n('II', 10, 37, 38, 39, 49, 50, 51, 57),
    },
    {
        'id': 'II.4',
        'titel': 'Schalldämpfer und Schallentstehung',
        'fragen': n('II', 33, 34, 35, 36, 86, 87),
    },
    {'id': 'II.5', 'titel': 'Sicherungen', 'fragen': n('II', 52, 53, 54)},
    {
        'id': 'II.6',
        'titel': 'Munition: Aufbau und Zündung',
        'fragen': n('II', 20, 21, 22, 23, 25, 26, 48),
    },
    {
        'id': 'II.7',
        'titel': 'Kaliber und Munitionsbezeichnung',
        'fragen': n('II', 17, 24, 27, 28, 29, 30, 31, 32, 40, 41, 42, 43, 44),
    },
    {
        'id': 'II.8',
        'titel': 'Ballistik: Begriffe und Teilgebiete',
        'fragen': n('II', 60, 61, 62, 74, 75, 76, 79, 80, 85),
    },
    {
        'id': 'II.9',
        'titel': 'Flugbahn, Drall und Streuung',
        'fragen': n('II', 59, 77, 78, 81, 82, 83, 84),
    },
    {
        'id': 'II.10',
        'titel': 'Reichweite und Gefährdungsbereich',
        'fragen': n('II', 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73),
    },
    {'id': 'II.11', 'titel': 'Unbrauchbarmachung', 'fragen': n('II', 90, 91, 92)},
    # ── Kapitel III: Handhabung von Schusswaffen ──────────────────────────
    {
        'id': 'III.1',
        'titel': 'Grundregeln im Umgang mit Schusswaffen',
        'fragen': n('III', 1, 8, 46),
    },
    {
        'id': 'III.2',
        'titel': 'Übergeben und Annehmen',
        'fragen': n('III', 2, 9, 10, 13, 20),
    },
    {
        'id': 'III.3',
        'titel': 'Laden, Entladen und Schussbereitschaft',
        'fragen': n('III', 11, 12, 27, 37, 38, 39, 40, 43, 45),
    },
    {
        'id': 'III.4',
        'titel': 'Ablegen und Tragen auf dem Stand',
        'fragen': n('III', 18, 19, 21, 26, 33, 42, 44),
    },
    {
        'id': 'III.5',
        'titel': 'Schießstätte: Ordnung, Aufsicht und Gefahrenbereich',
        'fragen': n('III', 15, 16, 17, 22, 23, 24, 25, 30, 31, 32, 34, 35, 48, 49),
    },
    {
        'id': 'III.6',
        'titel': 'Schießen außerhalb von Schießstätten',
        'fragen': n('III', 3, 14, 28, 29),
    },
    {
        'id': 'III.7',
        'titel': 'Munition auf der Schießstätte',
        'fragen': n('III', 4, 5, 6, 7, 36, 41, 47),
    },
    # ── Kapitel IV: Not- und Seenotsignalmittel ───────────────────────────
    {
        'id': 'IV.1',
        'titel': 'Begriffe und Arten der Signalmittel',
        'fragen': n('IV', 1, 2, 3, 9, 41, 42),
    },
    {
        'id': 'IV.2',
        'titel': 'Verwendung im Notfall',
        'fragen': n('IV', 4, 6, 7, 8, 10, 11, 12, 13, 43, 44, 45, 53, 59, 83),
    },
    {
        'id': 'IV.3',
        'titel': 'Technische Daten und Aufbau',
        'fragen': n('IV', 5, 48, 51, 52, 55, 57, 58),
    },
    {
        'id': 'IV.4',
        'titel': 'Kennzeichnung, Zulassung und Klassen',
        'fragen': n('IV', 39, 40, 47, 49, 50, 54, 56, 89),
    },
    {
        'id': 'IV.5',
        'titel': 'Haltbarkeit, Lagerung und Entsorgung',
        'fragen': n('IV', 14, 15, 16, 17, 18, 19, 25, 26, 46),
    },
    {
        'id': 'IV.6',
        'titel': 'Aufbewahrung und Transport',
        'fragen': n('IV', 20, 21, 22, 23, 24, 75, 76, 77),
    },
    {
        'id': 'IV.7',
        'titel': 'Erlaubnisse, Bedürfnis und Dokumente',
        'fragen': n('IV', 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 80, 85, 86, 87, 88),
    },
    {'id': 'IV.8', 'titel': 'Überlassen und Führen', 'fragen': n('IV', 78, 79, 82)},
    {
        'id': 'IV.9',
        'titel': 'Rechtsgrundlagen und Zuständigkeiten',
        'fragen': n('IV', 61, 63, 64, 65, 66, 67, 68, 69, 70, 71),
    },
    {'id': 'IV.10', 'titel': 'Störungen und Versager', 'fragen': n('IV', 72, 73, 74)},
    {
        'id': 'IV.11',
        'titel': 'Missbrauch und Meldepflichten',
        'fragen': n('IV', 60, 62, 81, 84),
    },
]


def pruefen(gruppen: list[dict], fragen: dict[str, dict]) -> list[str]:
    """Alle Zusagen dieser Datei. Leere Liste heißt: in Ordnung."""
    befunde: list[str] = []

    kennungen = [gruppe['id'] for gruppe in gruppen]
    if len(set(kennungen)) != len(kennungen):
        befunde.append('Doppelte Gruppenkennung')

    zugeordnet: dict[str, str] = {}
    for gruppe in gruppen:
        if not gruppe['titel'].strip():
            befunde.append(f'{gruppe["id"]}: Titel fehlt')
        if len(gruppe['fragen']) < MINDESTGROESSE:
            befunde.append(
                f'{gruppe["id"]}: nur {len(gruppe["fragen"])} Fragen '
                f'(mindestens {MINDESTGROESSE})'
            )

        kapitel_der_gruppe = set()
        for frage_id in gruppe['fragen']:
            frage = fragen.get(frage_id)
            if frage is None:
                befunde.append(f'{gruppe["id"]}: {frage_id} gibt es im Katalog nicht')
                continue
            kapitel_der_gruppe.add(frage['kapitel'])
            if frage_id in zugeordnet:
                befunde.append(
                    f'{frage_id} steht in {zugeordnet[frage_id]} und in {gruppe["id"]}'
                )
            zugeordnet[frage_id] = gruppe['id']

        if len(kapitel_der_gruppe) > 1:
            befunde.append(f'{gruppe["id"]}: mischt Kapitel {sorted(kapitel_der_gruppe)}')

    # Die wichtigste Zusage: Vollständigkeit. Eine luecken­hafte Gliederung
    # hiesse, dass „Thema ueben" Fragen stillschweigend uebergeht.
    for frage_id, frage in fragen.items():
        if frage['kapitel'] in KAPITEL and frage_id not in zugeordnet:
            befunde.append(f'{frage_id} ({frage["kapitel"]}) liegt in keiner Gruppe')
        if frage['kapitel'] not in KAPITEL and frage_id in zugeordnet:
            befunde.append(f'{frage_id} gehoert zu Kapitel I und braucht keine Gruppe')

    return befunde


def selbsttest(fragen: dict[str, dict]) -> int:
    """Prüft die Prüfung: Jede Zusage wird einmal absichtlich verletzt."""
    fehler = 0
    beispiel = next(f for f in fragen.values() if f['kapitel'] == 'II')['id']
    zweites = next(
        f for f in fragen.values() if f['kapitel'] == 'II' and f['id'] != beispiel
    )['id']
    drittes = next(
        f
        for f in fragen.values()
        if f['kapitel'] == 'II' and f['id'] not in (beispiel, zweites)
    )['id']
    faelle = [
        ('Luecke', [{'id': 'X', 'titel': 'T', 'fragen': [beispiel, zweites, drittes]}],
         'liegt in keiner Gruppe'),
        ('erfundene Frage',
         [{'id': 'X', 'titel': 'T', 'fragen': ['II-99', beispiel, zweites]}],
         'gibt es im Katalog nicht'),
        ('zu kleine Gruppe', [{'id': 'X', 'titel': 'T', 'fragen': [beispiel]}],
         'mindestens'),
        ('Titel fehlt',
         [{'id': 'X', 'titel': '  ', 'fragen': [beispiel, zweites, drittes]}],
         'Titel fehlt'),
    ]
    for name, gruppen, erwartet in faelle:
        befunde = pruefen(gruppen, fragen)
        if any(erwartet in b for b in befunde):
            print(f'  erkannt:      {name}')
        else:
            print(f'  DURCHGELASSEN: {name}')
            fehler += 1

    # Doppelte Zuordnung braucht zwei Gruppen.
    doppelt = pruefen(
        [
            {'id': 'A', 'titel': 'T', 'fragen': [beispiel, zweites, drittes]},
            {'id': 'B', 'titel': 'T', 'fragen': [beispiel, zweites, drittes]},
        ],
        fragen,
    )
    if any('steht in' in b for b in doppelt):
        print('  erkannt:      doppelte Zuordnung')
    else:
        print('  DURCHGELASSEN: doppelte Zuordnung')
        fehler += 1

    return fehler


def main() -> int:
    katalog = json.loads(KATALOG.read_text(encoding='utf-8'))
    fragen = {f['id']: f for f in katalog['fragen']}

    if '--selbsttest' in sys.argv:
        fehler = selbsttest(fragen)
        print('Selbsttest bestanden.' if fehler == 0 else f'{fehler} Fehler.')
        return 1 if fehler else 0

    befunde = pruefen(GRUPPEN, fragen)
    if befunde:
        for befund in befunde:
            print(f'  {befund}')
        print(f'ABBRUCH: {len(befunde)} Beanstandungen - es wird nichts geschrieben.')
        return 1

    gruppen = [
        {
            'id': gruppe['id'],
            'kapitel': fragen[gruppe['fragen'][0]]['kapitel'],
            'titel': gruppe['titel'],
            'fragen': gruppe['fragen'],
        }
        for gruppe in GRUPPEN
    ]

    daten = {
        'meta': {'version': 1, 'stand': date.today().isoformat(), 'hinweis': HINWEIS},
        'gruppen': gruppen,
    }
    ZIEL.write_text(json.dumps(daten, ensure_ascii=False, indent=1) + '\n',
        encoding='utf-8',
        # LF wie im Archiv verlangt, nicht das CRLF von Windows.
        newline='\n',
    )

    je_kapitel: dict[str, int] = {}
    for gruppe in gruppen:
        je_kapitel[gruppe['kapitel']] = je_kapitel.get(gruppe['kapitel'], 0) + len(
            gruppe['fragen']
        )
    verteilung = ', '.join(f'{k}: {v}' for k, v in sorted(je_kapitel.items()))
    print(f'{len(gruppen)} Gruppen, {sum(je_kapitel.values())} Fragen ({verteilung}).')
    print(f'Geschrieben: {ZIEL.relative_to(WURZEL)}')
    return 0


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