"""Nachschlagewerkzeug für den amtlichen Gesetzestext.

Beim Schreiben der Erklärungen darf nichts aus dem Gedächtnis zitiert werden.
Dieses Werkzeug macht das Nachschlagen so bequem, dass es keinen Grund gibt,
es zu unterlassen. Grundlage ist `content/gesetze/index.json`, erzeugt aus dem
amtlichen XML von gesetze-im-internet.de.

Aufrufe
-------
Eine Norm im Volltext::

    python data-pipeline/gesetz.py WaffG "§ 12"
    python data-pipeline/gesetz.py AWaffV "§ 13" --absatz 2
    python data-pipeline/gesetz.py StGB "§ 32"

In allen Gesetzen nach einem Begriff suchen::

    python data-pipeline/gesetz.py --suche "Anscheinswaffe"
    python data-pipeline/gesetz.py --suche "Schalldämpfer" --gesetz WaffG

Innerhalb einer Anlage suchen (Anlagen sind lang)::

    python data-pipeline/gesetz.py WaffG "Anlage 1" --suche Kurzwaffen

Die Gliederung einer Norm anzeigen – genau die Angaben, die eine Fundstelle
enthalten darf::

    python data-pipeline/gesetz.py WaffG "§ 12" --gliederung

Übersicht aller Gesetze und ihres Standes::

    python data-pipeline/gesetz.py --gesetze
"""

from __future__ import annotations

import argparse
import io
import json
import re
import sys
from pathlib import Path

WURZEL = Path(__file__).resolve().parent.parent
INDEX = WURZEL / 'content' / 'gesetze' / 'index.json'

#: Zeichen Umfeld je Fundstelle bei der Volltextsuche.
UMFELD = 320


def daten() -> dict:
    if not INDEX.exists():
        raise SystemExit(
            f'Index fehlt: {INDEX}\nErst erzeugen mit: python data-pipeline/gesetze_index.py'
        )
    return json.load(io.open(INDEX, encoding='utf-8'))['gesetze']


def gesetze_auflisten(gesetze: dict) -> int:
    for kuerzel, gesetz in gesetze.items():
        anlagen = sum(1 for n in gesetz['normen'].values() if n['ist_anlage'])
        paragrafen = len(gesetz['normen']) - anlagen
        print(f'{kuerzel:12} {gesetz["langtitel"]}')
        print(f'{"":12} {paragrafen} Paragrafen, {anlagen} Anlagen')
        print(f'{"":12} Stand: {gesetz["stand"]}')
        print(f'{"":12} {gesetz["quelle"]}')
        print()
    return 0


def norm_zeigen(gesetz: dict, kuerzel: str, name: str, absatz: str | None) -> int:
    norm = gesetz['normen'].get(name)
    if norm is None:
        aehnlich = [b for b in gesetz['normen'] if b.replace(' ', '') == name.replace(' ', '')]
        hinweis = f'\nGemeint war vielleicht: {aehnlich[0]}' if aehnlich else ''
        print(f'{name} gibt es im {kuerzel} nicht.{hinweis}')
        return 1

    print(f'=== {kuerzel} {name} – {norm["titel"]} ===')
    print(f'Stand: {gesetz["stand"]}\n')

    if absatz is not None:
        eintrag = norm['absaetze'].get(absatz)
        if eintrag is None:
            vorhanden = ', '.join(norm['absaetze']) or 'keine'
            print(f'Abs. {absatz} gibt es nicht. Vorhanden: {vorhanden}')
            return 1
        print(f'({absatz}) {eintrag["text"]}')
        if eintrag['gliederung']:
            print(f'\nGliederung: {gliederung_text(eintrag["gliederung"])}')
        return 0

    print(norm['text'])
    return 0


def gliederung_text(gliederung: dict[str, list[str]]) -> str:
    """Gliederung als Aufzaehlung.

    Die meisten Absaetze zaehlen mit Nummern auf, einige unmittelbar mit
    Buchstaben (etwa § 3a Abs. 2 SprengG). Die Bezeichnung richtet sich
    deshalb nach der Marke, nicht nach der Ebene.
    """
    teile = []
    for marke, unter in gliederung.items():
        if not marke:
            # Eine leere Marke ist ein Fehler im Index, kein Grund
            # abzubrechen. Bis Fassung 0.24.1 stand hier `marke[0]`, und
            # `gesetz.py BeschG "§ 11" --gliederung` endete mitten in der
            # Ausgabe mit einem IndexError – ausgerechnet das Werkzeug, das
            # zeigen soll, welche Angaben eine Fundstelle tragen darf.
            teile.append('(unlesbare Marke im Index)')
            continue
        art = 'Nr.' if marke[0].isdigit() else 'Buchst.'
        teile.append(f'{art} {marke}' + (f' ({", ".join(unter)})' if unter else ''))
    return '; '.join(teile)


def gliederung_zeigen(gesetz: dict, kuerzel: str, name: str) -> int:
    """Zeigt, welche Angaben eine Fundstelle auf diese Norm enthalten darf."""
    norm = gesetz['normen'].get(name)
    if norm is None:
        print(f'{name} gibt es im {kuerzel} nicht.')
        return 1

    print(f'=== {kuerzel} {name} – {norm["titel"]} ===')
    if norm['ist_anlage']:
        print('Anlage: keine Absatzgliederung. In einer Fundstelle „stelle" verwenden,')
        print('z. B. { "norm": "Anlage 1", "stelle": "Abschnitt 1 Unterabschnitt 1 Nr. 1.1" }')
        return 0

    if not norm['absaetze']:
        print('Keine Absatzgliederung – die Norm wird als Ganzes zitiert.')
        return 0

    for nummer, absatz in norm['absaetze'].items():
        marke = gliederung_text(absatz['gliederung']) if absatz['gliederung'] else '(ohne Nummern)'
        print(f'  Abs. {nummer}: {marke}')
    return 0


def suchen(gesetze: dict, begriff: str, nur: str | None, in_norm: str | None) -> int:
    muster = re.compile(re.escape(begriff), re.IGNORECASE)
    treffer = 0

    for kuerzel, gesetz in gesetze.items():
        if nur is not None and kuerzel != nur:
            continue
        for name, norm in gesetz['normen'].items():
            if in_norm is not None and name != in_norm:
                continue
            for fund in muster.finditer(norm['text']):
                anfang = max(0, fund.start() - UMFELD)
                ende = min(len(norm['text']), fund.end() + UMFELD)
                auszug = norm['text'][anfang:ende].replace('\n', ' | ')
                print(f'--- {kuerzel} {name} ({norm["titel"][:60]}) ---')
                print(f'...{auszug}...\n')
                treffer += 1
                if treffer >= 40:
                    print('(weitere Treffer nicht angezeigt – Suchbegriff eingrenzen)')
                    return 0

    if treffer == 0:
        print(f'„{begriff}" kommt nicht vor' + (f' im {nur}' if nur else ' in keinem der Gesetze'))
        print('Das ist ein belastbares Ergebnis: Was hier fehlt, steht nicht im Gesetz.')
    return 0


def main() -> int:
    zerleger = argparse.ArgumentParser(
        description='Amtlichen Gesetzestext nachschlagen.',
        epilog='Ohne Nachschlagen keine Fundstelle.',
    )
    zerleger.add_argument('gesetz', nargs='?', help='Kürzel, z. B. WaffG')
    zerleger.add_argument('norm', nargs='?', help='„§ 12" oder „Anlage 1"')
    zerleger.add_argument('--absatz', help='Nur diesen Absatz zeigen')
    zerleger.add_argument('--gliederung', action='store_true', help='Nur die Gliederung zeigen')
    zerleger.add_argument('--suche', help='Volltextsuche')
    zerleger.add_argument('--gesetze', action='store_true', help='Alle Gesetze auflisten')
    argumente = zerleger.parse_args()

    gesetze = daten()

    if argumente.gesetze:
        return gesetze_auflisten(gesetze)

    if argumente.suche is not None and argumente.gesetz is None:
        return suchen(gesetze, argumente.suche, None, None)

    if argumente.gesetz is None:
        zerleger.print_help()
        return 1

    if argumente.gesetz not in gesetze:
        print(f'Unbekanntes Gesetz: {argumente.gesetz}')
        print(f'Bekannt sind: {", ".join(gesetze)}')
        return 1

    gesetz = gesetze[argumente.gesetz]

    if argumente.suche is not None:
        return suchen(gesetze, argumente.suche, argumente.gesetz, argumente.norm)

    if argumente.norm is None:
        print(f'{argumente.gesetz} – {gesetz["langtitel"]}, Stand: {gesetz["stand"]}')
        print(f'\nNormen: {", ".join(gesetz["normen"])}')
        return 0

    if argumente.gliederung:
        return gliederung_zeigen(gesetz, argumente.gesetz, argumente.norm)

    return norm_zeigen(gesetz, argumente.gesetz, argumente.norm, argumente.absatz)


if __name__ == '__main__':
    sys.stdout.reconfigure(encoding='utf-8', errors='replace')  # type: ignore[union-attr]
    raise SystemExit(main())
