#!/usr/bin/env python3
"""
Liest das Gefahrstoffverzeichnis (CLP-Info) unserer Eigenmarken und schreibt
je Artikel eine Zeile JSON.

    python3 dev/daten/clp-eigenmarken-lesen.py <datei.xlsx> <ziel.jsonl>

Hintergrund: Extrade liefert die Pflichtangaben für Fremdmarken. Für unsere
eigenen Marken (Riccardo, nikoliquids, Twisted, Red Dragon, New Firecastle)
gibt es diese Datei aus dem Gefahrstoffsystem GeSi³ – mit genau den Texten,
die auch auf der Flasche stehen.

Zwei Eigenheiten der Datei bestimmen die Verarbeitung:

1. Sie ist teilweise unfertig. "Noch nicht", "Nichtvorhanden" und "#N/A"
   stehen als Platzhalter in den Feldern. Sie werden wie leer behandelt –
   ein Platzhalter darf nie im Shop landen.

2. Die P-Sätze tragen Untervarianten ("P264.1", "P501.2"). Das sind die
   Fassungen MIT gefüllter Leerstelle, also genau das, was bei den
   Lieferantendaten fehlte. Die Texte werden deshalb wörtlich übernommen
   und nicht über unseren Katalog nachgeschlagen: maßgeblich ist, was auf
   dem Etikett steht.
"""
import json
import re
import sys

import openpyxl

PLATZHALTER = {'', '0', 'none', '#n/a', 'nan', 'noch nicht', 'nichtvorhanden',
               'nicht vorhanden', 'nicht verfügbar', '-'}
SIGNALWORT = {'Gefahr': 'danger', 'Achtung': 'warning'}


def echt(wert) -> str:
    text = ' '.join(str(wert or '').split())
    return '' if text.lower() in PLATZHALTER else text


def amtlicher_code(code: str) -> str:
    """
    "P264.1" auf "P264" zurückführen.

    Die Ziffer hinter dem Punkt ist die Variantennummer des
    Gefahrstoffsystems – sie sagt, WELCHE Fassung eines Satzes mit
    Leerstelle gemeint ist. Auf dem Etikett steht sie nicht, im Shop darf
    sie deshalb auch nicht stehen. Der Text der Variante bleibt natürlich
    erhalten, der ist ja der Gewinn.
    """
    ohne_variante = re.sub(r'\.\d+\b', '', code)

    return re.sub(r'\s*\+\s*', ' + ', ohne_variante).strip()


def spalten(zeile, praefix, anzahl):
    werte = []
    for i in range(1, anzahl + 1):
        wert = echt(zeile.get(praefix if i == 1 else f'{praefix}{i}', ''))
        if wert and wert not in werte:
            werte.append(wert)
    return werte


def main() -> int:
    if len(sys.argv) < 3:
        print(__doc__.strip(), file=sys.stderr)
        return 1
    quelle, ziel = sys.argv[1], sys.argv[2]

    mappe = openpyxl.load_workbook(quelle, data_only=True, read_only=True)

    # Code => amtlicher Wortlaut, wie ihn unser Gefahrstoffsystem druckt
    texte = {}
    for blatt in ('H Sätze', 'P Sätze'):
        if blatt not in mappe.sheetnames:
            continue
        for zeile in mappe[blatt].iter_rows(min_row=2, values_only=True):
            if zeile and zeile[0] and zeile[1]:
                code, text = echt(zeile[0]), echt(zeile[1])
                if code and text:
                    texte.setdefault(code, text)

    blatt = mappe['MPLA']
    zeilen = blatt.iter_rows(values_only=True)
    kopf = [echt(z) for z in next(zeilen)]
    felder = {name: i for i, name in enumerate(kopf) if name}

    geschrieben = unvollstaendig = ohne_ean = 0
    with open(ziel, 'w', encoding='utf-8') as ausgabe:
        for roh in zeilen:
            if not any(roh):
                continue
            zeile = {name: (roh[i] if i < len(roh) else None) for name, i in felder.items()}

            ean = ''
            for feld in ('EAN neue', 'EAN gescannt'):
                wert = echt(zeile.get(feld, ''))
                if re.fullmatch(r'\d{8,14}', wert):
                    ean = wert
                    break
            if not ean:
                ohne_ean += 1
                continue

            gefahr = spalten(zeile, 'H-Sätze', 7)
            sicherheit = spalten(zeile, 'P-Sätze', 8)
            piktogramme = [w for w in spalten(zeile, 'Piktograme', 6) if re.fullmatch(r'GHS0[1-9]', w)]
            signalwort = SIGNALWORT.get(echt(zeile.get('Signalwort', '')))

            # Ohne Gefahrensatz gibt es nichts zu kennzeichnen.
            if not gefahr:
                unvollstaendig += 1
                continue

            def satz(code):
                text = texte.get(code, '')
                return {'code': amtlicher_code(code), 'text': text} if text else None

            eintrag = {
                'ean': ean,
                'name': echt(zeile.get('Riccardo Bezeichnung', '')),
                'marke': echt(zeile.get('Kunde', '')),
                'signalWord': signalwort,
                'pictograms': piktogramme,
                'hazardStatements': [s for s in (satz(c) for c in gefahr) if s],
                'precautionaryStatements': [s for s in (satz(c) for c in sicherheit) if s],
                'ufi': echt(zeile.get('UFI Nummer', '')),
                'ohneText': [c for c in gefahr + sicherheit if c not in texte],
            }

            # EUH208 steht als fertiger Satz in einer eigenen Spalte, weil der
            # Name des Allergens je Rezeptur anders ist.
            euh = echt(zeile.get('EUH208', ''))
            if euh:
                eintrag['hazardStatements'].append({'code': 'EUH208', 'text': euh})

            if not re.fullmatch(r'[A-Z0-9]{4}(-[A-Z0-9]{4}){3}', eintrag['ufi']):
                eintrag['ufi'] = ''

            ausgabe.write(json.dumps(eintrag, ensure_ascii=False) + '\n')
            geschrieben += 1

    print(f'{geschrieben} Artikel geschrieben nach {ziel}', file=sys.stderr)
    print(f'  ohne Gefahrensatz übersprungen: {unvollstaendig}', file=sys.stderr)
    print(f'  ohne EAN übersprungen:          {ohne_ean}', file=sys.stderr)
    return 0


if __name__ == '__main__':
    sys.exit(main())
